在并发编程实践中,监视器(锁)的选择大有技巧:
如果要开发扩展(继承),监视器尽可能选择自身,或者子类能获取到的对象,否则即使加了锁,依旧会出现状态不同步的情形,看下面的例子:
public class DoubleLock {
private int counter = 0;
/*
* 每次加1
*/
public void addOne() {
// 对this引用加锁
synchronized (this) {
counter = counter + 1;
}
}
/*
* 每次加2
*/
public void addTwo() {
// 对Class加锁
synchronized (DoubleLock.class) {
counter = counter + 2;
}
}
}
在上面的代码中,addOne与addTwo都是对counter进行操作,并且都加了锁,但是锁的监视器不是同一个对象,所以对addTwo方法而言,addOne操作的对象counter依旧不可见,于是依旧会出现错误的结果。
下面是测试代码:
final DoubleLock lock = new DoubleLock();
// 四个线程,每个递增500,共递增2000
for(int n = 0 ; n < 4; n ++) {
new Thread() {
public void run() {
int i = 0;
while(i < 500) {
lock.addOne();
i ++;
}
}
}.start();
}
// 四个线程,每个递增250 * 2 = 500,共递增2000
for(int n = 0 ; n < 4; n ++) {
new Thread() {
public void run() {
int i = 0;
while(i < 250) {
lock.addTwo();
i ++;
}
}
}.start();
}
// 等递增操作完成
Thread.sleep(3000);
System.out.println(lock.counter);
这里插一句,在并发测试的代码正确性的校验重,经常会发现结果是正确的(没有出现异常),并且多次出现,但请注意,这依旧不能证明代码实现是正确的,这时请增加测试次数与线程执行时间,或调整(增大或减小)代码顺序执行之间的间隔(如添加Thread.sleep()代码)。
在上面的测试中,执行了多次,其中有一次的结果为3989,从而证明,不在同一个锁上的同步代码依旧是错误的。
总结
总结起来一句话,需要同步的变量、代码一定采用同一个监视器。