之前讨论是如何将对象封闭在线程之中,这样可以减少一些并发带来的同步和可见性问题。但是在有些时候,我们希望在多个线程间共享对象,此时必须确保安全地进行共享。

【不安全发布的示例】

可见性问题;其他线程看到的是不一致的状态;对象尚未创建完成就发布出去了。

不正确的发布:正确的对象被破坏

上被完全创建的对象还没有完整性,此时发布出去将使其他线程看到的是不一致的状态,然后看到对象的状态突然发生变化,即使线程在对象发布后还没有修改过它。

【示例代码】

未正确发布对象可能有两种情况:

1. 其他线程看到的对象的域可能是一个空引用,或者之前的旧值;

2. 更糟糕的情况是,线程看到的对象的引用是最新的,但是对象状态的值却是失效;

3. 线程在第一次读取域时得到的失效值,再次读取的时候会得到一个更新值。

不可变对象与初始化安全性

由于不可变对象是一种非常重要的对象,因此Java内存模型为不可变对象的共享提供了一种特殊的初始化安全性保证。(啥保证)

为了确保对象状态能呈现出一致性的视图,就必须使用同步。

另一方面,即使在发布不可变对象的引用时没有使用同步,也仍然可以安全地访问该对象。

为了维持这种初始化安全性的保证,必须满足不可变性的所有需求:状态不可修改,所有域有事final类型,以及正确的构造过程。

Object的构造函数会在子类构造函数运行之前先将默认值写入所有的域,因此某个域的默认值可能被视为失效值。

任何线程都可以在不需要额外同步的情况下安全地访问不可变对象,即使在发布这些对象时没有使用同步。

这种保证还将延伸到被正确创建对象中所有fianl类型的域。在没有额外同步的情况下,也可以安全地访问fianl类型的域。然而,如果fianl类型的域所指向的是可变对象,那么在访问这些域所指向的对象的状态仍然需要同步。

安全发布的常用模式

可变对象必须通过安全的方式来发布,这通常意味着在发布和使用该对象的线程时都必须使用同步。

使用对象的线程能够看到该对象处于已发布的状态,并稍后介绍如何在对象发布后对其可见性进行修改。

要安全地发布一个对象,对象的引用以及对象的状态必须同时对其他线程可见。一个正确构造的对象可以通过以下方式来安全地发布。

(以前从未考虑过对象的引用和对象的状态的关系,引用不是一定可以拿到对象的状态么?原来不是这样的)

  • 在静态初始化函数中初始化一个对象的引用。
  • 将对象的引用保持到volatile类型的域或者AtomicReference对象中。
  • 将对象的引用保存到某个正确构造对象的final类型域中。
  • 将对象的引用保存到一个由锁保护的域中。

在线程安全容器内部的同步意味着,将对象放入到某个容器,例如Vector或synchronizedList时,将满足最后一条需求。如果线程A将对象X放入到一个线程安全的容器,随后线程B读取这个对象,那么可以确保B看到A设置的X状态,即便在这段读/写X的应用程序代码中没有包含显式的同步。

通过讲一个键或者值放入Hashtable,synchronizedMap或者ConcurrentMap中,可以安全地将它发布给任何从这些容器中访问它的线程(无论是直接访问,还是通过迭代器访问)。通过将某个元素放入Vector、CopyOnWriteArrayList、CopyOnWriteArraySet、synchronizedList或synchronizedSet中,可以将该元素安全地发布到任何从这些容器中访问该元素的线程。通过将某个元素放入BlockingQueue或者ConcurrentLinkedQueue中,可以将该元素安全地发布到任何从这些队列中访问该元素的线程。

类库中的其他数据传递机制(例如Future和Exchanger)同样能实现安全发布。

通常要发布一个静态构造的对象,最简单和最安全的方式是使用静态的初始化:

【代码演示】

