1. 定义

  • 发布对象(Publish): 使一个对象能够被当前范围之外的代码所使用
  • 对象逸出(Escape): 一种错误的发布。当一个对象还没有构造完成时,就使它被其他线程所见

1.1 发布对象

public class UnsafePublish {private String[] states = {"a","b","c"};public String[] getStates(){return states;}/*** 通过new UnsafePublish()发布了一个UnsafePublish类的实例* 通过实例的public方法得到了私有域states数组的引用* 可以在其他任何线程里修改这个数组里的值* 这样在其他线程中想使用states数组时,它的值是不完全确定的* 因此这样发布的对象是线程不安全的,因为无法保证是否有其他线程对数组里的值进行了修改* @param args*/public static void main(String[] args) {UnsafePublish unsafePublish = new UnsafePublish();for (String i : unsafePublish.getStates()) {System.out.print(i+" ");}unsafePublish.getStates()[0] = "d";System.out.println();for (String i : unsafePublish.getStates()) {System.out.print(i+" ");}}
}

1.2 对象逸出

@NotThreadSafe
public class Escape {private int thisCanBeEscape = 0;public Escape(){new InnerClass();}/*** 内部类的实例里面包含了对封装内容thisCanBeEscape的隐含引用* 这样在对象没有被正确构造之前,他就会被发布,有可能有不安全的因素在* 一个导致this引用在构造期间逸出的错误   是在构造的函数过程中启动了一个线程* 无论是隐式的启动还是显示地启动都会造成this引用的逸出,新线程总是会在对象构造完毕* 之前就已经看到this引用    所以要再构造函数中使用线程,就不要启动它而应该专有的start或初始化的方法来统一启动线程,* 可以采用工厂方法和私有构造函数来完成对象创建和监听器的注册等*** 在对象未完成构造之前   不可以将其发布*/private class InnerClass{public InnerClass(){System.out.println(Escape.this.thisCanBeEscape);}}public static void main(String[] args) {new Escape();}
}

2. 问题(引用+状态,构造函数+正确发布)

不正确的发布可变对象导致的两种错误:

1、发布线程之外的所有线程都可以看到被发布对象的过期的值【引用过期】
2、线程看到的被发布对象的引用是最新的,然而被发布对象的状态却是过期的【状态过期】

正确发布一个对象遇到的两个问题:

  (1)引用本身要被其他线程看到;

  (2)对象的状态要被其他线程看到。

  ps: 在多线程编程中,首要的原则,就是要避免对象的共享,因为如果没有对象的共享,那么多线程编写要轻松得多,但是,如果要共享对象,那么除了能够正确的将构造函数书写正确外,如何正确的发布也是一个很重要的问题。

  

public class Client {public Holder holder;public void initialize(){holder = new Holder(42);//这个代码不是原子的}
}public class Holder {int n;public Holder(int n) {this.n = n;}public void assertSanity() {if(n != n)throw new AssertionError("This statement is false.");}
}/**在Client类中,Holder对象被发布了,但是这是一个不正确的发布。由于可见性问题,其他线程看到的Holder对象将处于不一致的状态,即使在该对象的构成构函数中已经正确的该构建了不变性条件,这种不正确的发布导致其他线程看到尚未创建完成的对象。主要是Holder对象的创建不是原子性的,可能还未构造完成,其他线程就开始调用Holder对象。由于没有使用同步的方法来却确保Holder对象(包含引用和对象状态都没有)对其他线程可见,因此将Holder成为未正确发布。问题不在于Holder本身,而是其没有正确的发布。上面没有正确发布的可能导致的问题:别的线程对于holder字段,可能会看到过时的值,这样就会    导致空引用,或者是过时的值(即使holder已经被设置了)(引用本身没有被别的线程看到)更可怕的是,对于已经更新holder,及时能够看到引用的更新,但是对于对象的状态,看到的却可能是旧值,对于上面的代码,可能会抛出AssertionError异常主要是holder = new Holder(42);这个代码不是原子性的,可能在构造未完成时,其他线程就会调用holder对象引用,从而导致不可预测的结果。
*/

3. 安全对象的构造过程

不要在构造函数内显式或者隐式的的公布this引用。

(1)在对象构造期间,不要公布this引用

