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

一、不变性编程

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

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

1.1 final域ide

  关键字final用于构造不可变对象,final类型的域是不能修改的(可是final域所引用的对象是可变的,那么这些引用的对象是能够修改的),即便对象是可变的,经过将可变对象的某些域声明为final类型,至关于告诉维护人员这些域是不可变化的。函数

 

二、正确发布一个对象this

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

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

  咱们看下面的代码:code

 1 public class Client {
 2     public Holder holder;
 3     
 4     public void initialize(){
 5         holder = new Holder(42);
 6     }
 7 }
 8 
 9 
10 public class Holder {
11     int n;
12     public Holder(int n) {
13         this.n = n;
14     }
15     public void assertSanity() {
16         if(n != n)
17              throw new AssertionError("This statement is false.");
18     }
19 }
View Code

  在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内部存在同步机制,因此这种方式初始化对象均可以被安全的发布。对于可变对象,安全的发布之时确保在发布当时状态的可见性,而在随后的每次对象的访问时,一样须要使用同步来确保修改操做的可见性。

相关文章
相关标签/搜索