读写锁的流程。
读写锁用的是一个aqs的同步器。
我们的流程是t1加写锁,t2加读锁。
state既要给写锁也要给读锁用。
写锁占了低16位,读锁占了高16位。
state 0没有加锁1加了锁 大于1多次重入,也可能是不同的读锁加锁的。
看下源码:
写锁的lock。
和之前一样,尝试加锁失败了才会进入队列的。
我们稍微看下tryAcquire
state不为0表示即可能是加了读锁,也可能是加了写锁。
为0进入如下的方法,加锁。
这个方法是判断公平还是非公平的,如果为非公平锁一直返回的是false。这个就是检查老二的,非公平事不检查老二的。
公平锁检查老二,看下有没有老二。
这个方法,如果是非公平锁总会返回的是false。
要是公平锁要检查队列看下队列的老二是不是有了,有了你要排在后面的。
要是非公平锁就是改state,设置为owner线程。
加锁成功就不在等待队列里面了。
---
c不为0,可能加的是读锁,也可能加的是写锁。
要是加的为写锁的话:
w==0加的是读锁,和我们要加的写锁是互斥的所以return为false。
w!=0不是当前的线程也是return的false。
上面这个流程是t1加的写锁。
写锁的获取流程:https://blog.csdn.net/fygu18/article/details/81784574
其实加锁成功就两步设置status和设置独占线程。
---253---
t1线程的写锁加成功了,接着就是t2加读锁了。
读写锁就是失败返回-1成功返回1。大于1是信号量的。
找读锁的lock。
返回值的说明:
区别:
-1表示失败,但是0和正数表示成功,0表示没有后继节点需要唤醒,整数表示还有几个后继节点需要唤醒。
读写锁就是0和1,信号量是大于1的。
进去看代码:
这个就是检查写锁的,看下有没有加写锁。
看下加写锁的是不是自己。这个意思是,同一个线程已经加了写锁,再加读锁的话是可以成功的。
这里t1已经加了写锁,t2加读锁,直接返回的是-1。
----
读锁的最终到这里:
我们只分析的是-1的情况就是
t2线程要进队列里面的。
看下加的节点类型:
加入的节点是shared类型的节点,这个要注意。执行完addWaiter之后还是活跃的状态的。
找下t2有没有前驱节点,节点是head说名是老二,有资格获得锁,再次调用tryAcquired
处于活跃状态就是没有被park住。
进入下面这个if,就是失败了是不是该阻塞住。
第一次是前驱节点设置为-1,返回false,第二次通过判断返回true。
内部将前驱节点设置为-1。再进入循环。
这个文章很好:https://blog.csdn.net/chen77716/article/details/6641477
最后park住:
---254---
t1加了写锁,t2加了读锁。
再来t3加读锁,t4加写锁,此时变成这个样子。
因为没有释放写锁,所以t3线程和t4线程都会加入到里面的。
两点注意:
1.0 -1 -1是有职责去唤醒后面的节点,是waitStatus属性。
2.独占和共享状态,就是node的shared属性和ex属性。
---
此时t1是写锁,t2 t3是读锁 t4是写锁。
---255---
t1忙完了,写锁的unlock。
我们看下代码:
我们看下tryRelease:
首先减去1,查看是不是减为0,查看要查看写锁的部分是不是减为0的。
当前执行的线程设置为null。
回来主方法,再来,这时候假设写锁都解开了。
执行唤醒流程,唤醒等待队列头结点的下一个节点。
我们想下t2读锁是在哪里被暂停的呢?
共享的读锁我们找到,这个方法相当于reentrantLock的acquiredQueue。
在这里继续运行。
前驱节点是head才有资格去竞争锁的。
进入tryAcquireShared方法看怎么竞争成功的。
此时这个为0,独占锁不为0肯定不能竞争成功的。
这里分析过了,不分析了往下分析。
这个方法以后再说,这个就是公平非公平。
c的写锁为0,则读锁基数+1,是高位+65536。
直接进入这个方法:
读锁的高位+1。下面的不看了。
return 1。读锁加锁成功。
再来就是这个方法,加锁成功再往后走。
加锁成功但是不是独占锁,所以是不能放在这里的。
进入:断开头节点
传入的是当前的节点和1
当前节点的下一个节点还是共享节点的话进入这个方法。
这里把头节点的状态由-1改为0。
把头节点去掉,t2所在的节点改为头节点。t2是当前线程。
---256---
还没完呢?
进入到sethead方法:
这个方法看下。
首先拿到当前节点的下一个节点。
就是这个:
就是判断t3是不是shared。
是的话调用这个方法:
头节点的状态尝试由-1改为0,防止改动的时候其他的线程受到干扰,-1是我要唤醒后继节点的。失败会continue。
再来:头节点的后续节点被唤醒。
这个就是唤醒的t3。
唤醒就是结束阻塞然后再循环。就是再进入到doAcquirieShared方法。
--
t3也是唤醒共享锁时候阻塞住的。
进入tryAcquireShared:
此时这个是1再+1。
共享读锁多个线程都可以让基数增加。
看下这个方法:
读的话就是一个唤醒,其他的都唤醒的,大家都读,因为不是互斥的
流程赏析:
最后是这样的了:
---257---
读锁的unlock:
现在t2和t3都是运行的状态,但是还没有unlock呢。
进入读锁的unlock。
第一步:
进入tryReleaseShared
可知,拿到state状态。
此时在这里减去1.
不是0则返回的是false。
此时不会进入到doReleaseShared的。
再次进入:
读锁是不用知道当前的线程的节点的,就是没有
t2 t3都进去了就减为0了。
进入:
改是首先尝试把头节点的状态由-1改为0,防止干扰。
开始唤醒。
t4在哪里恢复运行呢?在这里
在这里恢复与运行的。
注意设置这个在哪里:
---258---
StampedLock:
乐观读:戳失败才升级为读锁。
---259---
代码:
---260---
乐观读的读锁写锁都不支持条件变量的。