常见的单例模式:饿汉式、懒汉式、双重校验(加强懒汉式)、静态内部类、枚举
饿汉式:
public class Singleton {
private static Singleton singleton=new Singleton();
private Singleton(){
}
public static Singleton newInstance(){
return singleton;
}
}
优点:不存在线程安全问题。
缺点:类加载就创建了单例,如果Singleton对象比较大的话,这种创建方式不推荐。
懒汉式:
public class Singleton {
private static Singleton singleton=null;
private Singleton(){
}
public static Singleton newInstance(){
if(singleton==null){
singleton=new Singleton();
}
return singleton;
}
}
优点:弥补了饿汉式类加载就创建单例的缺点;
缺点:单线程使用是没有问题的,如果是多线程会出现线程安全问题。
双重校验(加强懒汉式)
public class Singleton {
private volatile static Singleton singleton=null;
private Singleton(){
}
public static Singleton getInstance(){
if(singleton==null){//#1
synchronized (Singleton.class) {//#2
if(singleton==null){//#3
singleton=new Singleton();//#4
System.out.println("创建了!!!!!!!!!!!!!!!!!!!");//#5
}//#6
}//#7
}
return singleton;
}
}
优点:弥补了懒汉式的线程安全问题;
缺点:创建单例时需要两次判断,在代码复杂的情况下容易出错。
以下几点需要注意:
- 构造方法定义为private,防止其他类通过构造方法创建对象,破坏单例模式。
- synchronized(Singleton.class)很重要,不是synchronized(this)。两个的区别,前者是同步当前类,后者是同步一个对象
- volatile关键字,这个很重要,如果不加上这个关键字,很可能出现问题,虽然概率极低。具体来说就是synchronized虽然保证了原子性,但却没有保证该区域指令重排序的正确性,会出现A线程执行初始化,但可能因为构造函数里面的操作太多了,所以A线程的uniqueInstance实例还没有造出来,但已经被赋值了。而B线程这时过来了,错以为uniqueInstance已经被实例化出来,一用才发现uniqueInstance尚未被初始化。要知道我们的线程虽然可以保证原子性,但程序可能是在多核CPU上执行。。
就是说可以通过volatile关键字来保证对象singleton=new Singleton()这段代码的有序性,先创建对象再给引用赋值,而不是先给引用赋值,再创建对象。参见【深入理解jvm高级特性】12章,java内存模型与线程 - jdk1.5以上使用,1.5以下volatile对重排序不起作用。
静态内部类:
public class Singleton {
private Singleton(){
}
private static class SingletonHolder{
private static Singleton singleton=new Singleton();
}
public static Singleton newInstance(){
return SingletonHolder.singleton;
}
}
优点:在内部类加载时创建单例,而不是在singleton类加载时创建,弥补了饿汉式的缺点。同时线程是安全的。推荐使用。
枚举:
public enum Singleton {
INSTANCE;
private Object object;
Singleton() {
object = new Object();
}
public Object getInstance(){
return object;
}
}
前边四种都有共同的缺点:
1)需要额外的工作来实现序列化,否则每次反序列化一个序列化的对象时都会创建一个新的实例。
2)可以使用反射强行调用私有构造器(如果要避免这种情况,可以修改构造器,让它在创建第二个实例的时候抛异常)。
而枚举类很好的解决了这两个问题,使用枚举除了线程安全和防止反射调用构造器之外,还提供了自动序列化机制,防止反序列化的时候创建新的对象。因此,《Effective Java》作者推荐使用的方法。不过,在实际工作中,很少看见有人这么写。
单例模式在安卓中的应用(摘抄:https://www.cnblogs.com/android-blogs/p/5530239.html)
比如Android-Universal-Image-Loader中的单例
|
|
比如EventBus中的单例
|
|
上面的单例都是比较规规矩矩的,当然实际上有很多单例都是变了一个样子,单本质还是单例。
如InputMethodManager 中的单例
|
|
AccessibilityManager 中的单例,看代码这么长,其实就是进行了一些判断,还是一个单例
|
|
枚举:枚举被评为 实现单例的最佳方法,但网上很少见到有这么用。而且枚举可以防止反射漏洞。
public enum Singleton {
INSTANCE;
private Object object;
//构造方法
Singleton() {
//想要创建的单例
object = new Object();
}
public Object getInstance(){
return object;
}
}
综合来说,双重校验(DCL)和静态内部类模式使用的比较多。枚举最佳。可以尝试使用。
参考:https://www.2cto.com/kf/201606/517959.html
https://blog.youkuaiyun.com/jm_heiyeqishi/article/details/51052889
https://blog.youkuaiyun.com/goodlixueyong/article/details/51935526