  如果要在构造函数中创建内部类,那么就不能在构造函数中把他发布了,应该在构造函数外发布,即等构造函数执行完毕,初始化工作已全部完成,再发布内部类。

public class EventListener { public EventListener(EventSource eventSource) { // do our initialization ... // register ourselves with the event source eventSource.registerListener(this); } public onEvent(Event e) { // handle the event }
}
public class RecordingEventListener extends EventListener { private final ArrayList list; public RecordingEventListener(EventSource eventSource) { super(eventSource); list = Collections.synchronizedList(new ArrayList()); } public onEvent(Event e) { list.add(e); super.onEvent(e); } public Event[] getEvents() { return (Event[]) list.toArray(new Event[0]); }
} 

  

(2)不要隐式地暴露“this”引用

public class EventListener2 { public EventListener2(EventSource eventSource) { eventSource.registerListener( new EventListener() { public onEvent(Event e) { eventReceived(e); } }); } public eventReceived(Event e) { }
}
同样也是子类化问题

(3)不要从构造函数内启动线程
     a)在构造函数中启动线程时,构造函数还未执行完毕,不能保证此对象已经完全构造
     b)如果在启动的线程中访问此对象,不能保证访问到的是完全构造好的对象

3. 安全发布常用模式

要安全的发布一个对象,对象的引用和对象的状态必须同时对其他线程可见。一般一个正确构造的对象(构造函数不发生this逃逸),可以通过如下方式来正确发布:

  (1)在静态初始化函数中初始化一个对象引用

  (2)将一个对象引用保存在volatile类型的域或者是AtomicReference对象中

  (3)将对象的引用保存到某个正确构造对象的final类型的域中。

  (4)将对象的引用保存到一个由锁保护的域

/**

   不变性: 某个对象在被创建后其状态就不能被修改,那么这个对象就称为不可变对象,不可变对象一定是线程安全的。不可变对象很简单。他们只有一种状态,并且该状态由构造函数来控制。

  当满足以下条件时,对象才是不可变的:

    1)对象创建以后其状态就不能改变;

    2)对象的所有域都是final类型;

    3)对象是正确创造的(在对象创建期间,this引用没有溢出)。

(1)Java中存在三种对象a)不变对象:对象状态创建后不能再修改,对象的所有域为final,对象是正确构造的b)基本不变对象:不满足不变对象的约束,但是初始化后不再变化c)可变对象:不满足上述不变对象和基本不变对象的约束(2)安全发布技术a)即确保对象引用和状态对其他线程正确可见b)方式静态初始化器初始化对象引用将引用存储到volatile域将引用存储到正确创建对象的final域将引用存储到由锁正确保护的域(3)三种对象安全发布方式a)不变对象:任何形式机制发布b)基本不变对象:保证安全发布即可c)可变对象:不仅要保证安全发布,而且要确保对象状态的正确改变(即用锁或其他方式,保证对象状态的正确改变)

  通常,要发布一个静态构造的对象,最简单和最安全的方式是使用静态初始化器: public static Holder = new Holder(42);

  静态初始化器由JVM在类的初始化阶段执行,由于JVM内部存在同步机制,所以这种方式初始化对象都可以被安全的发布。

  对于可变对象,安全的发布之时确保在发布当时状态的可见性,而在随后的每次对象的访问时,同样需要使用同步来确保修改操作的可见性。(状态可见性+同步)

**/

4. 容器安全发布保证

  在线程安全容器内部同步意味着,在将对象放到某个容器中,比如Vector中,将满足上面的最后一条需求。如果线程A将对象X放到一个线程安全的容器中,随后线程B读取这个对象,那么可以确保可以确保B看到A设置的X状态,即便是这段读/写X的应用程序代码没有包含显示的同步。下面容器内提供了安全发布的保证:

  (1)通过将一个键或者值放入Hashtable、synchronizedMap或者ConcurrentMap中,可以安全将它发布给任何从这些容器中访问它的线程。

  (2)通过将某个元素放到Vector、CopyOnWriteArrayList、CopyOnWriteArraySet、synchroizedList,可以将该元素安全的发布到任何从这些容器中访问该元素的线程。

  (3)通过将元素放到BlockingQueue或者是ConcrrentLinkedQueue中,可以将该元素安全的发布到任何从这些访问队列中访问该元素的线程。

  

5. 网址

  1. 安全发布对象(一)

  2. Java多线程——volatile关键字、发布和逸出

  3. Java多线程——不变性与安全发布

  4. 第三章 对象的共享(三)

转载于:https://www.cnblogs.com/haimishasha/p/11594585.html

