• 并发编程学习笔记(十四、AQS同步器源码解析3,Lock & Condition & AQS)


    目录:

    • Lock & Condition & AQS使用示例
    • Lock & Condition & AQS结构简述
    • Lock与Condition的API解析

    Lock & Condition & AQS使用示例

    在说Lock和Condition之前我们先来看一下这两个的用法,从用法到源码,一步一步来。

      1 public class ConditionDemo {
      2 
      3     /**
      4      * 创建独占锁
      5      */
      6     private ReentrantLock lock = new ReentrantLock();
      7 
      8     /**
      9      * 创建条件1
     10      */
     11     private Condition condition1 = lock.newCondition();
     12 
     13     /**
     14      * 创建条件2
     15      */
     16     private Condition condition2 = lock.newCondition();
     17 
     18     /**
     19      * 创建条件3
     20      */
     21     private Condition condition3 = lock.newCondition();
     22 
     23     /**
     24      * 日期格式化
     25      */
     26     private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:MM:ss");
     27 
     28     public static void main(String[] args) throws InterruptedException {
     29         ConditionDemo conditionDemo = new ConditionDemo();
     30         Thread1 thread1 = conditionDemo.new Thread1();
     31         Thread2 thread2 = conditionDemo.new Thread2();
     32         Thread3 thread3 = conditionDemo.new Thread3();
     33 
     34         // Alibaba Java Coding Guidelines plugin 插件提示不要直接使用Executors
     35         ExecutorService executorService = Executors.newCachedThreadPool();
     36         executorService.execute(thread1);
     37         executorService.execute(thread2);
     38         executorService.execute(thread3);
     39         Thread.sleep(2000);
     40 
     41         // 依次唤醒各线程任务.
     42         signalTest(conditionDemo);
     43         executorService.shutdown();
     44     }
     45 
     46     /**
     47      * 依次唤醒各线程任务,以观察condition的作用
     48      *
     49      * @param conditionDemo ConditionDemo对象
     50      * @throws InterruptedException 中断异常
     51      */
     52     public static void signalTest(ConditionDemo conditionDemo) throws InterruptedException {
     53         // 创建独占锁 唤醒condition1的线程
     54         conditionDemo.lock.lock();
     55         conditionDemo.condition1.signal();
     56         // 释放独占锁 等待thread1执行完毕.
     57         conditionDemo.lock.unlock();
     58         Thread.sleep(2000);
     59 
     60         // 创建独占锁 唤醒condition2的线程
     61         conditionDemo.lock.lock();
     62         conditionDemo.condition2.signal();
     63         // 释放独占锁 等待thread2执行完毕.
     64         conditionDemo.lock.unlock();
     65         Thread.sleep(2000);
     66 
     67         // 创建独占锁 唤醒condition3的线程
     68         conditionDemo.lock.lock();
     69         conditionDemo.condition3.signal();
     70         // 释放独占锁 等待thread2执行完毕.
     71         conditionDemo.lock.unlock();
     72         Thread.sleep(2000);
     73     }
     74 
     75     /**
     76      * 线程任务定义类
     77      */
     78     public class Thread1 implements Runnable {
     79 
     80         public Thread1() {
     81         }
     82 
     83         @Override
     84         public void run() {
     85             try {
     86                 // 设置线程名称
     87                 Thread.currentThread().setName(Thread1.class.getSimpleName());
     88                 System.out.printf("%s线程启动
    ", Thread.currentThread().getName());
     89                 lock.lock();
     90                 // 在condition1上阻塞,并且释放独占锁lock.
     91                 condition1.await();
     92                 System.out.printf("%s线程被唤醒", Thread.currentThread().getName());
     93                 printDate();
     94             }
     95             catch (InterruptedException e) {
     96                 e.printStackTrace();
     97             }
     98             finally {
     99                 lock.unlock();
    100             }
    101         }
    102         
    103     }
    104 
    105     /**
    106      * 线程任务定义类
    107      */
    108     public class Thread2 implements Runnable {
    109 
    110         public Thread2() {
    111         }
    112 
    113         @Override
    114         public void run() {
    115             try {
    116                 Thread.currentThread().setName(Thread2.class.getSimpleName());
    117                 System.out.printf("%s线程启动
    ", Thread.currentThread().getName());
    118                 lock.lock();
    119                 // 在condition2上阻塞,并且释放独占锁lock.
    120                 condition2.await();
    121                 System.out.printf("%s线程被唤醒", Thread.currentThread().getName());
    122                 printDate();
    123             }
    124             catch (InterruptedException e) {
    125                 e.printStackTrace();
    126             }
    127             finally {
    128                 lock.unlock();
    129             }
    130         }
    131         
    132     }
    133 
    134     /**
    135      * 线程任务定义类
    136      */
    137     public class Thread3 implements Runnable {
    138 
    139         public Thread3() {
    140         }
    141 
    142         @Override
    143         public void run() {
    144             try {
    145                 Thread.currentThread().setName(Thread3.class.getSimpleName());
    146                 System.out.printf("%s线程启动
    ", Thread.currentThread().getName());
    147                 lock.lock();
    148                 // 在condition3上阻塞,并且释放独占锁lock.
    149                 condition3.await();
    150                 System.out.printf("%s线程被唤醒", Thread.currentThread().getName());
    151                 printDate();
    152             }
    153             catch (InterruptedException e) {
    154                 e.printStackTrace();
    155             }
    156             finally {
    157                 lock.unlock();
    158             }
    159         }
    160         
    161     }
    162 
    163     /**
    164      * 打印时间
    165      */
    166     void printDate() {
    167         System.out.println("【当前时间:" + simpleDateFormat.format(new Date()) + "】");
    168     }
    169     
    170 }

    上面代码看似很多单其实逻辑非常简单,只展示了Lock及Condition的基本用法:

    • 首先我们创建一个Lock的可重入锁子类ReentrantLock以及三个Condition执行条件
    • 然后我们为ConditionDemo构建了三个线程,每个线程都会先去从ReentrantLock获取锁然后被对应的Condition阻塞最后完成逻辑后释放锁
    • 最后通过signalTest()函数依次唤醒各线程的任务,其中会先通过lock.lock()获取资源,然后通过condition1.signal()通知对应的线程可执行,最后通过lock.unlock()释放锁

    通过上述代码,你可以了解到Condition可阻塞线程的执行(await()),然后满足条件后通知线程可执行(signal())。

    这里需要注意的是,Lock一般和Condition配合使用

    Lock & Condition & AQS结构简述

    通过上述demo,你应该简单了解到了Lock与Condition的基本用法了,现在我们来看看它们的类结构,更进一步知晓它们。

    通过ReentrantLock构造函数我们可以了解其根据NonfairSync实现,NonfairSync根据Sync实现,Sync又是根据我们熟悉的AQS实现;并且ReentrantLock是实现了Lock接口的。

    而Condition的话同理可得出,其实现类为ConditionObject,其为AQS的内部类,java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject

    由此可得出如下类图:

    Lock与Condition的API解析

    在学习Lock及Condition的API之前,我们先回想下Lock及Condition的作用,分别是对资源的加锁,以及让线程阻塞,等待通知后再执行。

    emmmmm,那这不就synchronize以及Object对象的wait、notify嘛(⊙o⊙)…,的确很相似,哈哈。

    的确在大多数情况下,内置锁(synchronize)都能很好的工作,但它在功能上存在一些局限性,例如无法实现非阻塞结构的加锁规则等。

    而Lock以及Condition的出现正是为了解决这一问题,它实现了可定时可轮询可中断的锁获取操作,公平队列,以及非块结构的锁

    注意:

    • 与内置锁不同,Lock是一种显式锁,它更加危险,因为在程序离开被锁保护的代码块时,不会像监视器锁那样自动释放,需要我们手动释放锁
    • 所以在使用Lock时,一定要记得手动释放锁(lock.unlock())

    ——————————————————————————————————————————————————————————————————————

    该说的都说了接下来我们来熟悉下Lock与Condition的API。

    1、Lock:

     1 public interface Lock {
     2 
     3     /**
     4      * 阻塞式获取锁。未获取到时线程会被阻塞,不会参与线程调度,直到获取到锁为止;获取锁的过程中不响应中断。
     5      */
     6     void lock();
     7 
     8     /**
     9      * 阻塞式获取锁。在以下两种情况下可中断锁,并抛出InterruptedException,抛出后会清除中断标志位
    10      * 1、调用方法时,中断标志位已经设置为true了
    11      * 2、在获取锁时线程被中断了,并且获取锁的实现会响应这个中断
    12      */
    13     void lockInterruptibly() throws InterruptedException;
    14 
    15     /**
    16      * 非阻塞式获取锁。无论成功与否都会理解返回,获取成功返回true,反之false
    17      */
    18     boolean tryLock();
    19 
    20     /**
    21      * 同tryLock(),但带有超时机制,并且可中断。
    22      * 
    23      * 若获取到锁立即返回true,反之线程会休眠,直到满足一下三个条件之一:
    24      * 1、获取到锁
    25      * 2、其它线程中断了当前线程
    26      * 3、设定的超时时间到了
    27      * 若超时时间到了,还未获取锁,则会返回false。注意,这里超时并不会抛出异常
    28      * 
    29      * 中断的情况和lockInterruptibly()一致
    30      */
    31     boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    32 
    33     /**
    34      * 释放锁,这里需要注意的是,只有拥有锁的线程才能释放锁,而且必须显示的释放锁。
    35      */
    36     void unlock();
    37 
    38     /**
    39      * 创建一个Condition条件对象
    40      */
    41     Condition newCondition();
    42     
    43 }

    2、Condition:

    上面我们看到了Lock的最后一个方法newCondition(),由此方法便可以的出Condition是与Lock绑定的,而且他们还是一个一对多的关系

    那么我们来看下Condition是对象什么:

    首先,一个新工具的出现肯定是为了解决某方面的问题的,如果说Lock是为了扩展现有的监视器锁,那么Condition肯定就是对wait、notify的延伸了。

    ——————————————————————————————————————————————————————————————————————

    在说Condition API之前,我觉得我有必要说明下wait、notify的弊端,这样一方面能让你了解到wait、notify为啥在项目中不常见了,另一方面你可以更好的了解Condition的工作原理。

    首先,我们调用wait方法的原因通常是线程有一定的条件不满足,我们才会去让其等待。其次,所有调用了wait方法的线程都会在同一个wait set中等待,嗯嗯这看上去很合理。

    但这正式这一机制的短板之处,因为每一个wait的线程其实都是在等待同一个notify或notifyAll方法来唤醒自己,这就会导致自己可能会被别的线程的notify方法唤醒。

    然而我们知道每个线程的执行条件肯定是不一样的,故会导致我虽然抢到资源取执行逻辑了,但却会发现自己依然不满足执行条件,然后就又wait了。

    这无疑使一个浪费CPU资源的操作,所以说最好的方式就是每个线程知道自己在wait什么条件,这样notify的时候也不会唤醒别的线程,这就是Condition的做法。

    ——————————————————————————————————————————————————————————————————————

    Condition的起源介绍完了,最后我们只要说下其API就可以了,其实非常简单,我相信你一下就能看出来其用法:

     1 public interface Condition {
     2 
     3     void await() throws InterruptedException;
     4 
     5     void awaitUninterruptibly();
     6 
     7     long awaitNanos(long nanosTimeout) throws InterruptedException;
     8 
     9     boolean await(long time, TimeUnit unit) throws InterruptedException;
    10 
    11     boolean awaitUntil(Date deadline) throws InterruptedException;
    12 
    13     void signal();
    14 
    15     void signalAll();
    16     
    17 }

    从方法定义上就可以非常明了的知道,我们按照顺序类推:

    • await(),线程等待
    • 等待,但不响应中断
    • 等待相应时间,纳秒单位
    • 等待相应时间,自定义单位
    • 等待到指定时间
    • 唤醒线程
    • 唤醒所有线程

    哈哈是不是非常明了。

    嗯~~~,说完了Condition之后是不是觉得它和Object的wait、notify非常想,是的非常想,所以我们最后来总结下。

  • 相关阅读:
    查看uCOS-II的CPU使用率
    ARM的工作环境和工作模式
    一个简单的 JSON 生成/解析库
    [转] libtool的作用及应用
    Qt 使用 net-snmp 包的过程记录
    Qt 立体水晶按键实现
    xampp 修改 mysql 默认 root 密码
    mint 设置无线 AP
    dpkg 小记
    转-ubuntu清理卸载wine的残余项目
  • 原文地址:https://www.cnblogs.com/bzfsdr/p/13154793.html
Copyright © 2020-2023  润新知