发布对象
- 发布对象:使一个对象能够被当前范围之外的代码所使用
- 对象溢出:一种错误的发布,当一个对象还没有构造完成时,就使它被其他线程所见
不正确的发布可变对象导致的两种错误:
1.发布线程意外的所有线程都可以看到被发布对象的过期的值
2.线程看到的被发布对象的引用是最新的,然而被发布对象的状态却是过期的
Java 线程安全性中的对象发布和逸出
单例模式
懒汉模式:
/** * 懒汉模式 * 单例实例在第一次使用时进行创建 */ @NotThreadSafe public class SingletonExample1 { // 私有构造函数 private SingletonExample1() { } // 单例对象 private static SingletonExample1 instance = null; // 静态的工厂方法 public static SingletonExample1 getInstance() { if (instance == null) { instance = new SingletonExample1(); } return instance; } }
这个是线程不安全的,如果同时有两个线程到达
if(instance == null)
一个new完之后还会再new一个对象
为了使得具有线程安全性,可以使用synchronized关键字,但是这种方式会使性能受影响。不推荐。
/** * 懒汉模式 * 单例实例在第一次使用时进行创建 */ @ThreadSafe @NotRecommend public class SingletonExample3 { // 私有构造函数 private SingletonExample3() { } // 单例对象 private static SingletonExample3 instance = null; // 静态的工厂方法 public static synchronized SingletonExample3 getInstance() { if (instance == null) { instance = new SingletonExample3(); } return instance; } }
改进:
/** * 懒汉模式 -》 双重同步锁单例模式 * 单例实例在第一次使用时进行创建 */ @NotThreadSafe public class SingletonExample4 { // 私有构造函数 private SingletonExample4() { } // 1、memory = allocate() 分配对象的内存空间 // 2、ctorInstance() 初始化对象 // 3、instance = memory 设置instance指向刚分配的内存 // JVM和cpu优化,发生了指令重排 // 1、memory = allocate() 分配对象的内存空间 // 3、instance = memory 设置instance指向刚分配的内存 // 2、ctorInstance() 初始化对象 // 单例对象 private static SingletonExample4 instance = null; // 静态的工厂方法 public static SingletonExample4 getInstance() { if (instance == null) { // 双重检测机制 // B synchronized (SingletonExample4.class) { // 同步锁 if (instance == null) { instance = new SingletonExample4(); // A - 3 } } } return instance; } }
但是这种方式依然是线程不安全的。原因是可能发生指令重排的情况,比如AB两个想同时到达这个方法,当A已经执行到 instance = new SingletonExample4(); // A - 3 的时候,在cpu中发生重排执行了
12这两步,此时B执行到 if (instance == null),发现instance不为空,直接return,而其实instance并没有进行初始化,B拿到这个对象就会发生错误。
进一步改进:使用volatile关键字,禁止进行指令重排(前面我们讲过,volatile的使用场景由两个,一个是状态表示量,一个是double check双重检测,也就是使用在这了)
注意:
volatile阻止的不
singleton = new Singleton()这句话内部[1-2-3]的指令重排,
而是保证了在一个写操作([1-2-3])完成之前,不会调用读操作(
if (instance == null)
)。
/** * 懒汉模式 -》 双重同步锁单例模式 * 单例实例在第一次使用时进行创建 */ @ThreadSafe public class SingletonExample5 { // 私有构造函数 private SingletonExample5() { } // 1、memory = allocate() 分配对象的内存空间 // 2、ctorInstance() 初始化对象 // 3、instance = memory 设置instance指向刚分配的内存 // 单例对象 volatile + 双重检测机制 -> 禁止指令重排 private volatile static SingletonExample5 instance = null; // 静态的工厂方法 public static SingletonExample5 getInstance() { if (instance == null) { // 双重检测机制 // B synchronized (SingletonExample5.class) { // 同步锁 if (instance == null) { instance = new SingletonExample5(); // A - 3 } } } return instance; } }
饿汉模式:
import com.mmall.concurrency.annoations.ThreadSafe; /** * 饿汉模式 * 单例实例在类装载时进行创建 */ @ThreadSafe public class SingletonExample2 { // 私有构造函数 private SingletonExample2() { } // 单例对象 private static SingletonExample2 instance = new SingletonExample2(); // 静态的工厂方法 public static SingletonExample2 getInstance() { return instance; } }
它是线程安全的。
缺点 1.如果创建过程中进行很多的运算,会导致类加载的时候特别的慢
2.如果创建出来的实例要很久以后才被调用,那么会导致资源的浪费
前面是使用静态域 的方式,我们也可以使用静态代码块的方式:在写静态域和静态代码块的时候一定要注意他们之间的顺序,他们是按顺序执行的,顺序不同出现的结果也会不同
/** * 饿汉模式 * 单例实例在类装载时进行创建 */ @ThreadSafe public class SingletonExample6 { // 私有构造函数 private SingletonExample6() { } // 单例对象,要放在静态代码块的前面 private static SingletonExample6 instance = null; static { instance = new SingletonExample6(); } // 静态的工厂方法 public static SingletonExample6 getInstance() { return instance; } public static void main(String[] args) { System.out.println(getInstance().hashCode()); System.out.println(getInstance().hashCode()); } }
枚举模式
/** * 枚举模式:最安全 */ @ThreadSafe @Recommend public class SingletonExample7 { // 私有构造函数 private SingletonExample7() { } public static SingletonExample7 getInstance() { return Singleton.INSTANCE.getInstance(); } private enum Singleton { INSTANCE; private SingletonExample7 singleton; // JVM保证这个方法绝对只调用一次 Singleton() { singleton = new SingletonExample7(); } public SingletonExample7 getInstance() { return singleton; } } }
相比于懒汉模式更加容易保证安全性,相比饿汉模式只在需要使用的时候才执行初始化。
这篇博客对java单例模式讲解的非常好:
https://www.jianshu.com/p/eb30a388c5fc?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io