从ReentrantLock实例分析AbstractQueuedSynchronizer和ConditionObject

1.实例:3个线程交替打印1,2,3一定次数

代码如下:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TestReentrantLock {

    private static final int times = 12;
    private static int count = 0;

    public static void main(String[] args) {
        // 实现1,2,3交替打印times次
        Lock lock = new ReentrantLock();
        Condition condition1 = lock.newCondition();
        Condition condition2 = lock.newCondition();
        Condition condition3 = lock.newCondition();

        Thread print1 = new Thread(() -> {
            lock.lock();
            for (int i = 0; i < times; i++) {
                try {
                    while (count % 3 != 0)
                        condition3.await();
                    print(1);
                    count++;
                    condition1.signal();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            lock.unlock();
        });

        Thread print2 = new Thread(() -> {
            lock.lock();
            for (int i = 0; i < times; i++) {
                try {
                    while (count % 3 != 1)
                        condition1.await();
                    print(2);
                    count++;
                    condition2.signal();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            lock.unlock();
        });

        Thread print3 = new Thread(() -> {
            lock.lock();
            for (int i = 0; i < times; i++) {
                try {
                    while (count % 3 != 2)
                        condition2.await();
                    print(3);
                    count++;
                    condition3.signal();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            lock.unlock();
        });

        print1.start();
        print2.start();
        print3.start();
    }

    private static void print(int num) {
        System.out.print(num);
    }
}

运行结果:

2.源码分析

首先3个线程启动后会执行lock方法,这个方法底层是AQS实现的。

ReentrantLock默认非公平锁,所以lock方法会首先尝试通过CAS直接获取锁,如果获取失败执行acquire(1)函数。

这里只有一个线程会获取成功,假设是线程2,那么此时state=1,exclusiveOwnerThread=Thread-1,队列的头尾指针都为null。

然后针对线程2,会进入循环开始准备打印,但此时由于1还没有打印,所以会执行condition1.await()这行;

具体进入源码,此时会执行addConditionWaiter()这个函数,将创建一个Node(thread-1,waitStatus=CONDITION)并加入condition1的队列中并返回这个节点。

然后会执行fullyRelease(node),具体是调用release(savedState)彻底释放锁,所以这里saveState是state的值。

这里release会首先调用tryRelease方法尝试释放,这个方法是由ReentrantLock中的Sync类实现的。

这里会将state置为0,exclusiveOwnerThread置为null,返回true。

然后当前锁队列为空,release函数返回true。

这时fullyRelease返回savedState即1。

然后会进入循环判断node是否在锁队列中,这里显然不在,所以进入循环。

然后会执行LockSupport.park(this)挂起当前线程。

这个时候lock对象state=0,exclusiveOwnerThread=null,队列也为空。

condition1队列如下图,condition2,condition3都为空。

假设现在到thread1执行,首先通过CAS获取锁,值state=1,exclusiveOwnerThread=thread-0。

然后不进入循环,直接打印1,将count加1,然后执行condition1.signal()。

这里会执行doSignal方法,将condition1队列的那个节点作为参数。

  在doSignal方法中,会将节点从队列移除,此时condition1队列为空。

     然后会执行transferForSignal方法,这里会调用enq方法将节点加入锁队列,即sync队列。

   调用结束后sync队列如下图所示,然后transferForSignal方法返回true。

        

然后doSingal方法结束,signal方法也结束。

此时锁还是线程1持有,3个条件队列都为空,然后进入下一次循环,此时count=1,需要执行condition3.await()。

首先创建节点加入condition3的队列,然后调用fullyRelease方法释放锁。fullyRelease会调用release方法释放锁,

  这里release在执行完tryRelease后,与上一次不同的是这次sync队列不为空,会执行unparkSuccessor函数,传入参数是head。

    这个函数会选择第一个合适的节点进行唤醒,这里就是唤醒了线程2。

  唤醒后整个fullyRelease方法结束。后面由于线程1不在sync队列,会被挂起。

这个时候lock对象state=0,exclusiveOwnerThread=null,sync队列如下图。

然后conditon3队列中存着线程1节点,condition1,condition2都为空。

线程1被挂起,线程2被唤醒。

如果这时线程3被执行,那么通过CAS得到了锁,lock对象state=1,exclusiveOwnerThread=thread-2。

那么这时对于线程2,在执行acquireQueued方法时调用tryRequire就会失败,然后会去调用shouldParkAfterFailedAcquire方法,

方法参数p为head,node为线程2对应节点。

这个方法将head节点的waitStatus置为SIGNAL,返回false。

这时会进行新的一次循环,这次在调用shouldParkAfterFailedAcquire时会返回true,这时会执行方法parkAndCheckInterrupt()。

这个方法会挂起当前线程,即线程2。

对于线程3这时由于count=1,所以执行condition2.await()方法。

首先创建节点加入condition2队列,然后调用fullyRelease释放锁,通过release函数调用tryRelease成功后,lock对象state=0,exclusiveOwnerThread=null。

然后会执行unparkSuccessor函数,重新唤醒线程2。再然后线程3由于不在sync队列,所以被挂起。

然后线程2这时执行acquireQueued方法,成功获取到了锁,将从sync队列中删去这个节点,此时lock对象state=1,exclusiveOwnerThread=thread-1。

condition1队列为空,condition2,condition3队列分别存储线程1和线程3对应的节点。

线程2获取到锁后,await方法执行结束。此时count=1跳出循环,然后打印2,count加1,调用condition2.signal()。

这里过程与之前线程1执行condition1.signal()类似,将线程3对应节点加入sync队列,并从condition2队列中移除。

然后是不满足循环条件,执行condition1.await()函数,首先将创建节点加入condition1队列,然后fullyRelease释放锁,再通过unparkSuccessor唤醒线程3。

最后由于线程2不在sync队列,被挂起。

这时线程3与线程2类似,先通过acquireQueued拿到锁并将节点从sync队列移除,condition2.await方法执行结束,然后跳出循环,打印3,然后count++,再执行

condition3.signal方法将线程1节点从condition3队列删除,加入sync队列。

然后再次循环不满足条件,执行condition2.await方法,将线程3节点加入condition2队列,然后通过tryRelease释放锁,通过unparkSuccessor唤醒线程1,自身由于不在

sync队列被挂起。

后续过程与以上类似,不再分析。

通过分析可以看出,在调用condition.signal()时,只是将condition队列上的第一个节点移到了sync队列,并不释放锁;

condition.await()会将当前线程的节点加入条件队列,然后释放锁,释放后如果有线程在sync队列就进行唤醒第一个合适的。之后会因为不在sync队列而被挂起。

原文地址:https://www.cnblogs.com/csdeblog/p/11442449.html

时间: 2024-10-28 14:46:30

从ReentrantLock实例分析AbstractQueuedSynchronizer和ConditionObject的相关文章

通过ReentrantLock源代码分析AbstractQueuedSynchronizer独占模式

1. 重入锁的概念与作用       reentrant 锁意味着什么呢?简单来说,它有一个与获取锁相关的计数器,如果已占有锁的某个线程再次获取锁,那么lock方法中将计数器就加1后就会立刻返回.当释放锁时计数器减1,若计数器不为0,说明线程仍然占有锁:若计数器值为0,线程才会真正释放锁. 可重入锁可以避免同一个线程嵌套(或者说递归)获取锁时的死锁现象. 考虑下面这样一种情况 public class LockAnalysis { private Lock l = new ReentrantLo

【OpenGL】Shader实例分析(七)- 雪花飘落效果

转发请保持地址:http://blog.csdn.net/stalendp/article/details/40624603 研究了一个雪花飘落效果.感觉挺不错的.分享给大家,效果例如以下: 代码例如以下: Shader "shadertoy/Flakes" { // https://www.shadertoy.com/view/4d2Xzc Properties{ iMouse ("Mouse Pos", Vector) = (100,100,0,0) iChan

Apache漏洞利用与安全加固实例分析

Apache 作为Web应用的载体,一旦出现安全问题,那么运行在其上的Web应用的安全也无法得到保障,所以,研究Apache的漏洞与安全性非常有意义.本文将结合实例来谈谈针对Apache的漏洞利用和安全加固措施. Apache HTTP Server(以下简称Apache)是Apache软件基金会的一个开放源码的网页服务器,可以在大多数计算机操作系统中运行,是最流行的Web服务器软件之一.虽然近年来Nginx和Lighttpd等Web Server的市场份额增长得很快,但Apache仍然是这个领

java基础学习05(面向对象基础01--类实例分析)

面向对象基础01(类实例分析) 实现的目标 1.如何分析一个类(类的基本分析思路) 分析的思路 1.根据要求写出类所包含的属性2.所有的属性都必须进行封装(private)3.封装之后的属性通过setter和getter设置和取得4.如果需要可以加入若干构造方法 5.再根据其它要求添加相应的方法6.类中的所有方法都不要直接输出,而是交给被调用处调用 Demo 定义并测试一个名为Student的类,包括属性有"学号"."姓名"以及3门课程"数学".

第十七篇:实例分析(3)--初探WDDM驱动学习笔记(十)

续: 还是记录一下, BltFuncs.cpp中的函数作用: CONVERT_32BPP_TO_16BPP 是将32bit的pixel转换成16bit的形式. 输入是DWORD 32位中, BYTE 0,1,2分别是RGB分量, 而BYTE3则是不用的 为了不减少color的范围, 所以,都是取RGB8,8,8的高RGB5, 6, 5位, 然后将这16位构成一个pixel. CONVERT_16BPP_TO_32BPP是将16bit的pixel转换成32bit的形式 输入是WORD 16BIT中

第十七篇:实例分析(4)--初探WDDM驱动学习笔记(十一)

感觉有必要把 KMDDOD_INITIALIZATION_DATA 中的这些函数指针的意思解释一下, 以便进一步的深入代码. DxgkDdiAddDevice 前面已经说过, 这个函数的主要内容是,将BASIC_DISPLAY_DRIVER实例指针存在context中, 以便后期使用, 支持多实例. DxgkDdiStartDevice 取得设备信息, 往注册表中加入内容, 从POST设备中获取FRAME BUFFER以及相关信息(DxgkCbAcquirePostDisplayOwnershi

实例分析Robots.txt写法

题意:经典八数码问题 思路:HASH+BFS #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int MAXN = 500000; const int size = 1000003; typedef int State[9]; char str[30]; int state[9],goal[9]={

Camera图像处理原理及实例分析-重要图像概念

Camera图像处理原理及实例分析 作者:刘旭晖  [email protected]  转载请注明出处 BLOG:http://blog.csdn.net/colorant/ 主页:http://rgbbones.googlepages.com/ 做为拍照手机的核心模块之一,camera sensor 效果的调整,涉及到众多的参数,如果对基本的光学原理及 sensor 软/硬件对图像处理的原理能有深入的理解和把握的话,对我们的工作将会起到事半功倍的效果.否则,缺乏了理论的指导,只能是凭感觉和经

HTTP的上传文件实例分析

HTTP的上传文件实例分析 由于论坛不支持Word写文章发帖. 首先就是附件发送怎么搞,这个必须解决.论坛是php的.我用Chrome类浏览器跟踪请求,但是上传的文件流怎么发过去没找到,估计流可能多或者什么的不好显示,只知道发送了文件名字.需要实际了解下post文件,不能只会后台或界面不了解前台数据处理和协议怎么传送数据. 图中:有些相关文章 HTTP请求中的form data和request payload的区别 AJAX POST请求中参数以form data和request payload