什么是单例模式:
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
注意:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
1.不好的方法:懒汉式(单线程下)
评价:lazy loading,线程不安全,在多线程下不能工作
//懒汉式
public class Singleton {
private static Singleton instance;
public Singleton(){
}
public static Singleton getInstance(){
if(instance==null){
instance=new Singleton();
}
return instance;
}
}
注意: 为什么会出现线程问题,线程A希望使用Singleton,调用getInstance()方法。因为是第一次调用,A就发现instance是null的,于是它开始创建实例,就在这个时候,CPU发生时间片切换,线程B开始执行,它要使用Singleton,调用getInstance()方法,同样检测到instance是null——注意,这是在A检测完之后切换的,也就是说A并没有来得及创建对象——因此B开始创建。B创建完成后,切换到A继续执行,因为它已经检测完了,所以A不会再检测一遍,它会直接创建对象。这样,线程A和B各自拥有一个Singleton的对象——单例失败!
所以我们需要下面的方法来改进。
2.不好的方法:懒汉式(多线程)
评价:lazy loading,加了Synchronize,线程安全,但是同时也会因为加了synchronize而影响效率
//懒汉 多线程
public class Singleton1 {
private static Singleton1 instance ;
public Singleton1(){}
public static synchronized Singleton1 getInstance1(){
if(instance==null){
instance =new Singleton1();
}
return instance;
}
}
3.可行的方法:饿汉模式
线程安全,非懒加载
//饿汉模式
public class Singleton2 {
private static Singleton2 instance = new Singleton2();
public Singlenton2(){}
public static Singleton2 getInstance2() {
return instance;
}
}
4.双检锁
原理可参照下面两个链接的内容:
https://blog.csdn.net/u011889786/article/details/53364618
https://www.jianshu.com/p/d82cbb83f393?from=timeline
第二个图片中线程执行顺序的图片有问题,参照第一个链接。
//双检锁
public class Singleton4 {
private volatile static Singleton4 singleton4 ;
public Singleton4(){}
public static Singleton4 getInstance4(){
if(singleton4==null){
synchronized(Singleton4.class){
if(singleton4==null){
singleton4=new Singleton4();
}
}
}
return singleton4;
}
}
5.推荐方法:静态内部类
这种方法即实现了懒加载,又保证了实例化instance时只有一个线程。饿汉式中只要类被装载,那么instance就会被实例化,而在此方法中Singleton类被装载了,instance也不一定被实例化。因为SingletonHolder类没有被主动使用。只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。
想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比第3种种方式就显得很合理。
public class Singleton3 {
private static class SingletonHolder{
private static final Singleton3 instance = new Singleton3();
}
private Singleton3 (){}
public static final Singleton3 getInstance(){
return SingletonHolder.instance;
}
}
6.枚举
描述:这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化
这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。
经验之谈:一般情况下,不建议使用第 1 种和第 2 种懒汉方式,建议使用第 3 种饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用第 5 种登记方式。如果涉及到反序列化创建对象时,可以尝试使用第 6 种枚举方式。如果有其他特殊的需求,可以考虑使用第 4 种双检锁方式。