要编写线程安全的代码,其核心在于要对状态访问操作进行管理,特别是对共享和可变状态的访问。
从非正式的意义来讲,对象的状态是指存储在状态变量(例如实例或静态域)中的数据,对象的状态可能包含其它依赖对象的域。
一个对象是否需要实现线程安全,取决于它是否会被多个线程访问。要使得对象是线程安全的,需要采取同步机制来协同对对象可变状态的访问。
Java同步机制:关键字synchronized、volatile类型的变量、显式锁(Lock)、原子变量。
无状态的对象一定是线程安全的。
竞态条件(Race Condition):计算的正确性取决于多个线程的交替执行时序时,就会发生竞态条件。例如“读取-修改-写入”操作和“先检查后执行”操作。
复合操作:要避免竞态条件问题就要保证在某个线程修改变量时,通过某种方式阻止其他线程使用该变量。“读取-修改-写入”操作和“先检查后执行”操作统称为复合操作:包含了一组必须以原子方式执行的操作以确保线程安全性。
加锁机制是Java中用于确保原子性的内置机制。
同步代码块(Synchronized Block)。内置锁可以支持原子性和可见性。同步代码块包含两部分:
其中该同步代码块的锁就是方法调用所在的对象。静态的synchronized方法以class对象作为锁。
Java的内置锁相当于一种互斥体(或互斥锁),这意味着最多只有一个线程能够持有这种锁。
当某个线程请求一个其他线程持有的锁时,就会阻塞。因为内置锁是可重入的,如果某个线程试图获得一个已经被自己占有的锁,就会成功。“重入”意味着获取锁的操作的粒度是“线程”,而不是“调用“。
重入的一种实现方法是为每一个锁设置一个计数器,同一个线程再次获取这个锁,计数值加一,而当线程退出同步代码块时,计数值减一。
如果内置锁是不可重入的,下面的代码将会死锁:
public class Widget{
public synchronized void doSomeThing(){ ... }
}
public class LoggingWidget extends Widget{
public synchronized void doSomeThing(){
//如果是非重入的锁,获取Widget上的锁时就会发生死锁
super.doSomeThing();
...
}
}
注意:
对象的内置锁和对象的状态之间没有内在的关联。当获取对象关联的锁时,并不能阻止其他线程访问该对象,只能阻止其他线程获取同一个锁。
可以使用@GuardBy标签标注使用的是哪一个锁。