一、发布对象
1.什么是发布对象
·使一个对象能够被当前范围之外的代码使用。当一个对象能够通过自身的public方法被其他代码得到并修改时,在多线程情况下就会出现线程不安全的现象。
·对象逸出:一种错误的发布。当一个对象还没有构造完成时,就使它被其他线程所见,提供给外部代码一个对象引用。
比如,一个private对象,被类中的public对象发布,就称这个对象被不安全的发布逸出了它本身的private作用域
还有需要注意的this对象逸出问题,一个类还没有构造完成,就被发布出去,这样就造成了this对象逸出问题,这时我们需要想办法在发布对象前完成构造函数,比如使用工厂方法,在下面会重点解释。
·一个可变对象需要被安全发布。
2.安全发布对象的四种方法
在某些情况下我们希望在多个线程间共享对象,此时必须确保对象安全地进行共享。
·在静态初始化函数中初始化一个对象引用。
·将对象的引用保存到volatile类型域或者AtomicReference对象中。
·将对象的引用保存到某个正确构造对象的final类型域中。
·将对象的引用保存到一个由锁保护的域中。
1)一般情况下我们都是在spring框架下进行开发,spring管理的bean都是单例模式,下面介绍如何保证一个实例只被初始化一次且线程安全。
单例模式的实现方法中常见的是“饿汉模式”(只在第一次调用对象时初始化对象),”懒汉模式“(在类装载时初始化对象),但是“饿汉模式”是线程不安全的,“懒汉模式”是线程安全的,因为在“饿汉模式”中获取对象时通过判断当前对象是否已经初始化来决定是否new一个对象,这样在对线程情况下容易出现,两个线程都检测到当前对象没有初始化的情况,从而导致两个线程得到了两个不一样的对象,并且造成了资源浪费。而“饿汉模式”也有缺点,如果类装载时做了过多的操作,会引起性能上的问题,如果使用“饿汉模式”只进行了类的加载,而没有对象的调用还会造成资源的浪费。所以我们想如何将“饿汉模式”做成线程安全的。
“ 饿汉模式”的线程安全写法:
a.给获取对象实例的方法添加syncronized关键字。但是这种方法会导致性能上的开销,因为同一时间只能有一个线程访问获取对象实例的方法。
b.将syncronized关键字下沉到方法体中,并且加上双重检测机制,代码如下,但是这种方法并不能完全保证线程安全,当我们new一个对象时,其实会执行三步操作(1.分配对象内存空间。2.初始化对象。3.设置对象引用指向刚分配的内存),由于指令重排序CPU+JVM优化的原因可能会导致2、3步骤发生重排序,导致new一个对象的操作变成了1、3、2,当线程A第一次判断对象引用是否为空时,线程B正好执行到“1、3、2”中的3,导致线程A发现对象引用已经有值了,导致错误的返回了一个对象的引用,一旦调用就会出现问题。这时需要给对象引用 instance 加上volatile关键字限制它不发生指令重排序。

c.还有一种方法可以既保证对象在调用时仅初始化一次,并且保证线程安全“枚举模式”。
使用枚举来做实例化非常安全,因为枚举类的构造函数是由JVM保证绝对只会调用一次。在类中添加一个私有的枚举类,在枚举类的构造函数中实例化对象,这样既保证了线程安全也保证了不会造成资源浪费,性能上也不错。
