并发(四):同步一定要在同一把锁上

在并发编程实践中,监视器(锁)的选择大有技巧:
如果要开发扩展(继承),监视器尽可能选择自身,或者子类能获取到的对象,否则即使加了锁,依旧会出现状态不同步的情形,看下面的例子:

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,从而证明,不在同一个锁上的同步代码依旧是错误的。

总结

总结起来一句话,需要同步的变量、代码一定采用同一个监视器。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值