4. 线程间的通信

本文探讨了Java中synchronized和LockAPI以及Condition对象在实现线程通信时的使用,特别关注了wait/notify机制和可能遇到的死锁问题,以及如何通过tag和Condition对象实现线程定制化通信,并提到了处理死锁的两种方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

线程通信

synchronized 和Lock 的线程通信

  1. 关键字 synchronized wait()/notify()这两个方法一起使用可以实现等待/通 知模式

    notify()通知时,JVM 会随机唤醒某个等待的线程。注意:如果有多个线程,并且设置了状态锁的时候使用notify()时会造成死锁问题,此时应使用notifyAll()

    - wait()会使当前线程等待,同时会释放锁,直到被唤醒,便从当前位置继续执行。
    - notify()用于随机唤醒一个等待的线程,从被锁主的地方继续执行。
    - notifyAll()用于唤醒全部等待的线程。
    

    下面的代码在使用2个线程以上分别执行product() consume()的时候便可能会进如死锁。

        /**
         *生产
         */
        public synchronized void product() throws InterruptedException {
            //修复虚假通知
            while (productCount != 0) {
                wait();
            }
            //通知消费
            productCount++;
            notify();
        }
    
        /**
         *消费
         */
        public synchronized void consume() throws InterruptedException {
            //修复虚假通知
            while (productCount != 1) {
                wait();
            }
            // 通知生产
            productCount--;
            notify();
        }
    

    这里调用的时候可能会AA线程生产之后notify()的唤醒通知发到了BB线程,而BB线程满足等待条件productCount != 0又会进行等待,没有现成进行消费所以就进入了死锁。解决:将notify()换成notifyAll()即可

    new Thread(()->product(),"AA").start(); // 生产方法
    new Thread(()->product(),"BB").start(); // 生产方法
    new Thread(()->consume()},"CC").start(); // 消费方法
    new Thread(()->consume()},"DD").start(); // 消费方法
    
  2. Lock 锁的 newContition()方法返回 Condition 对象,Condition 类 也可以实现等待/通知模式

    调用 Condition await()/signal()/signalAll()方法前,也需要线程持有相关的 Lock 锁

    - await()会使当前线程等待,同时会释放锁,直到被唤醒,便从当前位置继续执行。
    - signal()用于唤醒一个等待的线程,不是随机。
    - signalAll()用于唤醒全部等待的线程。
    
     	private Lock lock = new ReentrantLock();
        private Condition condition1 = lock.newCondition();
    
        /**
         * 生产
         */
        public void product() throws InterruptedException {
            lock.lock();
            try {
                if (productCount != 0) {
                    //达到仓库上限等待消费处理
                    condition1.await();
                }
                //通知消费
                productCount++;
                condition1.signalAll();
            } finally {
                lock.unlock();
            }
        }
    
        /**
         * 消费
         */
        public void consume() throws InterruptedException {
            lock.lock();
            try {
                if (productCount != 1) {
                    //仓库产品不足等待生产
                    condition1.await();
                }
                // 通知生产
                productCount--;
                condition1.signalAll();
            } finally {
                lock.unlock();
            }
        }
    

    这里的唤醒机制存在虚假唤醒不能保证线程一直让productCount处于1 / 0交替,因为在AA线程进行signalAll()时可能会被BB线程抢到并执行这时候便会进行生产操作,而再次signalAll()时才可能被消费方法抢到并执行。解决:将if()判断换成while()进行循环判断

    new Thread(()->product(),"AA").start(); // 生产方法
    new Thread(()->product(),"BB").start(); // 生产方法
    new Thread(()->consume()},"CC").start(); // 消费方法
    new Thread(()->consume()},"DD").start(); // 消费方法
    

线程定制化通信

线程定制化通信需要添加一个标志位确定需要执行哪一个方法 下列代码中使用了tag作为标志位。

0的时候生产,1的时候consume消费,2的时候conusme1消费。

资源类

    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();

    /**
     * 标志
     */
    private Integer tag = 0;

    /**
     * 生产
     */
    @Override
    public void product() throws InterruptedException {
        lock.lock();
        try {
            while (tag !=0) {
                //达到仓库上限等待消费处理
                condition1.await();
            }
            //通知消费
            productCount += 2;
            tag++;
            System.out.println(Thread.currentThread().getName() + "线程生产产品+++++++++++++++++++++++++++++++++++++++++++" + productCount);
            condition2.signal();
        } finally {
            lock.unlock();
        }
    }

    /**
     * 消费
     */
    @Override
    public void consume() throws InterruptedException {
        lock.lock();
        try {
            while (tag!=1) {
                //仓库产品不足等待生产
                condition2.await();
            }
            // 通知生产
            productCount--;
            tag++;
            System.out.println(Thread.currentThread().getName() + "线程消费产品-----------------------consume--------------------" + productCount);
            condition3.signal();
        } finally {
            lock.unlock();
        }
    }

    public void consume1() throws InterruptedException {
        lock.lock();
        try {
            while (tag!=2) {
                //仓库产品不足等待生产
                condition3.await();
            }
            // 通知生产
            productCount--;
            tag = 0;
            System.out.println(Thread.currentThread().getName() + "线程消费产品-------------------consume1------------------------" + productCount);
            condition1.signal();
        } finally {
            lock.unlock();
        }
    }

执行代码

        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                try {
                    factory.consume();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"BB").start();

        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                try {
                    factory.product();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"AA").start();

        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                try {
                    factory.consume1();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"CC").start();

效果

AA线程生产产品+++++++++++++++++++++++++++++++++++++++++++2
BB线程消费产品-----------------------consume--------------------1
CC线程消费产品-------------------consume1------------------------0
AA线程生产产品+++++++++++++++++++++++++++++++++++++++++++2
BB线程消费产品-----------------------consume--------------------1
CC线程消费产品-------------------consume1------------------------0

隐患

当其中一个定制化通信的方法出现异常或是出现问题无法修改标志位的时候则整个线程就无法进行下去

假设AA线程在执行中无法将tag修改为1时接下来的线程将无法进行下去,将存在死锁现象,其他线程将持续等await()

处理方法1:添加线程等待时间和触发条件进行强制执行(会出意外,不能一定能按照逻辑执行不推荐)

while (tag !=0) {
    // 达到仓库上限等待消费处理,设置超时时间1s 超过等待时间则返回false
    if (!condition1.await(1, TimeUnit.SECONDS)) {
        // 检查是否超时
        // 在超时后检查tag的状态,如果不满足预期,则处理
        if (tag != 0) {
            // 处理不满足预期的情况,比如抛出异常或者打印错误日志
            System.err.println("Product timed out waiting for tag to be 0.");
        }
        // 退出当前方法
        return;
    }
}

处理方法2:添加线程等待时间终止线程

while (tag != 0) {
    // 达到仓库上限等待消费处理,设置超时时间
    if (!condition1.await(1, TimeUnit.SECONDS)) {
        // 处理不满足预期的情况,比如抛出异常或者打印错误日志
        System.err.println("线程" + Thread.currentThread().getName() + "线程嘎调了");
        //终止执行
        Thread.currentThread().interrupt();
    }
}

image-20240315140618380

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值