单例模式(Singleton Pattern
)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式。在 GOF 书中给出的定义为:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
单例模式,指提供一种机制保证类只会被初始化成一个对象。
首先我们看看对象有几种来源:
- 通过调用构造函数获得;
- 通过反序列化获得。
- 通过反射获得。
所以如果要实现单例模式就要保证这几种方式获得的对象都是同一个。
我们依次从易到难的看:
1 如果我们考虑通过构造函数获取对象,那么我们只要将构造函数设置为private的,然后提供一个函数返回一个对象即可;
public class MySingleton {
private static MySingleton instance = new MySingleton();
private MySingleton(){
}
public static MySingleton getInstance(){
return instance;
}
}
上面这种方式在类加载的时候就初始化了一个MySingleton对象,当我们后面调用getInstance的时候,它返回的都是同一个对象。
但是这种方式有一个缺点:就是对象会在类加载的时候被初始化,如果的初始化很耗时,那么它将导致启动变慢;如果该对象将来没被用到,那么初始化它就等于白白浪费时间了,所以这种方式适合哪种确定会用到的,初始化不耗时的类。
所以我这里再给出另外一种初始化方式:
public class Singleton implements Serializable{
private volatile static Singleton instance;
private Singleton(){
}
public static Singleton getInstance(){
if(instance == null){
synchronized (Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
public Object readResolve(){
return instance;
}
}
这里有几点需要注意一下:
instance 前面的volatile: 它是为了防止构造函数里面的指令重排序,把初始化一半的对象暴露出来;
readResolve() 方法: 它是防止反序列化时得到不同的对象。
上面的代码虽然保证了线程安全和反序列化单例,但是无法防止通过反射的方式破坏单例。
此时,我们就需要修改构造函数来保证,构造函数只被调用一次:
private static boolean flag = false;
private Singleton(){
synchronized (Singleton.class){
if(!flag){
flag = true;
}
else{
throw new RuntimeException("不能二次初始化");
}
}
}
通过上面的讨论我们可以发现,在不提前初始的情况下,想写一个线程安全的单例模式还是很麻烦的。
小小单例都这么麻烦?没有简单的实现方式吗?被你问找了,还真有!!那就是通过枚举的方式
public enum EnumSingleton {
INSTANCE;
private String name;
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
}
简单明了,线程安全,又防止了反射,反序列化破坏单例模式。
网友评论