java多线程(四)多线程通信 —— Lock +Condition实现生产者模型

1. 回顾与更新

本篇文章着重参考:java多线程(3):Lock接口和Condition监视器接口使用详解 写的非常简洁易懂,我把代码和注释稍作修改,个人觉得理解起来更好一些,但是大部分使用原文语句,跟人感觉已经很易懂了。

上一篇文章使用的synchronized关键字来保证同步时,每个锁对象都有自己的一套监视器方法(wait,notify,notifyAll),这些方法是继承自Object类的。也就是说,锁对象被实例化后,只有一套监视器方法,一个线程要唤醒其他线程只能使用notifyAll方法,也就是说唤醒了其他所有线程,这也是多线程中的效率问题的根源,比如我是生产者我只想唤醒消费在线程,这就无法实现。

JDK5锁方法升级

为此,jdk1.5引入了java.util.concurrent.locks包,并提供了Lock和Condition接口及实现类。下面来看这两个类的文档说明:

  • Lock 实现提供了比使用 synchronized方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象。
  • Condition 将 Object 监视器方法(wait、notify 和notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待set(wait-set)。其中,Lock 替代了synchronized 方法和语句的使用,Condition 替代了 Object监视器方法的使用。

这两个接口将“锁对象”和“监视器对象”分开,这样,一个“锁”就可以关联多个“监视器对象”。也就能实现生产者线程固定唤醒消费者线程,消费者线程固定唤醒生产者线程。

JDK1.5升级后使用lock和unlock锁,显示地来进行加锁和解锁。Lock代替了synchronized,condition使用了一些方法替代了wait、notify、notifyAll 方法的使用:
await()替代wait
signal替代notify
signalAll替代notifyAll
实现了一个类中只唤醒对方的操作,不使用Lock一个锁只能使用wati和notify不能指定唤醒的线程。而使用Lock和condition一个锁可以有多个condition对象使用它可以唤醒指定线程。

2. 生产者消费者模型:使用Lock和Condition来实现

生产者消费者模型:例子说明
我们生产面包,生产1消费1,生产2消费2,也就是说我们的生产和消费要一一对应。并且生产和消费在不同线程执行。
而且每生产一个就要消费一个这样不造成资源的浪费。也就是说生产和消费一一对应。

我们新版本更新代码:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

// 资源类
class Resource {
    private int breadCount = 0;       // 资源编号
    private boolean flag = false;  // 资源类增加一个资源标志位,判断是否有资源

    private Lock lock = new ReentrantLock(); // 定义一个锁对象
    private Condition conProducer = lock.newCondition(); // 获得lock锁的"生产者"线程监视器对象
    private Condition conConsumer = lock.newCondition(); // 获得lock锁的"消费者"线程监视器对象

    // 生产资源
    public void produce() {
        //synchronized (this) {
        lock.lock(); // 线程获取锁
        // 使用lock时,将业务代码要写在try块中,保证异常的出现于捕获
        try {
            // 添加循环判断,如果flag为true,也就是有资源了,生产者线程就暂停生产,进入冻结状态,等待唤醒。
            while (flag == true) {
                try {
                    //this.wait();  // wait函数抛出的异常只能被截获
                    conProducer.await();// 指定让带有该锁的"生产者"线程进入冻结状态,等待

                    // 因为await会让该线程等在这里,如a果这里使用if判断,则当线程被唤醒后会直接往下执行,
                    // 不再进行flag判断了,则由于错误标记执行,这就可能造成多线程死锁。
                    //所以使用while循环判断,让线程再次判断是否标记正确
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            //当可以生产的时候flag=false时,执行以下

            breadCount++;    // 资源编号递增,用来模拟资源递增
            System.out.println(Thread.currentThread().getName() + "...生产者生产bread.." + breadCount);
            //生产完成修改flag
            flag = true;

            //this.notifyAll();
            conConsumer.signal();  // 随机唤醒"消费者"的一个线程
        } finally {//不管是否有异常都要释放锁,所以放在finally中
            lock.unlock();
        }
    }

    // 消费资源
    public void consume() {
        //synchronized (this) {
        lock.lock();
        try {
            //判断如果没有资源则消费者等待
            while (flag == false) {
                try {
                    //this.wait();  // wait函数抛出的异常只能被截获
                    conConsumer.await();
                    // 因为await会让该线程等在这里,如果这里使用if判断,则当线程被唤醒后会直接往下执行,
                    // 不再进行flag判断了,则由于错误标记执行,这就可能造成多线程死锁。
                    //所以使用while循环判断,让线程再次判断是否标记正确
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + "...消费者消费bread......." + breadCount);

            flag = false;
            //随机唤醒Producer的一个线程
            conProducer.signal();
        } finally {
            lock.unlock();
        }
    }
}

// 生产者类线程
class Producer implements Runnable {
    private Resource res;

    //构造函数中生产者初始化分配资源
    public Producer(Resource res) {
        this.res = res;
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            res.produce();     // 每个线程生产5个
        }
    }

}

// 消费者类线程
class Comsumer implements Runnable {
    private Resource res;

    //构造函数中消费者一初始化也要分配资源
    public Comsumer(Resource res) {
        this.res = res;
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            res.consume();  // 每个线程消费5个
        }
    }
}

public class SynchronizedSample {
    public static void main(String[] args) {
        Resource resource = new Resource();  // 实例化资源

        new Thread(new Producer(resource)).start(); // 创建2个生产者线程
        new Thread(new Producer(resource)).start(); // 创建2个生产者线程
        new Thread(new Comsumer(resource)).start(); // 创建2个消费者线程
        new Thread(new Comsumer(resource)).start(); // 创建2个消费者线程
    }
}
/*
Thread-1...生产者生产bread..1
Thread-2...消费者消费bread.......1
Thread-1...生产者生产bread..2
Thread-3...消费者消费bread.......2
Thread-1...生产者生产bread..3
Thread-3...消费者消费bread.......3
Thread-0...生产者生产bread..4
Thread-2...消费者消费bread.......4
Thread-1...生产者生产bread..5
Thread-3...消费者消费bread.......5
Thread-0...生产者生产bread..6
Thread-2...消费者消费bread.......6
Thread-1...生产者生产bread..7
Thread-3...消费者消费bread.......7
Thread-0...生产者生产bread..8
Thread-2...消费者消费bread.......8
Thread-0...生产者生产bread..9
Thread-3...消费者消费bread.......9
Thread-0...生产者生产bread..10
Thread-2...消费者消费bread.......10
 */

代码与之前版本相比,Lock.lock()和Lock.unlock()代替了“synchronized”关键字,而且Lock的使用更加灵活,也更加“面向对象”。Condition.await()/Condition.signal()/Condition.signalAll()方法替代了以前的this.await()/this.signal()/this.signalAll()方法,更重要的是,Lock和Condition分开,而且一个Lock可以绑定多个Condition,这可以唤醒对方线程,从而提高了多线程的执行效率。

3. 简洁一些的写法

package concurrent;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

// 共享资源类,主业务逻辑都写在这里,线程只管执行
class Resource {
    private int breadCount = 0;       // 资源编号
    private Lock lock = new ReentrantLock(); // 定义一个锁对象
    private Condition conditionP = lock.newCondition(); // 不同锁对象,来指定生产消费者线程
    private Condition conditionC = lock.newCondition();

    // 生产资源方法
    public void produce() {

        lock.lock(); // 加锁

        try {//try里是主业务逻辑

            while (breadCount != 0) {//使用while,如果用if则两个线程可以,多个线程就不行了
                //1。 当有面包的时候就等待,不生产
                conditionP.await();// 指定让带有该锁的"生产者"线程进入冻结状态,等待
            }
            //2。 干活,当空的时候开始生产
            breadCount++;
            System.out.println(Thread.currentThread().getName() + "  " + breadCount);
            //3。 通知, 所有消费者线程
            conditionC.signalAll();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {//不管是否有异常都要释放锁,所以放在finally中
            lock.unlock();
        }
    }

    // 消费资源
    public void consume() {
        lock.lock(); // 加锁

        try {//try里是主业务逻辑

            while (breadCount == 0) {//使用while,如果用if则两个线程可以,多个线程就不行了
                //1。 等待
                conditionC.await();
            }
            //2。 干活,
            breadCount--;
            System.out.println(Thread.currentThread().getName() + "  " + breadCount);
            //3。 通知
            conditionP.signalAll();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {//不管是否有异常都要释放锁,所以放在finally中
            lock.unlock();
        }
    }
}

public class LockProducer {
    public static void main(String[] args) {
        Resource resource = new Resource();  // 实例化资源

        new Thread(() -> {
            //执行五次
            for (int i = 1; i <= 5; i++) {
                try {
                    resource.produce();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, "生产者 A ").start();
        new Thread(() -> {
            //执行五次
            for (int i = 1; i <= 5; i++) {
                try {
                    resource.produce();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, "生产者 B ").start();

        new Thread(() -> {
            for (int i = 1; i <= 5; i++) {
                try {
                    resource.consume();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, "消费者 A ").start();
        new Thread(() -> {
            for (int i = 1; i <= 5; i++) {
                try {
                    resource.consume();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, "消费者 B ").start();
    }
}

/*
生产者 A   1
消费者 A   0
生产者 A   1
消费者 A   0
生产者 B   1
消费者 B   0
生产者 B   1
 */

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Charles Ray

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值