转载:https://blog.csdn.net/yanyan19880509/article/details/52435135java
前面介绍了java中排它锁,共享锁的底层实现机制,本篇再进一步,学习很是有用的读写锁。鉴于读写锁比其余的锁要复杂,不想堆一大波的文字,本篇会试图图解式说明,把读写锁的机制用另一种方式阐述,鉴于本人水平有限,若是哪里有误,请不吝赐教。多线程
ReentrantReadWriteLock的锁策略有两种,分为公平策略和非公平策略,二者有些小区别,为便于理解,本小节将以示例的形式来讲明多线程下,使用公平策略的读写锁是如何处理的。并发
首先看一下即将出场的伙伴们,咱们一共会出场几个线程,还有用于实现读写机制的AQS同步器队列。每一个线程中的 R(0)W(0)表示当前线程占用了多少读写锁。工具
接下来,咱们一步步来看在公平策略下多线程并发的读写机制是怎样的。学习
1.线程A请求一个读锁,此时无人竞争锁,A获取读锁1,即线程A重入次数为1,以下所示:spa
2.线程B请求一个读锁,因为AQS中没有等待节点,当前处于读锁占有状态(线程A占有1个读锁),因此B成功获取读锁,以下所示:.net
3.这时候,线程C请求一个写锁,因为当前其余两个线程拥有读锁,写锁获取失败,线程C入队列,以下所示:线程
AQS初始化会建立一个空的头节点,C入队列,而后会休眠,等待其余线程释放锁唤醒。3d
4.线程D也来了,线程D想获取一个读锁,虽然当于处于读锁占有阶段,可是目前D不占有任何数量的读锁,并且同步器队列中已经有等待节点,这时候,因为公平策略,D不得已,一个字,等,以下图所示:blog
5.这时候,线程A执行完了,释放了读锁,因为B仍然占有读锁,因此释放后读锁仍然没有彻底释放,写锁仍然没有机会执行,以下图所示:
6.此次,B也执行完了,执行完后,读锁所有释放,这时候会唤醒排在同步器队头的节点C,C成功获取一个写锁,以下图所示:
7.一旦任何一个线程获取了写锁,除了该线程本身,其它线程都将没法获取读锁和写锁,这时候,线程C再次请求一个读锁,这是容许的,但反过来若是一个线程先获取了读锁,再获取写法则是不行的。这时候的状态以下图所示:
8.这时候假设线程E也来了,E想获取读锁,因为当前处于写锁状态,直接入队,以下所示:
9.这会C终于把活干完了,把读锁和写锁都给释放了,而后线程D被唤醒,获取了读锁,以下图所示:
10.这时候,若是再来一个线程,好比A,也想获取读锁,因为节点中还有线程E在等待,并且当前线程A没有获取任何读锁,不是重入状态,因此只能置入队尾,以下图所示:
11.这时候,若是D再次调用了一次获取读锁,因为D属于可重入状态,因此直接把读锁+1便可,以下图所示:
12.因为D获取的是读锁,同步队列中的E等待的也是读锁,因此E会被唤醒,获取读锁继续执行,以下图所示:
13.一样的,因为线程A获取的是读锁,在E执行后,会唤醒线程A,A也能够得到读锁,并继续执行,以下图所示:
14.最后你们各自执行,悄然退场。
接下来咱们再来看一下非公平策略读写锁机制又是如何的,为了更好的对比,咱们沿用公平锁的流程。
因为获取读锁的逻辑比较复杂,咱们在这里先简单进行概括:
a. 若是当前全局处于无锁状态,则当前线程获取读锁
b. 若是当前全局处于读锁状态,且队列中没有等待线程,则当前线程获取读锁
c. 若是当前全局处于写锁占用状态(而且不是当前线程占有),则当前线程入队尾
d. 若是当前全局处于读锁状态,且等待队列中第一个等待线程想获取写锁,那么当前线程可以获取到读锁的条件为:当前线程获取了写锁,还未释放;当前线程获取了读锁,这一次只是重入读锁而已;其它状况当前线程入队尾。之因此这样处理一方面是为了效率,一方面是为了不想获取写锁的线程饥饿,总是得不到执行的机会
e. 若是当前全局处于读锁状态,且等待队列中第一个等待线程不是写锁,则当前线程能够抢占读锁
获取写锁相对就比较简单了,规则以下:
h. 若是当前处于无锁状态,则当前线程获取写锁
i. 若是当前全局处于读锁状态,当前线程入队尾
j. 若是当前全局处于写锁状态,除非是重入获取写锁,不然入队尾
接下来咱们看一遍流程:
1.线程A请求一个读锁,全局处于无锁状态,根据规则a,线程A获取了锁,以下图所示:
2.线程B请求一个读锁,根据规则b,线程B能够获取到读锁
3.这时候,线程C请求一个写锁,因为当前其余两个线程拥有读锁,写锁获取失败,线程C入队列(根据规则i),以下所示:
AQS初始化会建立一个空的头节点,C入队列,而后会休眠,等待其余线程释放锁唤醒。
4.线程D也来了,线程D想获取一个读锁,根据读锁规则d,队列中第一个等待线程C请求的是写锁,为避免写锁迟迟获取不到,而且线程D不是重入获取读锁,因此线程D也入队,以下图所示:
5.这时候,线程A执行完了,释放了读锁,因为B仍然占有读锁,因此释放后读锁仍然没有彻底释放,写锁仍然没有机会执行,以下图所示:
6.此次,B也执行完了,执行完后,读锁所有释放,这时候会唤醒排在同步器队头的节点C,C成功获取一个写锁,以下图所示:
7.一旦任何一个线程获取了写锁,除了该线程本身,其它线程都将没法获取读锁和写锁,这时候,线程C再次请求一个读锁,这是容许的,但反过来若是一个线程先获取了读锁,再获取写锁则是不行的。这时候的状态以下图所示:
8.这时候假设线程E也来了,E想获取读锁,因为当前处于写锁状态,直接入队,以下所示:
9.这会C终于把活干完了,把读锁和写锁都给释放了,而后线程D被唤醒,获取了读锁,以下图所示:
10.这时候,若是再来一个线程,好比A,也想获取读锁,虽然等待队列中,E线程恰好还没被唤醒,但A线程是能够抢占读锁的(这里假设抢占到了),这个跟公平锁有明显的区别,以下图所示:
11.这时候,若是D再次调用了一次获取读锁,因为D属于可重入状态,因此直接把读锁+1便可,以下图所示:
12.因为当前状态下处于读锁状态,前面的线程D其实醒来后,是会同时唤醒线程E的,因此线程E也醒过来继续干活了,以下图所示:
13.同步队列中没有等待线程了,各个线程执行完后,一切相安无事了。
考虑到业务的多样化,java5中提供的并发包中的工具类大部分都同时提供了公平及非公平策略,这两种策略下,通常而言,非公平锁吞吐会比较大,因此默认状况下都是使用的非公平策略。
本篇试图以尽可能简单的方式来阐明读写锁的实现机制,为了直观,咱们只考虑简单抽象的方式,实际在实现的时候,会使用CAS去竞争锁。特别是在非公平策略中的第10个步骤,这种状况下有可能E先获取了读锁。不少时候,咱们在大体了解了实现步骤,流程以后,再去品味源码,就会更加的轻松。
最后仍是建议你们在了解了思路以后,本身多看看源码,多思考,学到的才是属于本身的东西。