编写正确的并发程序的关键在于对共享的、可变的状态进行访问管理。
内存可见性,你可以通过显示的同步,或者利用内置于类库中的同步机制,来保证对象的安全发布。
1、可见性
为了确保跨线程写入的内存可见性,你必须使用同步机制。
指令重排会对多线程共享变量产生影响。
只要数据需要被跨线程共享,就进行恰当的同步。
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域中;
- 或者将它的引用存储到由锁正确保护的域中。