发布对象: 使一个对象能够被当前范围之外的代码所使用
对象溢出: 一种错误的发布, 当一个对象还没有构造完成时, 就使它被其他线程所见
不正确的发布可变对象导致的两种错误:
1, 发布线程意外的所有线程都可以看到被发布对象的过期的值
2, 线程看到的被发布对象的引用是最新的, 然而被发布对象的状态却是过期的
下面使用代码对不安全的发布和对象溢出进行说明:
不安全的发布示例
- import com.gwf.concurrency.annoations.NotThreadSafe;
- import lombok.extern.slf4j.Slf4j;
- import java.util.Arrays;
- /**
- * 不安全的发布
- * @author gaowenfeng
- * @date 2018-03-19
- */
- @Slf4j
- @NotThreadSafe
- public class UnsafePublish {
- private String[] states = {"a","b","c"};
- /**
- * 通过 public 发布级别发布了类的域, 在类的外部, 任何线程都可以访问这个域
- * 这样是不安全的, 因为我们无法检查其他线程是否会修改这个域导致了错误
- * @return
- */
- public String[] getStates() {
- return states;
- }
- public static void main(String[] args) {
- UnsafePublish unsafePublish = new UnsafePublish();
- log.info("{}", Arrays.toString(unsafePublish.getStates()));
- unsafePublish.getStates()[0] = "d";
- log.info("{}", Arrays.toString(unsafePublish.getStates()));
- }
- }
对象溢出示例
- /**
- * 对象溢出
- * 在对象构造完成之前, 不可以将其发布
- * @author gaowenfeng
- * @date
- */
- @Slf4j
- @NotThreadSafe
- @NotRecommend
- public class Escape {
- private int thisCannBeEscape = 0;
- public Escape(){
- new InnerClass();
- }
- /**
- * 包含了对封装实例的隐藏和引用, 这样在对象没有被正确构造完成之前就会被发布, 由此导致不安全的因素在里面
- * 导致 this 引用在构造期间溢出的错误, 他是在构造函数构造过程中启动了一个线程, 造成 this 引用的溢出
- * 新线程只是在对象构造完毕之前就已经看到他了, 所以如果要在构造函数中创建线程, 那么不要启动它,
- * 而是应该才用一个专有的 start, 或是其他的方式统一启动线程
- * 使用工厂方法和私有构造函数来完成对象创建和监听器的注册来避免不正确的发布
- */
- private class InnerClass{
- public InnerClass(){
- log.info("{}",Escape.this.thisCannBeEscape);
- }
- }
- public static void main(String[] args) {
- new Escape();
- }
- }
安全发布对象
安全发布对象有以下四种方法:
1, 在静态初始化函数中初始化一个对象引用
2, 将对象的引用保存到 volatile 类型域或者 AtomicReference 对象中
3, 将对象的引用保存到某个正确构造的 final 类型域中
4, 将对象的引用保存到一个由锁保护的域中
1 和 2 实例参考双重检测机制的线程安全的单例模式, 4 的实例可以参考线程安全的懒汉式单例模式. 线程安全的单例模式的几种实现方式参考: 单例模式 https://www.cnblogs.com/z00377750/p/9136821.html
来源: http://www.bubuko.com/infodetail-2659832.html