若是以前使用过读写锁, 那么能够直接看本篇文章. 若是以前未使用过, 那么请配合个人另外一篇文章一块儿看: [源码分析]读写锁ReentrantReadWriteLockhtml
我先给出一个demo, 这样你们就能够根据我给的这段代码, 边调试边看源码了. 仍是那句话: 注意"My" , 我把ReentrantReadWriteLock类 更名为了 "MyReentrantReadWriteLock"类 , "Lock"类 更名为了"MyLock"类. 你们粘贴个人代码的时候, 把相应的"My"都去掉就行了, 不然会编译报错哦.java
demo里是一个公平读写锁多线程
import java.util.HashMap; import java.util.Map; import java.util.Scanner; import java.util.concurrent.locks.Lock; import java.util.function.Supplier; public class ReentrantReadWriteLockTest2 { static final Scanner scanner = new Scanner(System.in); static volatile String cmd = ""; private static MyReentrantReadWriteLock lock = new MyReentrantReadWriteLock(true); private static MyReentrantReadWriteLock.ReadLock readLock = lock.readLock(); private static MyReentrantReadWriteLock.WriteLock writeLock = lock.writeLock(); public static void main(String[] args) { for (Map.Entry<String, Lock> entry : new HashMap<String, Lock>() {{ for (int i = 0; i < 10; i++) { put("r" + i, readLock); put("w" + i, writeLock); } }}.entrySet()) { new Thread(() -> func(entry::getValue, entry.getKey())).start(); } while (scanner.hasNext()) { cmd = scanner.nextLine(); } } public static void func(Supplier<Lock> myLockSupplier, String name) { String en_type = myLockSupplier.get().getClass().getSimpleName().toLowerCase().split("lock")[0]; String zn_type = (en_type.equals("read") ? "读" : "写"); blockUntilEquals(() -> cmd, "lock " + en_type + " " + name); myLockSupplier.get().lock(); System.out.println(name + "获取了" + zn_type + "锁"); blockUntilEquals(() -> cmd, "unlock " + en_type + " " + name); myLockSupplier.get().unlock(); System.out.println(name + "释放了" + zn_type + "锁"); } private static void blockUntilEquals(Supplier<String> cmdSupplier, final String expect) { while (!cmdSupplier.get().equals(expect)) quietSleep(1000); } private static void quietSleep(int mills) { try { Thread.sleep(mills); } catch (InterruptedException e) { e.printStackTrace(); } } }
使用例子在下面. 源码分析
咱们能够看到r1持有了读锁以后, r2来申请读锁, 也能够成功. 说明读锁是能够共享的.ui
接下来申请写锁. 申请的写锁会进入到`等待队列`.spa
而后我们再申请读锁r3, r4. 因为我们的demo是公平的读写锁. `等待队列`中有线程在等待写锁时, 后续的申请读锁的线程也都会直接进入`等待队列`. 不会和先来的写锁线程争抢.线程
大体就是这样.更详细的请看下面小节的详解.3d
(图1)指针
-----------2018.07.29 下午---更新-----关于图片不清楚的问题------------------------调试
本文下面评论中提出这篇博客的图片看着很不舒服. 我之后会注意的. 以前没想到显示图片会有差异.
不过这里临时先给出一个解决办法. 若是想看仔细看某一个图片, 那么请这样作(能够拿上面的图1来试试, 本篇中除了图2和图3均可以的 ):
在图片上右键 -> 新标签中打开图片 - > 在这个新标签中鼠标指针是一个放大镜, 左键点击一下, 让图片放大.而后你会发现, 图片很是很是很是清晰....
大概就是这样的区别:
(图2)
(图3)
----------------------下面我们回到本篇主题---------------------------
我们实例化一个读写锁后, 锁的状态大体以下图:
此时锁是空闲状态.
若是这个时候r1来申请读锁.那么就能够直接成功, 变化以下的黑色阴影部分.
firstReader 是线程的引用. 读锁是共享的, 能够有不少线程来获取读锁. 而firstReader是记录这些持有读锁线程中第一个得到读锁的线程的.
firstReaderHoldCount是 firstReader引用的线程的读锁得到次数(也就是firstReader重入的次数)
接下来若是r2来申请读锁, 会发生什么?
r2会申请成功, 并且变化以下:
其中cacheHoldCounter是一个引用, 老是指向最后一个得到读锁的线程的计数器.
接下来让w1线程申请写锁. 写锁和读锁是互斥的, 因此写锁没法申请成功, 因而会进入到`等待队列`.
因为等待队列是懒初始化, 因此这个时候才会产生等待队列的头结点:
而后就是把w1对应的Node尾插到`等待队列`中了:
而后再把当前节点的前驱节点的waitStatus置为-1. -1表示后继节点在等待线程被激活.
而后线程w1就放心地挂起了:
接下来我们再让r3线程获取读锁会怎么样呢?
(我们如今演示的是公平锁, 若是有线程在队列里等待的话, 后续申请读锁的线程就不会直接拿到读锁, 而是进入到等待队列中. 毕竟写锁先来的嘛, 不能插队.)
线程r3进入到了`等待队列`中.而后线程r3挂起了. 变化如上图的黑色阴影部分所示.
接下来我们让r4申请读锁, 最终结果和r3同样, 就是进入到了`等待队列`的最末尾. (可是这个r4在后续的讲解中有用)
因此r4就不用讲了, 和r3同样:
接下来我们释放r1的读锁:
而后释放r2的读锁:
(cachedHoldCounter我没有加阴影, 是由于, 他其实并非真的变为null了, 仍是指向原来的那个元素, 可是这个已经不重要了.)
当线程r2释放读锁的时候发现读锁已经被彻底释放了, 因此会激活`等待队列` 里的第一个线程.
而且让第一个线程对应的Node做为新的Head. 淘汰掉原先的Head.
释放w1的写锁:
线程w1释放了读锁后, 激活了本身的后继节点r3.
r3被激活后,开始准备获取读锁.
把firstReader指向本身后, 把本身替换为新的Head节点:
线程r3申请完读锁后, 查看后继节点的nextWaiter是否等于Node.SHARED. 若是是, 那么就会唤醒这个后继节点.
因此接下来会唤醒r4:
如今r4被激活了, r4开始申请读锁了:
而后r4即将成为新的Head节点:
到这里, demo里的演示部分就完成了.
最后, 我们依次把r3 和 r4 也都释放了吧. 反正也剩的很少了.
释放了r3的时候, 变化以下:
最后我们释放掉r4:
(其中的cachedHoldCounter并非真正地变为了null, 而是还在指向着原来的元素. 只是在这里显得没用了, 因此那部分没话)
线程r4执行完以后, 全部的线程就都释放了. 锁的状态以下:
到这里, 整个公平读写锁的申请锁, 释放锁的过程, 就都演示完了.