转载

【设计模式场景 一】单例模式之破坏

单例模式的另一篇博文

http://blog.csdn.net/nalanmingdian/article/details/77848079

##单例设计模式应用场景
在某些情况下,系统只需要一个实例。比如,一个系统存在多个打印任务,但只能一个有一个正在工作的任务。在Windows系统中只能打开一个任务管理器。所以,有时确保某个对象的唯一性很有必要。它能减少系统的性能开销。
##双重判断加锁及volatile单例模式

	public class Single{
		private Single(){}
		private static volatile Single instance;
		public static Single getInstance(){
			if(instance==null){
				synchronized(Single.class){
					if(instance==null)
						instance=new Single();
				}
			}
			return instance;
		}

	}

####为什么用Single.class而不用instance?
首先,锁只有两种,一种是类锁,一种是对象锁。在此处,由于instance未初始化,所以会报空指针异常。因此只能使用类锁。
###以上单例模式就是绝对线程安全的么?
不是!!!单例设计模式还是可以被破坏的。
例如反射,例如序列化。
##反射破坏单例模式
反射机制,就是动态的去获取类的所有信息,包括私有的构造函数。

public static void main(String[] args){
		try {
			Constructor cs=Class.forName("test.Single")
					.getDeclaredConstructor();
			//setAccessible是可以访问私有的成员变量
			cs.setAccessible(true);
			//反射机制获得实例
			Single s1=(Single) cs.newInstance();
			Single s2=(Single) cs.newInstance();
			//常规方法获得实例
			Single s3=Single.getInstance();
			Single s4=Single.getInstance();
			
			System.out.println("反射机制获得实例s1==s2:"+s1.equals(s2));
			System.out.println("常规方法获得实例s3==s4:"+s3.equals(s4));
			System.out.println("反射与常规的比较s1==s3:"+s1.equals(s3));
			
		} catch (Exception e) {
		}
	}

结果如下:

反射机制获得实例s1==s2:false
常规方法获得实例s3==s4:true
反射与常规的比较s1==s3:false

由上述代码可以看到,对于单例模式来说,反射可以获得其构造方法,这样就会导致可以创建多个实例,破坏了单例模式的对象唯一性。
预防:对单例模式中构造函数的调用次数进行限制。
代码如下:

class Single{
	private static boolean flag=false;
	private Single(){
		synchronized(Single.class)  
        {  
            if(flag == false)  
            {  
                flag = !flag;  
            }  
            else  
            {  
                throw new RuntimeException("单例模式被侵犯!");  
            }  
        }  
	}
	private static volatile Single s;
	public static Single getInstance(){
		if(s==null){
			synchronized (Single.class) {
				System.out.println("shili");
				if(s==null)
					s=new Single();
			}
		}
		return s;
	}
}

这里写图片描述
##序列化与反序列化破坏单例模式
###序列化与反序列化的定义
**对象持久化方式。**ObjectInputStream ObjectOutputStream…
**把对象转化为字节序列的过程就是对象的序列化;**序列化可以将对象写在流中进行网络传输,或者保存到文件、数据库等系统里。
把字节序列恢复成对象的过程就是对象的反序列化。
都实现Serializable接口(java.lang包中,没有任何方法)。
1。类能被序列化,其子也能被序列化。
2。static类成员,transient(对象存储时,该实例变量的值不需要维持)代表对象的临时数据,都不能被序列化。
在序列化与反序列化中,**serialVersionUID(static final)**非常重要,每个类的都是特定的,反序列化来通过它判定类的兼容性。不一致的话会抛出异常。
###如何破坏

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class Test1{
	public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException{
		 //Write Obj to file 序列化
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
        oos.writeObject(Single.getInstance());
        //Read Obj from file 反序列化
        File file = new File("tempFile");
        ObjectInputStream ois =  new ObjectInputStream(new FileInputStream(file));
        Single newInstance = (Single) ois.readObject();
        //判断是否是同一个对象
        System.out.println(newInstance == Single.getInstance());
        //结果为false
	}
	
}
class Single implements Serializable{
	private Single(){}
	private static volatile Single s;
	public static Single getInstance(){
		if(s==null){
			synchronized (Single.class) {
				if(s==null)
					s=new Single();
			}
		}
		return s;
	}
}

由以上代码可知,通过序列化和反序列化,得到的对象是一个新的对象,破坏了单例模式对象的唯一性。
对象的序列化过程通过ObjectOutputStream和ObjectInputputStream来实现的,那么带着刚刚的问题,分析一下ObjectInputputStream 的readObject 方法执行情况到底是怎样的。
为了节省篇幅,这里给出ObjectInputStream的readObject的调用栈
readObject—>readObject0—>readOrdinaryObject—>checkResolve
这里看一下重点代码,readOrdinaryObject方法的代码片段:

private Object readOrdinaryObject(boolean unshared)
        throws IOException
    {
        //此处省略部分代码
        Object obj;
        try {
            obj = desc.isInstantiable() ? desc.newInstance() : null;
        } catch (Exception ex) {
            throw (IOException) new InvalidClassException(
                desc.forClass().getName(),
                "unable to create instance").initCause(ex);
        }
        //此处省略部分代码
 
        if (obj != null &&
            handles.lookupException(passHandle) == null &&
            desc.hasReadResolveMethod())
        {
            Object rep = desc.invokeReadResolve(obj);
            if (unshared && rep.getClass().isArray()) {
                rep = cloneArray(rep);
            }
            if (rep != obj) {
                handles.setObject(passHandle, obj = rep);
            }
        }
        return obj;
    }

