上一篇详细的分析了独占模式下如何对线程进行处理:简单的总结是Java面向用户提供了锁的机制,后面的实现使用了一个同步队列,因为队列具备先进先出的特色,把每一个线程都构形成为队列中的节点,每一个节点定义一个状态值,符合状态的节点(线程)才能够有执行的机会,执行完释放,后面的线程只能是等待着前面的执行结果进行判断,每一个线程的执行都是独立的,不能有其余的线程干扰,因此在用户的角度来看线程是在同步的执行的,而且是独占式的。html
共享式和独占式的区别主要是可否在同一时刻不一样的线程获取到同步状态
node
上图能够很直观的看出独占式和共享式的区别。在对一个资源进行访问的时候,对读操做是共享式的,而对写操做是独占式的。并发
下面接着分析共享模式下线程之间是怎么实现的?
依然找到方法执行的入口,在上一篇咱们找到了这几种方式的顶层方法。ui
在acquireShared()
方法中,调用了tryAcquireShared()
返回一个状态值,进行判断,获取成功直接返回,失败进入等待队列中。this
public final void acquireShared(int arg) { if (tryAcquireShared(arg) < 0) doAcquireShared(arg); }
方法执行的顺序:线程
tryAcquireShared()
方法尝试去获取资源,具体实现是在子类中进行实现,成功直接返回,失败执行下面的方法。doAcquireShared()
方法,线程进入等待队列,等待获取资源。tryAcquireShared()
方法是在子类中实现的,这里不须要讨论,可是返回值已是定义好的。方法返回一个int类型的值,当返回值大于0的时候,表示可以获取到同步状态。code
doAcquireShared()方法:
在前面分析过的方法这里再也不分析htm
private void doAcquireShared(int arg) { //调用addWaiter方法把线程加入队列尾 final Node node = addWaiter(Node.SHARED); //设置成功标识 boolean failed = true; try { //设置中断标识 boolean interrupted = false; //死循环 for (;;) { //得到前驱结点 final Node p = node.predecessor(); //若是当前结点的前驱结点是头结点 if (p == head) { //尝试获取资源 int r = tryAcquireShared(arg); if (r >= 0) { //若是获取到资源 setHeadAndPropagate(node, r); //设置指向空,帮助回收 p.next = null; // help GC if (interrupted) //若是被中断过,从新设置中断 selfInterrupt(); failed = false; return; } } //判断线程状态移除或者挂起 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }
基本上和独占式的处理方式一致,根本的体现就是在下面的这个setHeadAndPropagate()
方法
setHeadAndPropagate():对象
private void setHeadAndPropagate(Node node, int propagate) { Node h = head; setHead(node); //若是还有资源剩余,继续唤醒后面挨着的线程 if (propagate > 0 || h == null || h.waitStatus < 0) { Node s = node.next; if (s == null || s.isShared()) doReleaseShared(); } }
在独占模式下获取资源,即便资源还有剩余,队列中的后一个线程节点也不会被唤醒执行,只有本身占有才是独占,而共享式的是有剩余就会给后面。blog
parkAndCheckInterrupt():
private final boolean parkAndCheckInterrupt() { //让当前对象中止执行,阻塞状态 LockSupport.park(this); //检查中断 return Thread.interrupted(); }
cancelAcquire():
private void cancelAcquire(Node node) { if (node == null) return; node.thread = null; //得到前驱结点 Node pred = node.prev; //若是状态值大于0 while (pred.waitStatus > 0) //移除当前节点的前驱结点 node.prev = pred = pred.prev; //设置节点状态值为CANCELLED node.waitStatus = Node.CANCELLED; //若是是尾节点,设置尾节点 if (node == tail && compareAndSetTail(node, pred)) { compareAndSetNext(pred, predNext, null); } else { int ws; //当节点既不是尾节点,也不是头节点的后继节点时,下面的这些判断其实执行的出队操做起做用的就是compareAndSetNext()方法将pred指向后继节点 if (pred != head && ((ws = pred.waitStatus) == Node.SIGNAL || (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) && pred.thread != null) { Node next = node.next; if (next != null && next.waitStatus <= 0) compareAndSetNext(pred, predNext, next); } else { //若是node是head的后继节点,则直接唤醒node的后继节点 unparkSuccessor(node); } node.next = node; // help GC } }
尝试释放资源若是成功,会唤醒后续处于等待状态中的节点
public final boolean releaseShared(int arg) { //尝试释放资源 if (tryReleaseShared(arg)) { //唤醒后继节点 doReleaseShared(); return true; } return false; }
doReleaseShared():
private void doReleaseShared() { for (;;) { Node h = head; if (h != null && h != tail) { int ws = h.waitStatus; if (ws == Node.SIGNAL) { //唤醒后继节点 if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) continue; unparkSuccessor(h); } else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) continue; } if (h == head) break; } }
和独占式的释放从代码上看他们的区别是共享式的释放方法中有一个循环。
引用:http://www.javashuo.com/article/p-xcevmtwv-gz.html
上面方法的流程也比较简单,一句话:释放掉资源后,唤醒后继。跟独占模式下的release()类似,但有一点稍微须要注意:独占模式下的tryRelease()在彻底释放掉资源(state=0)后,才会返回true去唤醒其余线程,这主要是基于独占下可重入的考量;而共享模式下的releaseShared()则没有这种要求,共享模式实质就是控制必定量的线程并发执行,那么拥有资源的线程在释放掉部分资源时就能够唤醒后继等待结点。例如,资源总量是13,A(5)和B(7)分别获取到资源并发运行,C(4)来时只剩1个资源就须要等待。A在运行过程当中释放掉2个资源量,而后tryReleaseShared(2)返回true唤醒C,C一看只有3个仍不够继续等待;随后B又释放2个,tryReleaseShared(2)返回true唤醒C,C一看有5个够本身用了,而后C就能够跟A和B一块儿运行。而ReentrantReadWriteLock读锁的tryReleaseShared()只有在彻底释放掉资源(state=0)才返回true,因此自定义同步器能够根据须要决定tryReleaseShared()的返回值。
在共享模式下,资源有剩余就会给后面的邻居,不会本身占有,这是和独占式的根本区别。