美文网首页Java进阶之路
juc包:使用 juc 包下的显式 Lock 实现线程间通信

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

作者: java搬砖从来不加班 | 来源:发表于2021-09-13 13:12 被阅读0次

    一、前置知识

    线程间通信三要素:

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

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

    在 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,我们的思路可能是:

    采用一个标志对象,可以是一个数值或者别的;

    当通信调用 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 的不同值,来通知不同的线程。

    相关文章

      网友评论

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

        本文链接:https://www.haomeiwen.com/subject/jelnwltx.html