Java 并发系列之十三:安全发布相关推荐

  1. 5W字高质量java并发系列详解教程(上)-附PDF下载

    文章目录 第一章 java.util.concurrent简介 主要的组件 Executor ExecutorService ScheduledExecutorService Future Count ...

  2. Java并发系列(10)——FutureTask 和 CompletionService

    接上一篇<Java并发系列(9)--并发工具类> 文章目录 8 FutureTask 与 CompletionService 8.1 FutureTask 8.1.1 类图 8.1.2 几 ...

  3. Java并发系列(11)——ThreadPoolExecutor实现原理与手写

    接上一篇<Java并发系列(10)--FutureTask 和 CompletionService> 文章目录 9 线程池 9.1 JDK 线程池 9.2 ThreadPoolExecut ...

  4. 【Java并发系列04】线程锁synchronized和Lock和volatile和Condition

    一.前言 多线程怎么防止竞争资源,即防止对同一资源进行并发操作,那就是使用加锁机制.这是Java并发编程中必须要理解的一个知识点.其实使用起来还是比较简单,但是一定要理解. 有几个概念一定要牢记: 加 ...

  5. Java并发系列—并发编程挑战

    并发编程的目的是为了让程序运行得更快,但并不是启动更多的线程就能让程序最大限度地并发执行.在进行并发编程时,如果希望通过过线程执行任务让程序运行得更快,会面临非常多的挑战,比如上下文切换的问题.死锁的 ...

  6. java并发编程第十三课 死锁

    第67讲:如何写一个必然死锁的例子? 本课时我们会首先介绍什么是死锁,死锁有什么危害和特点,然后通过代码分析一个"必然死锁的例子". 死锁是什么?有什么危害? 什么是死锁 发生在并 ...

  7. java 实现队列读写锁_史上最全的Java并发系列之Java中的锁的使用和实现介绍(二)...

    前言 文本已收录至我的GitHub仓库,欢迎Star:https://github.com/bin392328206/six-finger 种一棵树最好的时间是十年前,其次是现在 絮叨 上节是锁的第一 ...

  8. 【java并发系列】Fork/Join任务(转)

    原文链接 当我们需要执行大量的小任务时,有经验的Java开发人员都会采用线程池来高效执行这些小任务.然而,有一种任务,例如,对超过1000万个元素的数组进行排序,这种任务本身可以并发执行,但如何拆解成 ...

  9. Java并发编程(二十三)------并发设计模式之生产者消费者模式

    参考文章:Java实现生产者消费者问题与读者写者问题 目录 1. 生产者消费者问题 1.1 wait() / notify()方法 1.2 await() / signal()方法 1.2.1 对sy ...

最新文章

  1. 将动画装入MicroPython I2C OLED
  2. IDEA的UML图介绍(一)
  3. mac版lightroom cc_Photoshop问世30周年 Mac和iPad版获重要更新
  4. java 点餐界面_Java小项目点餐系统(二)之服务端 | 学步园
  5. QT中事件发送函数sendEvent()、postEvent()详解
  6. 得到鹅厂最新前端开发手册一份
  7. 剑指offer之反转链表
  8. 【jeecg-mybatis版本】 mybatis+spring mvc 完美整合方案 查询,保存,更新,删除自动生成
  9. 苹果新闻订阅服务Apple News +推出48小时获得逾20万用户
  10. 案例29-购物车提交订单
  11. cocos2d-x调度器原理,mainloop的Update
  12. php json数组大小,php json转换成数组形式
  13. mgr.dll病毒手工清除方法!
  14. 推箱子视频教学Java,推箱子游戏教学视频
  15. 常见驱动程序相关知识
  16. C语言表上作业法运输问题,表上作业法解运输问题
  17. ESB(企业服务总线)知识
  18. BZOJ4200: [Noi2015]小园丁与老司机
  19. matlab三重积分计算方法,一般区域二重、三重积分MATLAB计算方法
  20. Pr:导出设置之多路复用器与常规

热门文章

  1. (三)标记图像以进行AI模型训练
  2. 在ASP.NET Identity 2.0中使用声明(Claims)实现用户组
  3. 计算机网络管理2018版,2018~2019学年度第二学期“计算机网络管理”专业技能竞赛火热进行中...
  4. 易语言编写的档案管理系统源码_校园固定资产管理系统方案
  5. python技巧 pdf-求教使用python库提取pdf的方法?
  6. hdfs中与file数组类似的数组_EXCEL中数组的应用专题之十二:行列数相同数组的运算...
  7. php str_replace替换特殊字符
  8. python的socket编程接收浏览器上传的文件_使用python套接字编程将文件发送到浏览器...
  9. 计算机一级学科评选,我系力学被评选为一级学科国家重点学科
  10. xml getelementsbytagname php,用PHP编写和读取XML的几种方式