`
森林的天空
  • 浏览: 14461 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

JAVA并发编程学习笔记之AQS源码分析(共享与互斥)

 
阅读更多

共享模式与独占模式(非原创)

AQS的内部队列采用的是CLH队列锁模型,CLH队列是由一个一个结点(Node)构成的。Node类中有两个常量SHARE和EXCLUSIVE,顾名思义这两个常量用于表示这个结点支持共享模式还是独占模式,共享模式指的是允许多个线程获取同一个锁而且可能获取成功,独占模式指的是一个锁如果被一个线程持有,其他线程必须等待。多个线程读取一个文件可以采用共享模式,而当有一个线程在写文件时不会允许另一个线程写这个文件,这就是独占模式的应用场景。

 

  1. /** Marker to indicate a node is waiting in shared mode */  
  2. static final Node SHARED = new Node();  
  3.   
  4. /** Marker to indicate a node is waiting in exclusive mode */  
  5. static final Node EXCLUSIVE = null;  
  6.   
  7. final boolean isShared() {  
  8.     return nextWaiter == SHARED;  
  9. }  

以上代码是两种模式的定义,可以通过方法isShared来判断一个结点处于何种模式。

 

 

共享模式下获取锁

共享模式下获取锁是通过tryAcquireShared方法来实现的,其流程大至如下:
AQS类方法中方法名不含shared的默认是独占模式,前面提到子类需要重写tryAcquire方法,这是在独占模式下。如果子类想支持共享模式,同样必须重写tryAcquireShared方法,线程首先通过tryAcquireShared方法在共享模式下获取锁,如果获取成功就直接返回,否则执行以下步骤:
  1. /** 
  2.  * Acquires in shared uninterruptible mode. 
  3.  * @param arg the acquire argument 
  4.  */  
  5. private void doAcquireShared(int arg) {  
  6.     final Node node = addWaiter(Node.SHARED);  
  7.     boolean failed = true;  
  8.     try {  
  9.         boolean interrupted = false;  
  10.         for (;;) {  
  11.             final Node p = node.predecessor();  
  12.             if (p == head) {  
  13.                 int r = tryAcquireShared(arg);  
  14.                 if (r >= 0) {  
  15.                     setHeadAndPropagate(node, r);  
  16.                     p.next = null// help GC  
  17.                     if (interrupted)  
  18.                         selfInterrupt();  
  19.                     failed = false;  
  20.                     return;  
  21.                 }  
  22.             }  
  23.             if (shouldParkAfterFailedAcquire(p, node) &&  
  24.                 parkAndCheckInterrupt())  
  25.                 interrupted = true;  
  26.         }  
  27.     } finally {  
  28.         if (failed)  
  29.             cancelAcquire(node);  
  30.     }  
  31. }  
1、创建一个新结点(共享模式),加入到队尾,这个过程和独占模式一样,不再重复;
2、判断新结点的前趋结点是否为头结点,如果不是头结点,就将前趋结点的状态标志位设置为SIGNAL,当前线程可以安全地挂起,整个过程结束;
3、如果它的前趋是头结点,就让前趋在共享模式下获取锁,如果获取成功,把当前结点设置为头结点;
4、设置为头结点之后,满足释放锁条件就阻塞等待释放锁。
满足释放锁的条件为:允许传播或者需要通知继任结点,或者继任结点是共享模式的结点
  1. if (propagate > 0 || h == null || h.waitStatus < 0) {  
  2.           Node s = node.next;  
  3.           if (s == null || s.isShared())  
  4.               doReleaseShared();  
  5.       }  
 

共享模式下释放锁

这是通过方法releaseShared来实现的,整个流程如下:
1、调用子类的tryReleaseShared尝试获取锁,如果失败,直接返回;
2、如果成功调用doReleaseShared方法做后续处理,doReleaseShared方法如下:
  1. /** 
  2.     * Release action for shared mode -- signal successor and ensure 
  3.     * propagation. (Note: For exclusive mode, release just amounts 
  4.     * to calling unparkSuccessor of head if it needs signal.) 
  5.     */  
  6.    private void doReleaseShared() {  
  7.        /* 
  8.         * Ensure that a release propagates, even if there are other 
  9.         * in-progress acquires/releases.  This proceeds in the usual 
  10.         * way of trying to unparkSuccessor of head if it needs 
  11.         * signal. But if it does not, status is set to PROPAGATE to 
  12.         * ensure that upon release, propagation continues. 
  13.         * Additionally, we must loop in case a new node is added 
  14.         * while we are doing this. Also, unlike other uses of 
  15.         * unparkSuccessor, we need to know if CAS to reset status 
  16.         * fails, if so rechecking. 
  17.         */  
  18.        for (;;) {  
  19.            Node h = head;  
  20.            if (h != null && h != tail) {  
  21.                int ws = h.waitStatus;  
  22.                if (ws == Node.SIGNAL) {  
  23.                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))  
  24.                        continue;            // loop to recheck cases  
  25.                    unparkSuccessor(h);  
  26.                }  
  27.                else if (ws == 0 &&  
  28.                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))  
  29.                    continue;                // loop on failed CAS  
  30.            }  
  31.            if (h == head)                   // loop if head changed  
  32.                break;  
  33.        }  
  34.    }  
这个方法就一个目的,就是把当前结点设置为SIGNAL或者PROPAGATE,如果当前结点不是头结点也不是尾结点,先判断当前结点的状态位是否为SIGNAL,如果是就设置为0,因为共享模式下更多使用PROPAGATE来传播,SIGNAL会被经过两步改为PROPAGATE:
compareAndSetWaitStatus(h, Node.SIGNAL, 0)
compareAndSetWaitStatus(h, 0, Node.PROPAGATE)
为什么要经过两步呢?原因在unparkSuccessor方法:
  1. private void unparkSuccessor(Node node) {  
  2.     int ws = node.waitStatus;  
  3.     if (ws < 0)  
  4.         compareAndSetWaitStatus(node, ws, 0);  
  5.         ......  
  6. }  
如果直接从SIGNAL到PROPAGATE,那么到unparkSuccessor方法里面又被设置为0:SIGNAL--PROPAGATE---0----PROPAGATE
对头结点相当于多做了一次compareAndSet操作,其实性能也殊途同归啦!
 

闭锁(CountDownLatch)

闭锁是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
闭锁有几个重要的方法:
  1. public void await() throws InterruptedException;  
  2. public void countDown();  
其中await方法使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断,如果锁存器为0方法立即返回,一开始锁存器不会为0,当调用countDown方法之后锁存器会减少,当锁存器减少到0时,await方法就会返回。现在看看await方法的实现:
  1. public void await() throws InterruptedException {  
  2.     sync.acquireSharedInterruptibly(1);  
  3. }  
不出所料,闭锁的await方法正是使用的共享模式的AQS,acquireSharedInterruptibly和acquireShared方法类似,只不过会先响应中断。也就是当有多个线程调用await方法时,这些线程都被阻塞到了doAcquireShared方法的以下地方:
  1. if (shouldParkAfterFailedAcquire(p, node) &&  
  2.                    parkAndCheckInterrupt())  
  3.                    interrupted = true;  
前面看到doAcquireShared里面有一个for循环,退出for循环的唯一方式是要tryAcquireShared方法返回值大于0,下面看看tryAcquireShared的方法在闭锁中的实现:
  1. public class CountDownLatch {  
  2.     private static final class Sync extends AbstractQueuedSynchronizer  {  
  3.         Sync(int count) {  
  4.             setState(count);  
  5.         }  
  6.         ......  
  7.     }  
  8.       
  9.     private final Sync sync;  
  10.           
  11.     protected int tryAcquireShared(int acquires) {  
  12.         return (getState() == 0) ? 1 : -1;  
  13.     }  
  14.     ......  
  15. }  
count代表是的线程数,在创建闭锁的同步器时这个count值被赋给了state,因此state肯定不为0,所以tryAcquireShared方法肯定返回-1,也就是这些线程调用await方法时tryAcquireShared都返回-1,这些线程都会阻塞在doAcquireShared的for循环里。然后这些线程依次调用countDown方法,直到最后一个线程调用完后这些线程才会退出for循环继续执行。下面看看countDown方法的实现过程:
  1. public void countDown() {  
  2.     sync.releaseShared(1);  
  3. }  
  4.   
  5. //sync.releaseShared  
  6. public final boolean releaseShared(int arg) {  
  7.     if (tryReleaseShared(arg)) {  
  8.         doReleaseShared();  
  9.         return true;  
  10.     }  
  11.     return false;  
  12. }  
仍然不出所料,countDown方法正是调用的releaseShared方法,前面提到releaseShared会先调用tryReleaseShared方法,这是由闭锁实现的:
  1. protected boolean tryReleaseShared(int releases) {  
  2.     // Decrement count; signal when transition to zero  
  3.     for (;;) {  
  4.         int c = getState();  
  5.         if (c == 0)  
  6.             return false;  
  7.         int nextc = c-1;  
  8.         if (compareAndSetState(c, nextc))  
  9.             return nextc == 0;  
  10.     }  
  11. }  
该方法会递减state的值,直到变为0返回false.
现在整个闭锁的执行流程很明确了:N个线程调用await阻塞在for循环里面,然后N个线程依次调用countDown,每调用一次state减1,直接state为0,这些线程退出for循环(解除阻塞)!
退出for循环时,由于头结点状态标志位为PROPAGATE,而且这些结点都是共享模式,由头结点一传播,这些结点都获取锁,于是齐头并进执行了......
共享与独占在读写锁里面也有用到,后面再分析。
 

参考资料:

分享到:
评论

相关推荐

    Java并发编程学习笔记

    本文档主要内容如下: 1、线程安全和锁 Synchronized 底层实现原理 2、可重入锁与 Synchronized 的其他特性 3、ThreadLocal 的底层实现与使用 ...11、AQS源码分析 12、CAS原理分析和使用场景 13、.....

    java并发编程:juc、aqs

    Java 并发编程中的 JUC(java.util.concurrent)库以及其核心组件 AQS(AbstractQueuedSynchronizer)在构建高性能、可伸缩性的多线程应用方面具有重要的地位。 AQS 是 JUC 中的核心组件,它提供了一个框架,让...

    Java并发编程解析 | 解析AQS基础同步器的设计与实现

    Java并发编程解析 | 解析AQS基础同步器的设计与实现

    AQS源码分析 (1).pdf

    java锁底层实现,AQS源码分析。我在公司内部分享写的,如果想进一步了解,可以私聊

    阿里专家级并发编程架构师课程 彻底解决JAVA并发编程疑难杂症 JAVA并发编程高级教程

    课程内容包括了JAVA手写线程池,UC线程池API详解,线程安全根因详解,锁与原子类,分布式锁原理与实现方式,并发编程-AQS等等针对性非常强的JAVA编程开发教程,这其中的内容对JAVA开发技能的拔尖,非常的有帮助。...

    JAVA并发编程与高并发解决方案-并发编程四之J.U.C之AQS.docx

    JAVA并发编程与高并发解决方案-并发编程四之J.U.C之AQS.docx

    阿里专家级并发编程架构师课程-网盘链接提取码下载 .txt

    课程内容包括了JAVA手写线程池,UC线程池API详解,线程安全根因详解,锁与原子类,分布式锁原理与实现方式,并发编程-AQS等等针对性非常强的JAVA编程开发教程,这其中的内容对JAVA开发技能的拔尖,非常的有帮助。...

    Java并发编程原理与实战

    线程之间通信之join应用与实现原理剖析.mp4 ThreadLocal 使用及实现原理.mp4 并发工具类CountDownLatch详解.mp4 并发工具类CyclicBarrier 详解.mp4 并发工具类Semaphore详解.mp4 并发工具类Exchanger详解.mp4 ...

    龙果 java并发编程原理实战

    第4节学习并发的四个阶段并推荐学习并发的资料 [免费观看] 00:09:13分钟 | 第5节线程的状态以及各状态之间的转换详解00:21:56分钟 | 第6节线程的初始化,中断以及其源码讲解00:21:26分钟 | 第7节多种创建线程的...

    java并发编程面试题

    java并发编程 基础知识,守护线程与线程, 并行和并发有什么区别? 什么是上下文切换? 线程和进程区别 什么是线程和进程? 创建线程有哪几种方式?,如何避免线程死锁 线程的 run()和 start()有什么区别? 什么是 ...

    Java并发之AQS详解.pdf

    Java并发之AQS详解.pdf

    多线程与高并发编程笔记、源码等

    超全的多线程与高并发的编程笔记,从JVM&JMM角度讲多线程,synchronized优化原理,AQS和线程池等等,需要的童鞋请自行下载!

    Java 并发编程原理与实战视频

    第4节学习并发的四个阶段并推荐学习并发的资料 [免费观看] 00:09:13分钟 | 第5节线程的状态以及各状态之间的转换详解00:21:56分钟 | 第6节线程的初始化,中断以及其源码讲解00:21:26分钟 | 第7节多种创建线程的...

    aqs-并发编程笔记.pdf

    aqs-并发编程笔记.pdf

    龙果java并发编程完整视频

    第4节学习并发的四个阶段并推荐学习并发的资料 [免费观看] 00:09:13分钟 | 第5节线程的状态以及各状态之间的转换详解00:21:56分钟 | 第6节线程的初始化,中断以及其源码讲解00:21:26分钟 | 第7节多种创建线程的...

    Java并发编程实战

    Java并发编程实战 本书深入浅出地介绍了Java线程和并发,是一本完美的Java并发参考手册。书中从并发性和线程安全性的基本概念出发,介绍了如何使用类库提供的基本并发构建块,用于避免并发危险、构造线程安全的类及...

    20.9.24aqs-并发编程笔记.pdf

    20.9.24aqs-并发编程笔记.pdf

    Java并发编程及高并发解决方案

    第1章课程准备; 第2章并发基础. 第3章项目准备5 第4章线程安全性 第5章安全发布对象 ...第16章高并发之服务降级与服务熔断思路8 第17章高并发之数据库切库分库分表思路 第18章高并发之高可用手段介绍 第19章课程总结

    Java并发编程与高并发解决方案.txt

    java并发编程与高并发解决方案(完整)

Global site tag (gtag.js) - Google Analytics