单例模式的另一篇博文
单例设计模式应用场景
在某些情况下,系统只需要一个实例。比如,一个系统存在多个打印任务,但只能一个有一个正在工作的任务。在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。枚举是一个饿汉式加载,因此也就是线程安全的。