共享对象

编写正确的并发程序的关键在于对共享的、可变的状态进行访问管理。

内存可见性,你可以通过显示的同步,或者利用内置于类库中的同步机制,来保证对象的安全发布。

1、可见性

为了确保跨线程写入的内存可见性,你必须使用同步机制。

Java Thread类的 yield()、join()

指令重排会对多线程共享变量产生影响。

只要数据需要被跨线程共享,就进行恰当的同步。

1.1、过期的数据

多线程分别执行get set,而没有对变量进行同步,指令重排导致过期的数据,改为同步:

1.2、非原子的64位操作

没有声明为volatile的64位数值变量(double和long),在多线程中共享,也可能不是安全的,(JVM运行将64位的读或写划分为两个32位的操作)。

1.3、锁和可见性

锁不仅仅是关于同步与互斥的,也是关于内存可见的,为了保证所有线程都能够看到共享的、可变变量的最新值,读取和写入线程必须使用公共的锁进行同步。

1.4、Volatile变量

加锁可以保证可见性与原子性;volatile变量只能保证可见性。

使用volatile的条件:

  • 写入变量时并不依赖变量的当前值;或者能够确保只有单一的线程修改变量的值
  • 变量不需要与其他的状态变量共同参与不变约束
  • 访问变量时,没有其他的原因需要加锁

2、发布和逸出

一个对象在尚未准备好时就将它发布,这种情况称作逸出。

一旦一个对象逸出,你就要假设存在其他的类或线程可能误用它,无论是出于恶意还是粗心。这是使用封装的强制原因:封装使得程序的正确性分析变得更可行,而且更不易偶然地破坏设计约束。

2.1、安全构建的实践

不要让this引用在构造期间逸出。

3、线程封闭

3.1、Ad-hoc线程限制

3.2、栈限制

3.3、ThreadLocal

4、不可变性

4.1、Final域

4.2、示例:使用volatile发布不可变对象

5、安全发布

5.1、不正确发布:当好对象变坏时

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

为了保证对象状态有一个一致性视图,我们需要同步。

不可变对象可以在没有额外同步的情况下,安全地用于任意线程;甚至发布它们时亦不需要同步。

5.3、安全发布的模式

安全发布对象的条件:

  • 通过静态初始化器初始化对象的引用;
  • 将它的引用存储到volatile域或AtomicReference;
  • 将它的引用存储到正确创建的对象的final域中;
  • 或者将它的引用存储到由锁正确保护的域中。

5.4、高效不可变对象

5.5、可变对象

5.6、安全地共享对象