`
森林的天空
  • 浏览: 15240 次
  • 性别: 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并发编程学习笔记

    Java并发编程是Java开发中必不可少的一部分,涉及到多线程、同步机制、线程池以及并发工具类等多个核心知识点。以下是对这些主题的详细说明: 1. **线程安全与锁 Synchronized 底层实现原理**: 线程安全是指在多...

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

    "Java并发编程解析 | 解析AQS基础同步器的设计与实现" 在Java领域中,解决并发编程问题的关键是解决同步和互斥的问题。同步是指线程之间的通信和协作,互斥是指同一时刻只能允许一个线程访问共享资源。Java领域中有...

    java并发编程:juc、aqs

    Java并发编程中的`JUC`(Java Util Concurrency)库是Java平台中用于处理多线程问题的核心工具包,它提供了一系列高效、线程安全的工具类,帮助开发者编写并发应用程序。`AQS`(AbstractQueuedSynchronizer)是JUC库中的...

    Java 并发编程实战.pdf

    本书名为《Java 并发编程实战》,主要针对Java开发者,在内容上深入探讨了Java语言中的线程和并发编程机制。本书的描述表明了它将复杂的技术内容用浅显易懂的方式表达出来,使之成为读者在学习和使用Java进行并发...

    Java并发编程全景图.pdf

    Java并发编程是Java语言中最为复杂且重要的部分之一,它涉及了多线程编程、内存模型、同步机制等多个领域。为了深入理解Java并发编程,有必要了解其核心技术点和相关实现原理,以下将详细介绍文件中提及的关键知识点...

    Java并发编程:深入解析抽象队列同步器(AQS)及其在Lock中的应用

    本文深入探讨了Java并发编程的关键组件——抽象队列同步器(AQS)及其在ReentrantLock的应用。AQS是处理线程同步问题的高效工具,是Java并发编程中的核心。文章首先简要介绍了并发编程领域的先驱Doug Lea。重点在于...

    AQS源码分析 (1).pdf

    接下来,我们来具体分析一下AQS的源码。AQS中定义了一个名为state的volatile变量,用于表示同步状态。这个变量有三种操作方法:getstate()、setstate()和compareAndSetState(),分别用于获取、设置和原子性地更新...

    图灵Java高级互联网架构师第6期并发编程专题笔记.zip

    04-Java并发线程池底层原理详解与源码分析-monkey 05-并发编程之深入理解Java线程-fox 06-并发编程之CAS&Atomic原子操作详解-fox 07-并发锁机制之深入理解synchronized(一)-fox 08-并发锁机制之深入理解...

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

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

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

    ### JAVA并发编程与高并发解决方案-并发编程四之J.U.C之AQS #### 引言 《JAVA并发编程与高并发解决方案-并发编程四之J.U.C之AQS》是一篇详细介绍Java实用并发工具包(Java Util Concurrency,简称J.U.C.)中重要...

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

    标题“多线程与高并发编程笔记、源码等”表明了资源的核心内容,涵盖了多线程和高并发编程的理论与实践。多线程允许一个应用程序同时执行多个任务,而高并发则指系统能够处理大量并发请求的能力。这两个概念在现代...

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

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

    Java并发编程原理与实战

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

    Java并发之AQS详解.pdf

    AbstractQueuedSynchronizer(AQS)是 Java 并发编程中的一个核心组件,提供了一套多线程访问共享资源的同步器框架。AQS 定义了两种资源共享方式:Exclusive(独占)和 Share(共享)。在 AQS 中,维护了一个 ...

    龙果 java并发编程原理实战

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

    java并发编程-AQS和JUC实战

    ### Java并发编程-AQS和JUC实战 #### 一、ReentrantLock 重入锁 **1.1 概述** - **基本介绍**: `ReentrantLock` 是一个实现了 `Lock` 接口的可重入互斥锁,提供比 `synchronized` 更丰富的功能。与 `synchronized...

    Java并发 结合源码分析AQS原理

    Java并发结合源码分析AQS原理 Java并发编程中,AQS(AbstractQueuedSynchronizer)是一个核心组件,它提供了一个基于FIFO队列和状态变量的基础框架,用于构建锁和其他同步装置。在这篇文章中,我们将深入探讨AQS的...

    java并发编程面试题

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

    7 AQS源码分析.docx

    《深入解析AQS源码:理解Java并发编程的核心机制》 AQS,即AbstractQueuedSynchronizer,是Java并发编程中的重要组件,主要用于构建锁和同步器。它基于一种称为CLH(Craig, Landin, and Hagersten)队列的等待队列...

Global site tag (gtag.js) - Google Analytics