懒汉模式的导致的线程问题
- 普通懒汉模式代码:
public class Singleton {
private static Singleton singleton = null;
/**
* 私有化实例,防止new出对象
*/
private Singleton(){}
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
问题:由于多线程的情况下线程执行是非常快的,如果是饿汉模式下确实保证了一个实体类只存在一个对象,但是在懒汉模式下由于需要对当前对象进行判断是否为空,在多条线程执行的情况下如果第一条线程正在执行如下代码:
if (singleton == null)
但是此时还没有执行singleton = new Singleton();的时候,第二条线程进入,这时两条或多条线程同时进行:
singleton = new Singleton();
那么这两条线程获取到的并不是同一个对象
- 有三种解决方式:
- 双重判断,这里最大的疑问可能就是第一重判断是否需要,仔细去想就会发现第一重判断会筛选掉大部分的线程获取锁,只会有小部分的线程执行到线程锁的那一步,我们都知道synchronized 的效率是比较低的,如果没有第一重判断每个线程都去获取锁会大大降低执行的效率,减少死锁等风险
public class SingletonThread { private static volatile SingletonThread singleton = null; /** * 私有化实例,防止new出对象 */ private SingletonThread(){} public static SingletonThread getInstance() { if (singleton == null) { synchronized (SingletonThread.class) { if (singleton == null) { singleton = new SingletonThread(); } } } return singleton; } }
- 静态内部类方式:静态内部类在外部类被加载的时候内部类是不会被加载的,只有调用getInstance方法后这个类才会被加载
public class SingletonThread { private static SingletonThread singleton = null; /** * 私有化实例,防止new出对象 */ private SingletonThread(){} private static class Holder { private final static SingletonThread singletonThread = new SingletonThread(); } public static SingletonThread getInstance() { return Holder.singletonThread; } }
- 通过枚举创建实例,因为枚举是默认为静态的,并且默认为私有的,每一个枚举系统会自动添加 public static final 修饰,所以无论如何创建枚举不存在线程安全问题,并且永远是单例:
/** * 不仅可以解决线程问题,还可以防止反射导致的多实例问题 * 枚举的特性: * <p>1.枚举类是一种特殊的类,它和普通的类一样,有自己的成员变量、成员方法、构造器 (只能使用 private 访问修饰符,所以无法从外部调用构造器,构造器只在构造枚举值时被调用); * 例如:SingletonThread2 instance = SingletonThread2.INSTANCE 此时枚举被调用</p> * <p>2.使用 enum 定义的枚举类默认继承了 java.lang.Enum 类,并实现了 java.lang.Seriablizable 和 java.lang.Comparable 两个接口;</p> * <p>3.所有的枚举值都是 public static final 的,且非抽象的枚举类不能再派生子类;</p> * <p>4.枚举类的所有实例(枚举值)必须在枚举类的第一行显式地列出,否则这个枚举类将永远不能产生实例。列出这些实例(枚举值)时,系统会自动添加 public static final 修饰,无需程序员显式添加。</p> * */ public enum SingletonThread2 { INSTANCE; }