Java并发编程(九)ReentrantReadWriteLock

一、ReentrantReadWriteLock简介

ReentrantReadWriteLock允许同一时间有一个写线程或多个读线程,满足了对读写并发控制有不同需求的场景,相对于排他锁,提高了并发性。在实际应用中,大部分情况下对共享数据(如缓存)的访问都是读操作远多于写操作,因此JDK提供了这个读写锁。ReentrantReadWriteLock支持以下特性:

  • 支持公平和非公平的获取锁的方式;
  • 支持可重入,读线程在获取了读锁后还可以获取读锁;写线程在获取了写锁之后既可以再次获取写锁又可以获取读锁;
  • 允许从写入锁降级为读取锁,其实现方式是:先获取写入锁,然后获取读取锁,最后释放写入锁。但是,从读取锁升级到写入锁是不允许的;
  • 读取锁和写入锁都支持锁获取期间的中断。

二、ReentrantReadWriteLock抢占锁

1、抢占读锁

  • 如果当前写线程已抢占并且本线程不是写线程,那么失败。
  • 否则,说明当前没有写线程或者本线程就是写线程(可重入),判断是否应该读线程阻塞并且读锁的个数是否小于最小值,并且CAS成功使读锁+1。 
  • 抢占失败的原因有三,第一是应该读线程应该阻塞;第二是因为读锁达到了上限;第三是因为CAS失败,有其他线程在并发更新state,那么会调动fullTryAcquireShared方法。

2、抢占写锁

  • 如果当前有写锁或者读锁。如果只有读锁,返回false,因为这时如果可以写,那么读线程得到的数据就有可能错误;如果有写锁,但是线程不同,即不符合写锁重入规则,返回false 。
  • 如果写锁的数量将会超过最大值65535,抛出异常;否则,写锁重入 。
  • 如果没有读锁或写锁的话,如果需要阻塞或者CAS失败,返回false;否则将当前线程置为获得写锁的线程。

3、小结

  • 如果当前没有写锁或读锁时,第一个获取锁的线程都会成功(当然先CAS),无论该锁是写锁还是读锁。 
  • 如果当前已经有了读锁,那么这时获取写锁将失败,获取读锁有可能成功也有可能失败。 
  • 如果当前已经有了写锁,那么这时获取读锁或写锁,如果线程相同(可重入),那么成功;否则失败。

三、ReentrantReadWriteLock释放锁

1、释放读锁

释放锁的第一步是更新计数,接下来进入死循环,尝试更新AQS的状态,一旦更新成功,则返回;否则,则重试。

2、释放写锁

  • 如果当前没有线程持有写锁,但是还要释放写锁,抛出异常 。
  • 得到解除一把写锁后的状态,如果没有写锁了,那么将AQS的线程置为null 。
  • 不管第二步中是否需要将AQS的线程置为null,AQS的状态总是要更新的。

3、小结

  • 获取锁要做的是更改AQS的状态值以及将需要等待的线程放入到队列中;释放锁要做的就是更改AQS的状态值以及唤醒队列中的等待线程来继续获取锁。
  • 如果当前是写锁被占有了,只有当写锁的数据降为0时才认为释放成功;否则失败。因为只要有写锁,那么除了占有写锁的那个线程,其他线程即不可以获得读锁,也不能获得写锁。
  • 如果当前是读锁被占有了,那么只有在写锁的个数为0时才认为释放成功。因为一旦有写锁,别的任何线程都不应该再获得读锁了,除了获得写锁的那个线程。

参考资料:

http://www.cnblogs.com/zaizhoumo/p/7782941.html

https://blog.csdn.net/qq_34144916/article/details/81197830

猜你喜欢

转载自blog.csdn.net/ss1300460973/article/details/84776887