java排他_聊聊java中的锁

前言

java中的锁大体可为分两种,一种叫排它锁,一种叫共享锁。排它锁,任意时刻只能有且只有一个线程持有,其它获取不到排它锁的线程要么自旋等待要么阻塞等待被唤醒。其中经常被我们提到的synchronized就是典型的排它锁,除此之外还有一个常用的ReentrantLock也是排它锁。

共享锁,一种可以同时被多个线程持有的锁,持有共享锁的线程之间不会相互竞争和阻塞。

排它锁很多时候等同于另外一个名称:写锁,共享锁等同于:读锁。

之所以出现读锁和写锁那是因为系统通常都是读多写少,读锁共享可以大大提高系统性能。

读锁与写锁

不知道大家有没有想过一个问题:既然修改的数据的时候已经加写锁了,写锁排它,那为什么修改完之后,读的时候还要加读锁呢?比如下面这段代码:

private int i = 1;

public synchronized void setI(int i){

this.i = i;

}

public int getI(){

return this.i;

}

有一个线程A调用完setI(2)之后,另外一个线程B调用getI()方法能保证返回值是2吗?答案是:不一定!why?java内存模型。关于java内存模型的简单描述可参考林林:剖析volatile、synchronized实现原理​zhuanlan.zhihu.coma30c9fcb2866cdb145c0c8cf636cf96c.png

那为什么加读锁就可以了呢?因为锁的内存语义是:让线程本地的副本变量无效化,从主内存中获取!。所以上面get()方法加锁读就可以读到最新的值。

所以写锁和读锁总是成对出现,因为只有写锁没有读锁保证不了线程安全。

锁设计

1、可重入

无论是读锁还是写锁,锁一定要设计成可重入的,因为方法本身可以调用自己。如果是锁不重入,线程第一次进来时加锁成功,第二次进来的时候就会阻塞,因为锁被占用了,结果自己阻塞自己,造成死锁。可重入的话,后面再进来,判断加锁线程是自身就不用阻塞了,避免死锁。如何实现可重入?存储加锁的线程的即可。写锁排它,所以任何时候只需要存储一个线程。读锁共享,用ThreadLocal。

2、读锁如何共享、写锁如何排它

参考java中ReentrantReadWriteLock的设计。

用一个32位的int字段,高16位存储是否有读锁,低16位是否有写锁,然后用volatile修饰,保证线程间可见性,更新值时用cas保证原子性。

假设存储字段名称为state,当加读锁时只需要cas(state,state + 1<<16),即可在高16位加1。

加写锁时cas(state, state + 1) 即可在低16位加1。

加锁时如果判断 (state >>> 16) > 0 就表示当前存在读锁,这时可以加读锁,写锁拒绝。

如果是state & ( 1 << 16 -1) > 0 就表示当前存在写锁,除了重入锁的线程外,其它线程的读锁、写锁都不可以加。

释放锁时用cas用还原操作即可。

这样就可以实现读锁的共享和写锁的排它。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值