Java并发编程进阶——锁(解析)

一、锁是什么


java开发中进行并发编程时针对操作同一块区域时,如果不加锁会出现并发问题,数据不是自己预计得到的值。我觉得有点像mysql事务中脏读、不可重复读、幻读的问题。加锁的目的是为了保证同一时间只有我一个人操作同一个资源。

二、如何在代码里面加锁


jdk提供给了我们很多锁的实现方式,用于各种情况锁的使用:

  1. 使用synchronized修饰方法、修饰代码块等;
  2. 使用ReentrantLock来获取锁;
  3. ReadWriteLock读写分开的读写锁;
  4. ReentrantReadWriteLock;

三、这些锁有什么区别


Ⅰ、实现原理不同

synchronized是锁实现原理是jdk实现的:

public class SynchronizedDemo {
     public static void main(String[] args) {
        Object o = new Object();
        synchronized (o){
            System.out.println("ReentrantLockDemo");
        }
    }
}

使用synchronized修饰的代码会在编译时加上monitorenter、monitorexit进行修饰,那么问题来了,为什么用这个修饰后就能够保证线程执行过程中的安全呢? 因为jdk在执行monitorenter、monitorexit区块的时候是保证原子性的,要么执行完成要么执行不完成。synchronized修饰的代码块有可视性、原子性、顺序性(防止重排序)。

ReentrantLock是怎么实现锁的机制呢

通过继承AbstractQueuedLongSynchronizer(AQS)来进行锁的,实现原理是AQS中有一个变量来控制是否获取到了锁,通过Unsafe的CAS操作来获取锁,从而保证线程安全。

那么问题来了?CAS操作的ABA问题如何解决

concurrent包中有提供AtomicStampedReference来解决ABA问题,也就是在CAS操作的同时需要再增加版本的判断,从而保证不出现ABA的问题。

public class SolveCAS {
    // 主内存共享变量,初始值为1,版本号为1
    private static AtomicStampedReference<Integer> atomicStampedReference = new
            AtomicStampedReference<>(1, 1);


    public static void main(String[] args) {
        // t1,期望将1改为10
        new Thread(() -> {
            // 第一次拿到的时间戳
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()+" 第1次时间戳:"+stamp+" 值为:"+atomicStampedReference.getReference());
            // 休眠5s,确保t2执行完ABA操作
            try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
            // t2将时间戳改为了3,cas失败
            boolean b = atomicStampedReference.compareAndSet(1, 10, stamp, stamp + 1);
            System.out.println(Thread.currentThread().getName()+" CAS是否成功:"+b);
            System.out.println(Thread.currentThread().getName()+" 当前最新时间戳:"+atomicStampedReference.getStamp()+" 最新值为:"+atomicStampedReference.getReference());
        },"t1").start();

        // t2进行ABA操作
        new Thread(() -> {
            // 第一次拿到的时间戳
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()+" 第1次时间戳:"+stamp+" 值为:"+atomicStampedReference.getReference());
            // 休眠,修改前确保t1也拿到同样的副本,初始值为1
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
            // 将副本改为20,再写入,紧接着又改为1,写入,每次提升一个时间戳,中间t1没介入
            atomicStampedReference.compareAndSet(1, 20, stamp, stamp + 1);
            System.out.println(Thread.currentThread().getName()+" 第2次时间戳:"+atomicStampedReference.getStamp()+" 值为:"+atomicStampedReference.getReference());
            atomicStampedReference.compareAndSet(20, 1, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName()+" 第3次时间戳:"+atomicStampedReference.getStamp()+" 值为:"+atomicStampedReference.getReference());

        },"t2").start();

    }
}

Ⅱ、使用场景不同

ReadWriteLock可以使用在读多写少的情况,尽量提升并发的能力 ReadWriteLock、synchronized使用的是独占锁,但是jdk对synchronized在编译时会有优化。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏爱撸猫的杰

rocketMQ安装中遇到的坑

The following softwares are assumed installed:

49130
来自专栏Android原创

写一个Gradle插件

我们在Android Studio中创建的app项目中,build.gradle常有如下这行代码:

23450
来自专栏娱乐心理测试

上传App Store 报错(ERROR ITMS-90475和ERROR ITMS-90474)

解决方案: 打开项目属性,选择“General”选项,页面下滑勾选红框标注的“Requires full screen”

10830
来自专栏WebDeveloper

Golang交叉编译

Golang 支持交叉编译,在一个平台上生成另一个平台的可执行程序,最近使用了一下,非常好用,这里备忘一下。

39920
来自专栏Sorrower的专栏

内核必须懂(四): 撰写内核驱动

11220
来自专栏yang0range

Android Studio3.3的使用

之前的的一篇文章,我们介绍了Android Studio3.3版本更新了那些功能。对我们开发人员来说,最显而易见的变化自然就是开发工具的变化。的确,这个版本升级...

19820
来自专栏网站制作

pageadmin CMS网站制作教程:http缓存方案的使用

pageadmin CMS网站制作教程: http缓存的作用是提供网站相应速度和负载,用户第一次访问一个页面时,会向服务器发出请求,服务器接受到请求后会对网站进...

5300
来自专栏码匠的流水账

聊聊jvm的Code Cache

JVM生成的native code存放的内存空间称之为Code Cache;JIT编译、JNI等都会编译代码到native code,其中JIT生成的nativ...

35440
来自专栏诸葛青云的专栏

5种方法,加密你的Python代码 !

Python越来越热门了,2019年3月TIOBE编程语言排行榜上,Python更是罕见的击败了“霸榜三巨头”之一的C++,挤进前三。

47700
来自专栏CRPER折腾记

Canvas绘制一个类似老版支付宝信用分仪表盘效果

ESM模块的发布,用了rollup来打包,很不错的一个工具,有时间我写个typescript-rollup-startkit

17910

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励