静态初始化由JVM在类的初始化阶段执行。由于在JVM内部存在着同步机制,因此通过这种方式初始化的任何对象都可以被安全地发布。

事实不可变对象

如果对象在发布之后不会被修改,那么对于其他在没有额外同步的情况下安全地访问这些对象的线程来说,安全发布时足够的。如果一个对象从技术上来说的可变的,但是其状态在发布后不会再改变,那么把这种对象称为“事实不可变对象(Effectively Immutable Object)“。

在没有额外的同步的情况下,任何线程都可以安全地使用被安全发布的事实不可变对象。

比如Date本身是可变的,但如果将它作为不可变对象来使用,那么在多个线程之间共享Date对象时,就可以省去对锁的使用。

可变对象

对于可变对象,不仅在发布对象时需要使用同步,而且在每次对象访问时同样需要使用同步来确保后续修改操作的可见性。要安全地共享可变对象,这些对象就必须被安全地发布,而且必须是线程安全地或者由某个锁保护起来。

对象的发布需求取决于它的可变性:

不可变对象可以通过任意机制来发布。

事实不可变对象必须通过安全方式来发布。

可变对象必须通过安全方式发布,并且必须是线程安全的或者由某个锁保护起来。

安全地共享对象

当获得一个对象的引用时,你需要知道这个引用上可以执行哪些操作。在使用它之前是否需要获得一个锁?是否可以修改它的状态,或者只能读取它?许多并发错误都是由于没有理解共享对象的这些“既定规则”而导致的。当发布一个对象时,必须明确说明对象的访问方式。

在并发程序使用和共享对象时,可以使用一些策略:

线程封闭:线程封闭的对象只能有一个线程拥有,对象被封闭在该线程中,并且只能由这个线程修改。

只读共享:在没有额外同步的情况下,共享的只读对象可以由多个线程并发访问,但任何线程都不能修改它。共享的只读对象包括不可变对象和事实不可变对象。

线程安全共享:线程安全的对象在其内部实现同步,因此多个线程可以通过对象的公有接口来进行访问而不需要进一步的同步。

保护对象:被保护的对象只能通过持有特定的锁来访问,保护对象包括封装在其他线程安全对象中的对象,以及已发布的并且由某个特定锁保护的对象。

