1. Volatile
1)Volatile 不具有原子性,不适合计数这种场景
使用Volatile必须要具备两个条件:
A。对变量的写操作不依赖当前值
B。该变量没有包含在具有其他变量不变的式子中
适合的使用场景:
A。特别适合状态标记量
B。Double Check
2. 有序性
关键字与有序性:
Volatile 可以保证一定的有序性
Synchronize Lock 可以保证有序性
happens-before 原则
1)程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作
2)锁定规则:一个unlock操作先行发生于后面对同一个锁的lock操作
3)Volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
4)传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
5)线程启动规则:Thread对象的start()方法先行发生于此线程的每一个动作
6)线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
7)线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
8)对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始
3. 安全发布对象
参考单例模式
1)在静态初始化函数中初始化一个对象引用
2)将对象的引用保存到volatile类型域或者AtomicReference对象
3)将对象的引用保存到某个正确构造对象的final类型中
4)将对象的引用保存到一个由锁保护的域中
单例对象 volatile + 双重检测机制 ->禁止指令重排
单例模式-枚举模式 使用枚举来实例化最安全,JVM保证枚举初始化只调用一次。相比于懒汉模式,安全性更容易保证,相比于饿汉模式是在实际调用的时候才初始化实例。推荐使用。
4 避免并发的方法
1)使用不可变对象
2)线程封闭
A。Ad-hoc 线程封闭:程序控制实现,最糟糕,忽略
B。堆栈封闭:局部变量,无并发问题。能用局部变量的就不用全局变量,全局变量容易引起并发问题。
C。ThreadLocal 线程封闭:利用 map 实现了线程封闭
5. 线程安全与不安全的类
线程不安全的类 线程安全的类
StringBuilder StringBuffer
SimpleDateFormat Datetime(Joda-Time包)
ArrayList Vector Stack
HashSet
HashMap HashTable
Collections.synchronizedXXX (List Set Map)
使用同步容器时,不一定能做到所有场合都是线程安全的。Vector 也可能出现线程不安全的情况
线程不安全写法:先检查再执行 if(condition(a)) { handle(a);}
注意:如果对象是多线程共享的,解决方法是加锁,或者保证上述两个操作是原子性的。
注意事项
1) 在使用 Foreach 或 iterator 时操作集合时,尽量不要在操作的过程中进行 Remove 的操作。如果一定要进行 Remove 操作,建议在遍历的过程中先进行标记,在遍历完成后再进行 Remove 的操作。或者使用 for 循环进行增删操作的遍历。