juc包:使用 juc 包下的显式 Lock 实现线程间通信


一、前置知识


线程间通信三要素:

多线程+判断+操作+通知+资源类。

上面的五个要素,其他三个要素就是普通的多线程程序问题,那么通信就需要线程间的互相通知,往往伴随着何时通信的判断逻辑。

在 java 的 Object 类里就提供了对应的方法来进行通知,同样的,保证安全的判断采用隐式的对象锁,也就是 synchronized 关键字实现。这块内容在:

java多线程:线程间通信——生产者消费者模型

已经写过。


二、使用 Lock 实现线程间通信


那么,我们知道 juc 包里提供了显式的锁,即 Lock 接口的各种实现类,如果想用显式的锁来实现线程间通信问题,唤醒方法就要使用对应的 Conditon 类的 await 和 signalAll 方法。(这两个方法名字也能看得出来,对应 Object 类的 wait 和 notifyAll)

Condition 对象的获取可以通过具体的 Lock 实现类的对象的 newCondition 方法获得。

public class Communication2 {
    public static void main(String[] args) {
        Container container = new Container();
        new Thread(()->{
            for (int i = 0; i < 10; i++){
                container.increment();
            }
        },"生产者1").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++){
                container.decrenment();
            }
        },"消费者1").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++){
                container.increment();
            }
        },"生产者2").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++){
                container.decrenment();
            }
        },"消费者2").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++){
                container.increment();
            }
        },"生产者3").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++){
                container.decrenment();
            }
        },"消费者3").start();
    }

}

class Container{
    private int count = 0;
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void increment(){
        lock.lock();
        try {
            while (count != 0){
                condition.await();
            }
            count++;
            System.out.println(Thread.currentThread().getName() + " "+count);
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void decrenment(){
        lock.lock();
        try{
            while (count == 0){
                condition.await();
            }
            count--;
            System.out.println(Thread.currentThread().getName()+ " " + count);
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
} 

输出也没有任何问题。

这里面我们模拟了 3 个生产者和 3 个消费者,进行对一个资源类,其实就是一个数字 count 的操作,并使用 Condition 类来进行唤醒操作。

从目前代码的用法来看, juc 包的 Lock 接口实现类和之前使用 synchronized + Object 类的线程通信方法是一样的,但是并发包的开发工具给了他更多的灵活性。灵活在哪?


三、唤醒特定线程


新技术解决了旧问题,这个方法解决的问题就是,在生产者消费者问题里:有时候我们并不想唤醒所有的对面伙伴,而只想要唤醒特定的一部分,这时候该怎么办呢?

如果没有显式的 lock,我们的思路可能是:

  1. 采用一个标志对象,可以是一个数值或者别的;
  2. 当通信调用 signalAll 的时候,其他线程都去判断这个标志,从而决定自己应不应该工作。

这种实现是可行的,但是本质上其他线程都被唤醒,然后一直阻塞+判断,其实还是在竞争。那么 Condition 类其实就已经提供了对应的方法,来完成这样的操作:

我们看这样一个需求:

  • 同样是多线程操作、需要通信。但是我们要指定各个线程交替的顺序,以及指定唤醒的时候是指定哪个具体线程,这样就不会存在唤醒所有线程然后他们之间互相竞争了。
  • 具体说是:AA 打印 5 次,BB 打印 10 次, CC 打印 15次,然后接着从 AA 开始一轮三个交替

代码如下:

public class TakeTurnPrint {
    public static void main(String[] args) {
        ShareResource resource = new ShareResource();
        new Thread(()->{resource.printA();},"A").start();
        new Thread(()->{resource.printB();},"B").start();
        new Thread(()->{resource.printC();},"C").start();
    }
}

/**
* 资源
*/
class ShareResource{
    private int signal = 0;//0-A,1-B,2-C
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();

    public void printA(){
        lock.lock();
        try{
            while (signal != 0){
                condition.await();//精准
            }
            for (int i = 0; i < 5; i++){
                System.out.println(Thread.currentThread().getName() + " " + i);
            }
            signal = 1;//精准
            condition1.signal();//精准指定下一个
        }catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    
    public void printB(){
        lock.lock();
        try{
            while (signal != 1){
                condition1.await();//精准
            }
            for (int i = 0; i < 10; i++){
                System.out.println(Thread.currentThread().getName() + " " + i);
            }
            signal = 2;//精准
            condition2.signal();//精准指定下一个
        }catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    
    public void printC(){
        lock.lock();
        try{
            while (signal != 2){
                condition2.await();//精准
            }
            for (int i = 0; i < 15; i++){
                System.out.println(Thread.currentThread().getName() + " " + i);
            }
            signal = 0;//精准
            condition.signal();//精准指定下一个
        }catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

其中,使用三个 Condition 对象,用一个 signal 的不同值,来通知不同的线程。

posted @ 2020-10-09 19:38  Life_Goes_On  阅读(298)  评论(0编辑  收藏  举报