Java并发编程(九)安全发布的更多相关文章

  1. Java并发编程 (九) 线程调度-线程池

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) 声明:实际上,在开发中并不会普遍的使用Thread,因为它具有一些弊端,对并发性能的影响比较大,如下: ...

  2. 《java并发编程实战》读书笔记2--对象的共享,可见性,安全发布,线程封闭,不变性

    这章的主要内容是:如何共享和发布对象,从而使它们能够安全地由多个线程同时访问. 内存的可见性 确保当一个线程修改了对象状态后,其他线程能够看到发生的状态变化. 上面的程序中NoVisibility可能 ...

  3. Java并发编程(五):Java线程安全性中的对象发布和逸出

    发布(Publish)和逸出(Escape)这两个概念倒是第一次听说,不过它在实际当中却十分常见,这和Java并发编程的线程安全性就很大的关系. 什么是发布?简单来说就是提供一个对象的引用给作用域之外 ...

  4. java并发编程笔记(九)——多线程并发最佳实践

    java并发编程笔记(九)--多线程并发最佳实践 使用本地变量 使用不可变类 最小化锁的作用域范围 使用线程池Executor,而不是直接new Thread执行 宁可使用同步也不要使用线程的wait ...

  5. java并发编程笔记(四)——安全发布对象

    java并发编程笔记(四)--安全发布对象 发布对象 使一个对象能够被当前范围之外的代码所使用 对象逸出 一种错误的发布.当一个对象还没构造完成时,就使它被其他线程所见 不安全的发布对象 某一个类的构 ...

  6. Java并发编程原理与实战九:synchronized的原理与使用

    一.理论层面 内置锁与互斥锁 修饰普通方法.修饰静态方法.修饰代码块 package com.roocon.thread.t3; public class Sequence { private sta ...

  7. 《Java并发编程实战》学习笔记 线程安全、共享对象和组合对象

    Java Concurrency in Practice,一本完美的Java并发参考手册. 查看豆瓣读书 推荐:InfoQ迷你书<Java并发编程的艺术> 第一章 介绍 线程的优势:充分利 ...

  8. java 并发编程

    闭锁 一种可以延迟线程的进度直到其到达终止状态.可以用来确保某些活动直到其他活动都完成后才继续执行 例如: 确保某个计算在其需要的所有资源都被初始化了之后才继续执行. 确保某个服务在其他依赖的服务都启 ...

  9. Java并发编程二三事

    Java并发编程二三事 转自我的Github 近日重新翻了一下<Java Concurrency in Practice>故以此文记之. 我觉得Java的并发可以从下面三个点去理解: * ...

  10. Java并发编程 Volatile关键字解析

    volatile关键字的两层语义 一旦一个共享变量(类的成员变量.类的静态成员变量)被volatile修饰之后,那么就具备了两层语义: 1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了 ...

随机推荐

  1. 动态规划之DP中判断是否到达某一状态(最短时间是什么)?

    codevs1684 垃圾陷阱  时间限制: 1 s  空间限制: 128000 KB  题目等级 : 黄金 Gold   题目描述 Description 卡门——农夫约翰极其珍视的一条Holste ...

  2. 网络采集软件核心技术剖析系列(6)---将任意博主的全部博文下载到SQLite数据库中并通过Webbrower显示(将之前的内容综合到一起)

    一 本系列随笔目录及本节代码下载 自己开发的豆约翰博客备份专家软件工具问世3年多以来,深受广大博客写作和阅读爱好者的喜爱.同时也不乏一些技术爱好者咨询我,这个软件里面各种实用的功能是如何实现的. 该软 ...

  3. Android ProgressBar手动控制开始和停止

    这两天有个需求,点击按钮从SD卡解压压缩包,并读取压缩包内txt文档内容,然后在街面上显示出来.毕竟IO操作很耗时,如果文件较大会花费不少时间.所以,在处理数据的时候能给个进度就好了.我们通常的做法就 ...

  4. mysql 中查询一个字段是否为null的sql

    查询mysql数据库表中字段为null的记录: select * 表名 where 字段名 is null 查询mysql数据库表中字段不为null的记录: select * 表名 where 字段名 ...

  5. javascript快速入门6--Script标签与访问HTML页面

    Script标签 script标签用于在HTML页面中嵌入一些可执的脚本 <script> //some script goes here </script> script标签 ...

  6. Laravel 5系列教程二:路由,视图,控制器工作流程

    免费视频教程地址https://laravist.com/series/laravel-5-basic 上一篇教程我们走了那么长的路,终于把Laravel安装好了,这一篇教程我们就要进入Laravel ...

  7. Servlet执行时一般实现哪几个方法?

    public void init(ServletConfig config) public ServletConfig getServletConfig() public String getServ ...

  8. perl学习笔记——文件测试

    文件测试主要用于查看如文件是否存在.文件大小.文件更新时间等信息. 文件测试操作符 -e  测试文件是否存在: die "Oops!A file called '$filename' alr ...

  9. 倍福TwinCAT(贝福Beckhoff)应用教程13.3 TwinCAT控制松下伺服 NC配合完整上位

    这是TwinCAT教程的最后一节,简单讲述了以C#为上位,通过ADS控制TwinCAT下位,实现完整控制两轴模组的功能.可以发现,在上位层已经没有了运动控制的代码,不管是要执行哪种运动,无非是把目标参 ...

  10. ACE消息队列(转)

    1    消息队列 ACE消息队列由三个部分组成:消息队列(ACE_Message_Queue).消息块(ACE_Message_Block).数据块(ACE_Data_Block) 1.1    A ...