JAVA--多线程生产者消费者问题详解

最近学到多线程了,之前在网上看了很多博客,解释的都不怎么详细,对于新手来说确实比较难懂,所以我就来分享一下自己的理解,希望能够帮助到需要的人。
首先,来解释一下线程间的通信:就是多个线程执行的任务不同,但是操作的数据相同。我们需要线程同步的执行,这时要用到同一把锁,锁可以是任意的对象,这就是wait(),notify(),notifyAll()方法在Object里而不是在Thread里的原因了,这些方法必须用在同步中,因为同步中才有锁。在这里,资源类的对象就可以作为锁。不多说,先看看单个生产者和单个消费者的代码。

一.单个生产者和单个消费者


class Product
{
    private String name;
    private int count;
    private boolean flag; //标志
    //生产功能
    public synchronized void product(String name){
        if(flag)
                try{this.wait();}catch(InterruptedException e){e.printStackTrace();}
        this.name = name+"...."+count;
            System.out.println(Thread.currentThread().getName()+"生产了"+this.name);
        count++;
        flag = true;
        this.notify();
    }
    //消费功能
    public synchronized void consume(){
        if(!flag)
            try{this.wait();}catch(InterruptedException e){e.printStackTrace();}
        System.out.println(Thread.currentThread().getName()+"消费了"+name);
        flag = false;
        this.notify();
    }
}
//生产者类
class Productor implements Runnable
{
    private Product product;
    Productor(Product product){
        this.product = product;
    }
    public void run(){
            while(true){
                product.product("笔记本电脑");
            }
    }

}
//消费者类
class Consumer implements Runnable
{
    private Product product;
    Consumer(Product product){
        this.product = product;
    }
    public void run(){
            while(true){
                product.consume();
            }
    }
}
class Pro_Con
{
    public static void main(String[] args) 
    {
        Product product = new Product();
        Productor productor = new Productor(product);
        Consumer consumer = new Consumer(product);
        new Thread(productor).start();//一个生产线程
        new Thread(consumer).start();//一个消费线程
    }
}

this.wait() 指明持有哪个锁的线程去等待,让线程等待(会放弃锁),把线程放入了线程池.
this.notify() 唤醒线程池中的任意一个线程

这里用到的是synchronized来进行同步,当标志位为true时,等待并释放锁;当标志位为false时生产者生产一个,标志位置为true,唤醒消费者线程。

二.多个生产者和多个消费者(PS:生产单个,消费单个)

当有多个生产者和消费者时,我们上面的代码就不安全了,会发生生产者生产了两个,消费者消费一个或生产一个,消费两个的情况。我来简单分析下:
生产者1:抢到CPU资源,判断flag为false,生产一个,标志位置为true,空唤醒,再判断,为true,进入等待状态
生产者2:抢到CPU资源,进入,发现flag已经被设为true了,进入等待状态。
消费者1:抢到CPU资源,判断flag为true,消费一个,标记为置flase,如果唤醒的是生产者1。生产者一判断为false,生产一个,标记位置为true。

这时,如果唤醒的如果是生产者2,生产者2不会再判断,直接执行下面的代码,这时就会出现生产两个,消费一个的情况。

那该怎么解决呢???

要让它重新判断,把判断语句if(),改为while()就行了 ,但是会产生死锁,原因是当唤醒的是本方线程时会导致所有线程都等待。因为notify唤醒的是任意一个线程,不能保证唤醒的是对方线程,所以使用notifyAll()唤醒所有线程。
//生产功能
public synchronized void product(String name){
        while(flag)//此处
            try{this.wait();}catch(InterruptedException e){e.printStackTrace();}
        this.name = name+"...."+count;
        System.out.println(Thread.currentThread().getName()+"生产了"+this.name);
        count++;
        flag = true;
        this.notifyAll();//此处
    }

//消费功能
    public synchronized void consume(){
        while(!flag) //此处
            try{this.wait();}catch(InterruptedException e){e.printStackTrace();}
        System.out.println(Thread.currentThread().getName()+"消费了"+name);
        flag = false;
        this.notifyAll();//此处
    }

三.使用Lock和Condition接口替代synchronized(优化程序性能)

上一个例子使用了notifyAll()方法解决了死锁的问题,但是每次都唤醒所有的线程会使程序的性能降低。JDK1.5之后对锁进行了单独描述,使用的接口Lock替代synchronized的方式:
·创建一把锁,也就是创建Lokc的子类对象
·把需要同步的代码放在lock()和unlock()之间
·Lock lock = new ReentrantLock();
Condition condition = lock.newCondition()
获取和锁绑定的condition对象

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