如第一部分所示:

Object obj;
try {
    obj = desc.isInstantiable() ? desc.newInstance() : null;
} catch (Exception ex) {
    throw (IOException) new InvalidClassException(desc.forClass().getName(),"unable to create instance").initCause(ex);
}

obj对象即本方法要返回的对象。也可以暂时理解为ObjectInputStream的readObject返回的对象。
desc.isInstantiable:如果一个serializable/externalizable的类可以在运行时被实例化,那么该方法就返回true。
desc.newInstance:该方法通过反射的方式调用无参构造方法新建一个对象。

因此,**反序列化时会调用无参数的构造方法创建一个新的对象。**这就破坏了单例模式的对象唯一性。
###防止序列化破坏
由第二段代码得知,

if (obj != null &&
            handles.lookupException(passHandle) == null &&
            desc.hasReadResolveMethod())
        {
            Object rep = desc.invokeReadResolve(obj);
            if (unshared && rep.getClass().isArray()) {
                rep = cloneArray(rep);
            }
            if (rep != obj) {
                handles.setObject(passHandle, obj = rep);
            }
        }

hasReadResolveMethod:如果实现了serializable 或者 externalizable接口的类中包含readResolve则返回true
invokeReadResolve:通过反射的方式调用要被反序列化的类的readResolve方法。
因此,我们在单例模式中定义一个readResolve方法,并在该方法中指定要返回的对象的生成策略,就可以防止单例被破坏了。

//在单例中添加
private Object readResolve() {
        return singleton;
    }

##枚举方法实现单例模式
在Java1.5版本起,单元素枚举实现单例模式成为最佳方法。
###枚举知识
枚举类似类,一个枚举可以拥有成员变量,成员方法,构造方法。
最基本用法:

	enum Type{
		A,B,C,D;
	}

其实,在创建时,编译器会自动生成一个继承java.lang.Enum的类,因此枚举类型不能再继承其他的类,Enum类中实现了Serializable接口,使其序列化与反序列化的对象是同一个
,如上面的enum可以看作是如下方式实现的:

class Type extends Enum{
    public static final Type A;
    public static final Type B;
    ...
}

可以把Type看作类,把ABCD看作实例。
一个enum的构造方法是private的,不允许对其进行调用。
也可以在其中创建“类方法”,“实例方法”

enum Type{
    A,B,C,D;

    static int value;
    public static int getValue() {
        return value;
    }

    String type;
    public String getType() {
        return type;
    }
}

在原有的基础上,添加了类方法和实例方法。我们把Type看做一个类,那么enum中静态的域和方法,都可以视作类方法。和我们调用普通的静态方法一样,这里调用类方法也是通过 Type.getValue()即可调用,访问类属性也是通过Type.value即可访问。
下面的是实例方法,也就是每个实例才能调用的方法。那么实例是什么呢?没错,就是A,B,C,D。所以我们调用实例方法,也就通过 Type.A.getType()来调用就可以了。
最后,对于某个实例而言,还可以实现自己的实例方法。再看下下面的代码:

enum Type{
	A{
	    public String getType() {
	        return "I will not tell you";
	    }
	},B,C,D;
	static int value;
	
	public static int getValue() {
	    return value;
	}
	
	String type;
	public String getType() {
	    return type;
	 }
}

A实例后面的{…}就是属于A的实例方法,可以通过覆盖原本的方法,实现属于自己的定制。 除此之外,我们还可以添加抽象方法在enum中,强制ABCD都实现各自的处理逻辑:

enum Type{
    A{
        public String getType() {
            return "A";
        }
    },B {
        @Override
        public String getType() {
            return "B";
        }
    },C {
        @Override
        public String getType() {
            return "C";
        }
    },D {
        @Override
        public String getType() {
            return "D";
        }
    };

    public abstract String getType();
}

###枚举单例
枚举类的实例都大写。
枚举的构造方法是私有化的,每个实例都会调用一次构造函数。

enum SingletonC{
    INSTANCE;
    private String field;

    public String getField() {
        return field;
    }

    public void setField(String field) {
        this.field = field;
    }
}

调用 SingletonC.INSTANCE;
##总结
1。枚举实例是static final 的,保证只被实例化一次。
2。对于序列化与反序列化来说,枚举的继承的Enum类默认实现了Serializable接口。在对枚举类进行序列化时,还不需要添加readRsolve方法就可以避免单例模式被破坏。

//枚举类的声明
public abstract class Enum<E extends Enum<E>>
        implements Comparable<E>, Serializable

3。对于反射来说,枚举的反编译是一个抽象类,就不能通过反射来创建实例了。
4。枚举是一个饿汉式加载,因此也就是线程安全的。

文章最后发布于: 2017-09-16 11:55:21
展开阅读全文
0 个人打赏
私信求帮助

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: Age of Ai 设计师: meimeiellie

分享到微信朋友圈

×

扫一扫,手机浏览