Java多线程--- condition原理,为什么Condition可以精确唤醒指定的线程类型

Java多线程— condition原理

Java中,关于如何精确唤醒你想指定的线程类型,大家都会做,不就是使用显式的 lock 和 condition 吗。

但我不知道大家是否思考过这样的问题: 精确唤醒 与 精确执行 的区别

我规定 A B C 三个线程按 A B C 顺序执行,不使用 lock 和 condition ,我就使用 内置锁synchronized 和 wait、notify ,发现也可以实现, 但是 这种方式却并不是 “精确唤醒”,虽然它也让线程执行顺序达到了你的预期,但它的代价更大。

为什么 内置锁的方式代价更大 ?

因为使用内置锁,可能存在 从 A执行 --> B执行 期间, 其它线程被唤醒 并持有了锁,但发现没轮到自己执行,又释放了锁,这无疑增加了线程切换的消耗;

而使用 Condition ,那么 A执行后,唤醒的必然是B线程,不可能是是其它线程。

为什么 condition 可以唤醒指定的线程?

我们举个例子,规定

  • 新建A B C 三个线程
  • A线程先执行,然后 B 执行,再 C执行
  • A B C 依次循环

代码如下:

public class ExactSignal {
    public static void main(String[] args) {
        Example example = new Example();
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                example.printA();
            }
        },"A").start();

        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                example.printB();
            }
        },"B").start();

        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                example.printC();
            }
        },"C").start();
    }
}


class Example {
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
    // 给线程指定下标
    private int index = 1;

    public void printA(){
        lock.lock();
        try {
            while(index != 1){
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName()+"---AAA");
            index = 2;
            condition2.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printB(){
        lock.lock();
        try {
            while(index != 2){
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName()+"---BBB");
            index = 3;
            condition3.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printC(){
        lock.lock();
        try {
            while(index != 3){
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName()+"---CCC");
            index = 1;
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

测试结果: 确实 A B C 循环依次执行

先搞清楚几个概念:

  • 条件谓词 : 使某个操作成为状态依赖操作的前提操作;说人话就是:线程执行的条件,若该条件为假,则阻塞,为真 则继续执行

  • 条件队列:它使得一组线程(等待线程集合)能够通过某种方式来等待特定条件变成真,其元素是一个个正在等待相关条件的线程。

  • Lock 对应 synchronized 的内置锁,而condition 对应 内置锁的条件队列

  • 在 synchronized 中,一把锁只维护一个条件队列,一个 “条件队列” 却与多个 “条件谓词” 相关。

  • 当 使用 notify 方法时,JVM 会从条件队列上等待的多个线程中 选择一个来唤醒,notifyAll 唤醒所有,所以 notify 可能会导致 “信号丢失” 的问题。( A 执行完通知 B,但 JVM 唤醒了C,B没醒过来(信号丢失),锁 被C 抢了(虚假唤醒,又叫 过早唤醒),所以设置 while 循环去判断条件谓词真假,既可以解决信号丢失,又可以解决 虚假唤醒)

  • 一个 lock 可以有多个 condition,每一个condition 就是一个 条件队列,在同一个条件队列中的所有线程,他们的条件谓词是一样的,即他们被阻塞的原因是一样的

以上代码中,A B C 顺序执行的原因:

  • 存在 3 个条件队列 , 姑且叫做 a b c
  • a 队列的条件谓词是 index = 1 ;b 队列的条件谓词是 index = 2; c 队列的条件谓词是 index = 3
  • 当 index = 1 时,B 和 C 进入各自的方法,分别被 condition2 和 condition3 塞入各自的条件队列 分别是 b 和 c, 即 b 队列有 B 线程, c 队列有 C线程
  • A 执行完,index = 2, 调用 b 队列的通知方法 condition2.signal() ,此时唤醒的线程只可能是 b 队列中的某个线程, 不可能是c 队列,所以C 线程并不会被唤醒; 如果使用notify的话,C 线程可能就会被唤醒,之后C又阻塞,增大了线程切换的开销 和锁请求的次数
  • 所以 信号丢失 的问题 , condition 是可以解决的,但注意 condition 不能解决 虚假唤醒, 所以 你 使用condition 时,还是需要使用 while 循环

综上所述,按照我的理解,使用 condition 可以将 等待线程 根据他们的条件谓词 进行分类,确保程序员可以唤醒指定类型的线程,避免唤醒没必要执行的线程,减少了 循环执行await 的时间 ,线程切换的开销 和锁的请求次数

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值