1、不变性

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

  当满足以下条件时,对象才是不可变的:(1)、对象创建以后其状态就不能改变;(2)、对象的所有域都是final类型;(3)、对象是正确创造的(在对象创建期间,this引用没有溢出)。

1.1 final域

  关键字final用于构造不可变对象,final类型的域是不能修改的(但是final域所引用的对象是可变的,那么这些引用的对象是可以修改的),即使对象是可变的,通过将可变对象的某些域声明为final类型,相当于告诉维护人员这些域是不可变化的。

2、正确发布一个对象

  正确发布一个对象遇到的两个问题:(1)引用本身要被其他线程看到;(2)对象的状态要被其他线程看到。

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

  我们看下面的代码:

 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对象引用,从而导致不可预测的结果。

2.1安全发布常用模式

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

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

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

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

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

  

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

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

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

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

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

  静态初始化器由JVM在类的初始化阶段执行,由于JVM内部存在同步机制,所以这种方式初始化对象都可以被安全的发布。对于可变对象,安全的发布之时确保在发布当时状态的可见性,而在随后的每次对象的访问时,同样需要使用同步来确保修改操作的可见性。

  

Java多线程——不变性与安全发布的更多相关文章

  1. Java多线程之构造与发布

    资料来源 http://www.ibm.com/developerworks/library/j-jtp0618/ http://www.javaspecialists.eu/archive/Issu ...

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

    1.volatile关键字 Java语言提供了一种稍弱的同步机制,即volatile变量.被volatile关键字修饰的变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在每次读取volatit ...

  3. Java多线程编程详解

    转自:http://programming.iteye.com/blog/158568 线程的同步 由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题.Ja ...

  4. Java多线程编程(4)--线程同步机制

    一.锁 1.锁的概念   线程安全问题的产生是因为多个线程并发访问共享数据造成的,如果能将多个线程对共享数据的并发访问改为串行访问,即一个共享数据同一时刻只能被一个线程访问,就可以避免线程安全问题.锁 ...

  5. Java多线程系列目录(共43篇)

    最近,在研究Java多线程的内容目录,将其内容逐步整理并发布. (一) 基础篇 01. Java多线程系列--“基础篇”01之 基本概念 02. Java多线程系列--“基础篇”02之 常用的实现多线 ...

  6. 关于JAVA多线程的那些事__初心者

    前言 其实事情的经过也许会复杂了点,这事还得从两个月前开始说.那天,我果断不干IT支援.那天,我立志要做一个真正的程序猿.那天,我26岁11个月.那天,我开始看Android.那天,我一边叨念着有朋自 ...

  7. Java最重要的21个技术点和知识点之JAVA多线程、时间处理、数据格式

    (四)Java最重要的21个技术点和知识点之JAVA多线程.时间处理.数据格式  写这篇文章的目的是想总结一下自己这么多年JAVA培训的一些心得体会,主要是和一些java基础知识点相关的,所以也希望能 ...

  8. 15个顶级Java多线程面试题及答案

    1)现在有T1.T2.T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行? 这个线程问题通常会在第一轮或电话面试阶段被问到,目的是检测你对”join”方法是否熟悉.这个多线程问题比 ...

  9. 50个Java多线程面试题

    不管你是新程序员还是老手,你一定在面试中遇到过有关线程的问题.Java 语言一个重要的特点就是内置了对并发的支持,让 Java 大受企业和程序员的欢迎.大多数待遇丰厚的 Java 开发职位都要求开发者 ...

随机推荐

  1. 宽字符、多字节、unicode、utf-8、gbk编码转化

    今天遇到一个编码的问题,困惑了我很长时间,所以就简要的的了解了一下常用的编码类型. 我们最常见的是assic编码,它是一种单字节编码,对多容纳256个字符. 我们在编程的时候经常遇到unicode,u ...

  2. [Selenium]点击下拉框之后,从下拉列表选择元素进行点击很容易失败

    点击下拉框之后,下拉列表会显示出来,但是有时候下拉列表会很快就消失掉,导致后面选择元素的时候会失败. 像这种情况,需要将鼠标移动到下拉列表上,使下拉列表维持显示,然后才选择元素进行点击. 将鼠标移动到 ...

  3. Mybatis 实用篇(三)参数处理

    Mybatis 实用篇(三)参数处理 sql 语句中的参数 parameterType 可以省略不写. 一.参数封装 1.1 单个参数处理 public interface UserMapper { ...

  4. 在MS Test中如何测试private方法

    前言: 在博客开始之前,我们先讨论一下是否应该对private方法做测试,通常有两种观点: private方法应该被测试: private方法不应该被测试: 我们以下面的代码为例子来进行说明: pub ...

  5. 20155317 2016-2017-2 《Java程序设计》第7周学习总结

    20155317 2016-2017-2 <Java程序设计>第7周学习总结 教材学习内容总结 1.在只有Lambda表达式的情况下,参数的类型必须写出来. 2.Lambda表达式本身是中 ...

  6. win7下面iis错误汇总

    1.分析器错误消息: 无法识别的属性“targetFramework”.请注意属性名称区分大小写 解决方法: 修改.NET Framework 版本为相应版本即可,我以前用的是2.0换成4.0的时候出 ...

  7. 使用 John the Ripper 破解 Windows 密码

    cd /target/windows/system32/config 使用 SamDump2 来提取哈希,并将文件放到你的 root 用户目录中的一个叫做 hashes 的 文件夹中. samdump ...

  8. cxgrid的FINDPANEL编程

    cxgrid的FINDPANEL编程 FindPanel := TcxGridFindPanel.Create(cxGrid1DBTableView1.Controller); self.cxGrid ...

  9. [leetcode] 11. Same Tree

    因为我刷题是难度不是按发布日期,所以就有可能遇到这种情况,比如这个... Given two binary trees, write a function to check if they are e ...

  10. Android-自定义控件之事件分发

    最大范围 外层蓝色区域是继承ViewGroup 中间红色区域,也是继承ViewGroup 最中间黑色区域,是继承View 布局相关的代码: <!-- 事件分发 --> <view.c ...