--多线程中,单生产单消费,多生产多消费的问题

 

多线程的技术可以解决多部分代码同时运行的问题。这片文章,我们来讨论一下,在多线程里面的单生产,单消费,多生产,多消费的问题。个人由于是初学者,不正确的地方,还望多多指出。

 

首先,我们来定义我们的需求,我们使用多线程的技术,解决单生成单消费的问题,简单的说就是生产一个资源,就消费一个资源,如果存储的容器里面没有资源,我们就不打印。 我们可以定义两个线程,一个用来生产,一个用来消费,生产一个,就打印一个,消费一个也打印一个。对于资源,我们可以定义一个变量来存储,用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运行的效率。

 

posted @ 2013-12-13 19:13  lee笔记  阅读(614)  评论(0编辑  收藏  举报