1 线程安全的定义
当多个线程访问某个类时,这个类始终都能表现出正确的行为,那么就称这个类是线程安全的。
2 使用和共享对象时保证线程安全的方法
2.1 同步锁
2.1.1 synchronized
2.1.2 ReentrantLock
增加了一些高级功能,如:等待可中断、可实现公平锁,锁可以绑定多个条件。
2.2 乐观锁
CAS
2.3 线程封闭
线程封闭的对象只能由一个线程拥有,对象被封闭在该线程中,并且只能由这个线程修改。
2.3.1 栈封闭
只通过局部变量访问对象。
2.3.2 ThreadLocal类
推荐方法。其核心类是ThreadLocalMap(一个自定义hash map,但并非继承HashMap),Thread的成员变量,key为ThreadLocal。
2.4 只读共享
可以由多个线程并发访问,但任何线程都不能修改它。
2.4.1 不可变对象
- 将类和所有状态变量设为final。
- 对象是正确创建的(创建期间this引用没有逸出)。
- 不提供修改状态变量的方法,且暴露状态变量时只暴露状态变量的拷贝。
2.4.2 事实不可变对象
技术上可变,业务上不变。必须通过安全方式发布。
2.5 线程安全共享
在对象内部实现同步。
3 安全发布对象的方法
注:不可变对象可通过任何机制发布
3.1 在静态初始化函数中初始化一个对象引用
静态初始化器由JVM在类的初始化阶段执行,内部存在着同步机制。
3.2 将对象的引用保存到volatile类型的域或者AtomicReferance对象中
3.2.1 volatile特性
- 可见性:对一个 volatile 变量的读,总是能看到(任意线程)对这个 volatile变量最后的写入。
- 原子性:对任意单个 volatile 变量的读/写具有原子性,但类似于 volatile++这种复合操作不具有原子性。
3.2.2 AtomicReferance
3.3 将对象的引用保存到某个正确构造对象的final类型域中
对于final域,编译器和处理器要遵守两个重排序规则:
1. 在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。
2. 初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序。
3.4 将对象的引用保存到一个由锁保护的域中
如:Hashtable、ConcurrentMap、BlockingQueue等容器中。
4 什么是JMM
Java内存模型(JMM)规定了JVM必须遵循一组最小保证,这组保证规定了对变量的写入操作在何时将对其他线程可见。
4.1 Happens-Before规则
4.1.1 程序顺序规则
若程序中操作A在操作B之前,则线程中操作A在操作B之前执行。
4.1.2 监视器锁规则
在同一监视器锁上的解锁操作必须在加锁操作之前执行。
4.1.3 volatile变量规则
对volatile变量的写操作必须在读操作之前执行。
StoreStore volatile写 StoreLoad
volatile读 LoadLoad LoadStore
4.1.4 线程启动规则
Thread.start必须在线程中执行的操作之前被调用。
4.1.5 线程关闭规则
线程中的任何操作必须在其他线程检测到该线程结束之前执行,或者从Thread.join中返回,或调用Threas.isAlive返回false。
4.1.6 中断规则
当一个线程在另一个线程上调用interrupt时,必须在被中断线程检测到interrupt调用之前执行。
4.1.7 终结器规则
对象的构造函数必须在启动该对象的终结器之前执行完成。
4.1.8 传递性
如果操作A在操作B之前执行,操作B在操作C之前执行,则操作A必须在操作C之前执行。
5 防止死锁
5.1 锁顺序
5.2 开放调用
在不持有锁的情况下调用外部方法。
5.3 tryLock
支持定时的锁