--多线程中,单生产单消费,多生产多消费的问题
多线程的技术可以解决多部分代码同时运行的问题。这片文章,我们来讨论一下,在多线程里面的单生产,单消费,多生产,多消费的问题。个人由于是初学者,不正确的地方,还望多多指出。
首先,我们来定义我们的需求,我们使用多线程的技术,解决单生成单消费的问题,简单的说就是生产一个资源,就消费一个资源,如果存储的容器里面没有资源,我们就不打印。 我们可以定义两个线程,一个用来生产,一个用来消费,生产一个,就打印一个,消费一个也打印一个。对于资源,我们可以定义一个变量来存储,用name表示资源的名称,num表示资源的数目,每生产一个,就num++。在这个例子中,我们以生产馒头为例子,作为演示:
步骤:
l 我们定义一个resource资源,在里面定义生产资源和消费资源的方法。
l 然后我们再定义生产者Producer的类,消费者Customer的类,并且实现Runnable的接口。在生产者和消费者的类里面,重写run函数
l 创建一个测试函数,创建main函数,new一个资源的对象,创建生产者和消费者的对象,并且创建线程,把任务对象作为作为参数传递给多线程对象。调用start方法。
代码如下:
class Resource { private String name; private int num = 1; public synchronized void addResource(String name){ this.name = name + num; num++; System.out.println(Thread.currentThread().getName()+"生产..........."+this.name); } public synchronized void getResource(){ System.out.println(Thread.currentThread().getName()+"消费..."+name); } } class Producer implements Runnable { private Resource r; Producer(Resource r){ this.r = r; } public void run(){ while (true) { r.addResource("馒头"); } } } class Coustomer implements Runnable { private Resource r; Coustomer(Resource r){ this.r = r; } public void run (){ while (true) { r.getResource(); } } } class Test { public static void main(String[] agrs){ ////创建资源 Resource r = new Resource(); Producer pro = new Producer(r); Coustomer cou = new Coustomer(r); Thread t0 = new Thread(pro); Thread t1 = new Thread(cou); t0.start(); t1.start(); } }
通过运行,运行结果如下:
从运行的结果我们看出问题:
程序中的生产的线程,和消费的线程可以运行。但是出现了生产多次,消费一次,或者生产一次,消费多次的情况。 说白了,由于线程执行切换的随机性,程序不知道什么时候该生产,什么时候该消费。
按照我们的要求,当没有的资源的时候,生产线程执行一次,当有资源的时候,消费的线程执行一次。对于线程的控制,我们可以考虑,用一个布尔类型的标记flag来标记资源,当资源没有的时候,flag为false,这个时候生产线程执行,当生产线程执行完毕,flag标记为true,消费线程执行,之后flag线程再把flag标记为false。 同时我们得注意的是,当线程不执行的时候,要告诉CPU处于等待状态,就是释放CPU的执行权和执行资格,然后通知其他线程去执行。对于等待线程,我们可以调用线程中的wait方法,通知其他方法,我们可以调用notify方法。 Synchronized部分的代码修改如下:
public synchronized void addResource(String name){ while (flag) { try{this.wait();}catch(InterruptedException e){} } this.name = name + num; num++; System.out.println(Thread.currentThread().getName()+"生产..........."+this.name); flag = true; this.notify();///通知其他休眠的一个线程 } public synchronized void getResource(){ while (!flag) { try{this.wait();}catch(InterruptedException e){} } System.out.println(Thread.currentThread().getName()+"消费..."+name); flag = false; this.notify();///通知其他休眠的一个线程 }
我们运行一下,结果如下:
从运行的结果,我们可以看出,这部分以及完成了我们所需要的功能。 每当生产一个馒头,就会被消费一个馒头。 这就是单生成单消费的问题。那么我们可以不可以再提另外一个要求呢?就是有两个线程在生产,同时也有两个线程在消费,在生产的过程中,任意一个生产线程在生产,消费过程中,任意一个消费线程在消费。 这就是多生产多消费的问题了。增加代码如下:
Producer pro = new Producer(r); Coustomer cou = new Coustomer(r); Thread t0 = new Thread(pro);/////生产线程 Thread t1 = new Thread(pro);/////生产线程 Thread t2 = new Thread(cou);/////消费线程 Thread t3 = new Thread(cou);/////消费线程 t0.start(); t1.start(); t2.start(); t3.start();
首先我们来分析一下:
有两个生产线程,比如Thread-0 和 Thread-1,消费线程也有两个 Thread-2 和Thread-3。 当Thread-0在执行生产的过程中,Thread-1,Thread-2,Thread-3都处于休眠的状态。 当Thread-0执行完毕,这时flag为True状态,我们可以用notifyAll()来唤醒其他都在休眠的线程。如果Thread-1 被唤醒,一判断标记为True则继续休眠,而Thread-2和Thread-3任意一个线程被唤醒,一判断flag为True,则执行消费。
所以在源代码里面,我们把this.notify()改为this.notifyAll(). 再来运行一下程序看看:
从运行的结果来看,我们解决了多线程的问题,实现了多生产多消费的问题。但是从代码的分析中,我发现,是不是唤醒代码那一部分通知CPU切换是不是有点低效。因为我们用的是notifyAll,这样唤醒了消费者线程的同时,也唤醒了一个生产者的线程。这样就降低了效率。对于这个问题,我们怎么解决呢?
在JDK升级时,提供了解决的方案,优化了同步和锁。java.util.concurrent.locks包中提供了Lock接口。synchronized对锁的操作都是隐式的,无论是拿到锁,还是放掉锁,都是隐式的。Lock接口中的代码范例:提示了 lock()获取锁 unlock()释放锁; Lock提供了显示的锁操作。另外,Lock接口的出现比同步不仅仅是提供了显示的锁操作,而且更加的面向对象。以前的锁是任意对象,现在的锁被封装成单独的对象,并将对锁的操作封装到了锁对象。在之前,同步的锁和监视器是绑定的,也就是说之前的wait和notify notifyAll方法需要和同步的锁绑定。那么新锁Lock它有没有绑定的用于操作锁上线程的方法呢?我们在,阅读Lock接口文档,Condition接口,这个接口替代了Object中的监视器(锁)方法。同步synchronized和任意锁对象组合,通过同步操作任意对象锁的获取和释放。并通过Object中的wait notify notifyAll方法对同步中的锁上的线程进行状态的操作。synchronized+任意对象锁+Objedt(wait notify notifyAll)。现在,用Lock替代了同步,用Condition替代了Object(wait notify notifyAll)。 按照这个升级后的方法,我们可以分析下:
将程序中的wait notifyAll notify 替换成Condition的监视器方法await signal signalAll。先有condition对象,必须要和Lock相关。通过condition文档描述发现了Lock接口有一个newCondition方法可以获取到和当前锁相关的condition对象。
对于本程序,我们可以把源代码改为如下:
import java.util.concurrent.locks.*; class Resource { private String name; private int num = 1; private boolean flag = false;////定义标记 private Lock lock = new ReentrantLock();/////定义一个单独的Lock对象 private final Condition pro = lock.newCondition();////将监视器绑定到lock上面 private final Condition cou = lock.newCondition(); public void addResource(String name){ lock.lock();////标记锁 try{ while (flag) { try{pro.await();}catch(InterruptedException e){} } this.name = name + num; num++; System.out.println(Thread.currentThread().getName()+"生产..........."+this.name); flag = true; cou.signal(); }finally{ /////释放锁 lock.unlock(); } } public void getResource(){ lock.lock(); try{ while (!flag) { try{cou.await();}catch(InterruptedException e){} } System.out.println(Thread.currentThread().getName()+"消费..."+name); flag = false; pro.signal(); //lock.unlock(); }finally{lock.unlock();} } } class Producer implements Runnable { private Resource r; Producer(Resource r){ this.r = r; } public void run(){ while (true) { r.addResource("馒头"); } } } class Coustomer implements Runnable { private Resource r; Coustomer(Resource r){ this.r = r; } public void run (){ while (true) { r.getResource(); } } } class Demo2 { public static void main(String[] agrs){ ////创建资源 Resource r = new Resource(); /////创建任务 Producer pro = new Producer(r); Coustomer cou = new Coustomer(r); //////创建线程并开启 Thread t0 = new Thread(pro); Thread t1 = new Thread(pro); Thread t2 = new Thread(cou); Thread t3 = new Thread(cou); t0.start(); t1.start(); t2.start(); t3.start(); } }
运行一下,结果如下:
使用Lock接口,使锁与监视器分开了,可以达到,唤醒或者是等待指定的线程,大大地提高了CPU运行的效率。