• 从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队列而被挂起。

  • 相关阅读:
    解决Navicat for MySQL 连接 Mysql 8.0.11 出现1251- Client does not support authentication protocol 错误
    使用jquery validate结合zui作表单验证
    python中利用openpyxl、xlsxwriter库操作Excel
    pandas常用方法之read_excel详解
    利用python中的win32com模块操作Word、Excel文件
    python中如何将csv文件转为xls文件
    python中如何压缩和解压缩文件
    用python实现多个表格合并按字段去重
    ansible基础知识,基本操作
    关于Sublime Text无法打开 Package Control 及其他报错问题的解决
  • 原文地址:https://www.cnblogs.com/csdeblog/p/11442449.html
Copyright © 2020-2023  润新知