java并发编程实践 2.4 用锁来保护状态

缘起

《java并发编程实践》 2.4节

分析

什么叫做一个状态是被锁保护的?

对于可以被多根线程访问的可变状态,如果所有这些线程要访问(并不仅仅只有对状态的写操作的时候才需要同步~)它都必须要首先获取同一把锁的话,我们就称这个状态是由这把锁保护的. 正因为如此,程序员必须要清楚这把锁到底是什么?

一般情况下,我们在对象内部封装所有的可变状态,通过对象的内部锁来作为访问这些可变状态的锁. 即类似这种代码

1
2
3
4
5
6
7
8
9
10
11
public class XXX {
private Type1 mutable1;
private Type2 mutable2;
private Type3 mutable3;

// synchronized使用当前XXX的对象实例作为锁
public synchronized void write(Type1 pp) {
this.mutable11 = pp;
}

}

但是请注意,即使一根线程获取了当前XXX的对象的实例作为锁,也不能阻止其他线程访问当前XXX对象的实例,只能阻止其他线程再次获取这把锁而已.

正因为synchronized锁用的太频繁,所以每个java对象都有一个内部锁与之关联——而不需要程序员显式的创建锁对象.

是不是每个方法都是同步的(并且由同一把锁同步),那么整个方法调用就是同步的呢? 不是,例如下面的使用Vector的代码

1
2
3
if(!vector.contains(e)) {
vector.add(e);
}

java.util.Vector的contains方法和add方法都是同步方法,但是上述代码并不是线程安全的. 因为两个原子操作合并成为复合操作之后如果没有锁保护的话,依旧不是原子的.

而且锁的引入也会带来性能问题(因为锁的本质是顺序化(serialization), 与并行带来的高效率是矛盾的双方).

这个在下一小节会讲到.