class Product
{
    private String name;
    private int count;
    private boolean flag;
    //创建一个锁对象
    private Lock lock = new ReentrantLock();
    //获取和锁绑定的生产者condition对象
    private Condition pro = lock.newCondition();
    //获取和锁绑定的消费者condition对象
    private Condition con = lock.newCondition();

    //生产功能,注意:synchronized已经被替代了
    public void product(String name){
        lock.lock(); //获取锁
        try{
            while(flag)
                try{pro.await();}catch(InterruptedException e){e.printStackTrace();}
            this.name = name+"...."+count;
            System.out.println(Thread.currentThread().getName()+"生产了"+this.name);
            count++;
            flag = true;
            con.signal();
        }finally{
            lock.unlock();//释放锁,因为要释放锁,所以放在finally中
        }
    }

    //消费功能
    public void consume(){
        lock.lock();
        try{
            while(!flag)
                try{con.await();}catch(InterruptedException e){e.printStackTrace();}
            System.out.println(Thread.currentThread().getName()+"消费了"+name);
            flag = false;
            pro.signal();
        }finally{
            lock.unlock();
        }
    }
}

Condition中的await()等待,signal()唤醒线程池中任意一个,有JVM决定,signalAll()唤醒线程池中所有线程。
注:后两个代码只是部分,其他代码参考第一个。

四.多个生产者和多个消费者(PS:生产多个,消费多个)

//使用 jdk1.5的Lock,Condition实现可以同时生产多个,消费多个的功能
import java.util.concurrent.locks.*;
class Clothes
{
    //产品的名称
    private String name;
    //产品的价格
    private double price;
    //存储产品的容器
    private Clothes[] arr = new Clothes[100];
    //定义一把锁
    private Lock lock = new ReentrantLock();
    //操作生产线程的Condition对象
    private Condition pro = lock.newCondition();
    //操作消费线程的Condition对象
    private Condition con = lock.newCondition();
    //生产线程和消费线程使用的下标以及产品数量
    private int proindex,conindex,count;

    public Clothes(){}
    public Clothes(String name,double price)
    {
        this.name = name;
        this.price = price;
    }
    public String toString()
    {
        return name+","+price;
    }
    //生产功能
    public void produce()
    {
          lock.lock();
          try
          {
            //当容器满的时候就不能生产
              while(arr.length==count)
              {
                  try{pro.await();}catch(InterruptedException e){e.printStackTrace();}
              }
              arr[proindex] = new Clothes("衬衫",9.99);
              System.out.println(Thread.currentThread().getName()+"生产了..."+arr[proindex]);
              //判断下标加1后是否和数组长度相同,相同则置为0
              if(++proindex==arr.length)
                  proindex = 0;
              //数量加1
              count++;
              con.signal();//唤醒一个消费线程
          }
          finally
          {
             lock.unlock();
          }
    }
   //消费功能
   public void consume()
    {
         lock.lock();
         try
         {
            //当count为0的时候不能消费
            while(count==0)
                 try{con.await();}catch(InterruptedException e){e.printStackTrace();}
            //消费一件产品
            Clothes yifu = arr[conindex];
            System.out.println(Thread.currentThread().getName()+"消费了........"+yifu);
            if(++conindex==arr.length)
                conindex = 0;
            //数量减1
            count--;
            pro.signal();

         }
         finally
         {
             lock.unlock();
         }
    }
}
//生产任务
class Producer implements Runnable
{
    private  Clothes clo;
    public Producer(Clothes clo)
    {
        this.clo = clo;
    }
    public void run()
    {
        while(true)
        {
            clo.produce();
        }
    }
}
//消费任务
class Consumer implements Runnable
{
    private  Clothes clo;
    public Consumer(Clothes clo)
    {
        this.clo = clo;
    }
    public void run()
    {
         while(true)
         {
            clo.consume();
         }
    }
}
class Demo7 
{
    public static void main(String[] args) 
    {
        Clothes clo = new Clothes();

        Producer producer = new Producer(clo);
        Consumer consumer = new Consumer(clo);

        Thread t0 = new Thread(producer);
        Thread t1 = new Thread(producer);

        Thread t2 = new Thread(consumer);
        Thread t3 = new Thread(consumer);

        t0.start();
        t1.start();
        t2.start();
        t3.start();

    }
}

多个生产者和多个消费者–生产和消费多个与生产和消费单个并没有什么区别,只是加了个容器–数组,将生产的产品存起来,而不是生产一个消费一个。把判断条件改变下就行了,容器满了,生产者不能生产;容器空了,消费者不能消费,仅此而已。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值