JAVA--多线程生产者消费者问题详解
2016-07-18 13:48
465 查看
最近学到多线程了,之前在网上看了很多博客,解释的都不怎么详细,对于新手来说确实比较难懂,所以我就来分享一下自己的理解,希望能够帮助到需要的人。
首先,来解释一下线程间的通信:就是多个线程执行的任务不同,但是操作的数据相同。我们需要线程同步的执行,这时要用到同一把锁,锁可以是任意的对象,这就是wait(),notify(),notifyAll()方法在Object里而不是在Thread里的原因了,这些方法必须用在同步中,因为同步中才有锁。在这里,资源类的对象就可以作为锁。不多说,先看看单个生产者和单个消费者的代码。
this.wait() 指明持有哪个锁的线程去等待,让线程等待(会放弃锁),把线程放入了线程池.
this.notify() 唤醒线程池中的任意一个线程
这里用到的是synchronized来进行同步,当标志位为true时,等待并释放锁;当标志位为false时生产者生产一个,标志位置为true,唤醒消费者线程。
生产者1:抢到CPU资源,判断flag为false,生产一个,标志位置为true,空唤醒,再判断,为true,进入等待状态
生产者2:抢到CPU资源,进入,发现flag已经被设为true了,进入等待状态。
消费者1:抢到CPU资源,判断flag为true,消费一个,标记为置flase,如果唤醒的是生产者1。生产者一判断为false,生产一个,标记位置为true。
这时,如果唤醒的如果是生产者2,生产者2不会再判断,直接执行下面的代码,这时就会出现生产两个,消费一个的情况。
·创建一把锁,也就是创建Lokc的子类对象
·把需要同步的代码放在lock()和unlock()之间
·Lock lock = new ReentrantLock();
Condition condition = lock.newCondition()
获取和锁绑定的condition对象
Condition中的await()等待,signal()唤醒线程池中任意一个,有JVM决定,signalAll()唤醒线程池中所有线程。
注:后两个代码只是部分,其他代码参考第一个。
多个生产者和多个消费者–生产和消费多个与生产和消费单个并没有什么区别,只是加了个容器–数组,将生产的产品存起来,而不是生产一个消费一个。把判断条件改变下就行了,容器满了,生产者不能生产;容器空了,消费者不能消费,仅此而已。
首先,来解释一下线程间的通信:就是多个线程执行的任务不同,但是操作的数据相同。我们需要线程同步的执行,这时要用到同一把锁,锁可以是任意的对象,这就是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(); } }
多个生产者和多个消费者–生产和消费多个与生产和消费单个并没有什么区别,只是加了个容器–数组,将生产的产品存起来,而不是生产一个消费一个。把判断条件改变下就行了,容器满了,生产者不能生产;容器空了,消费者不能消费,仅此而已。
相关文章推荐
- Python3写爬虫(四)多线程实现数据爬取
- C#实现多线程的同步方法实例分析
- C#实现子窗体与父窗体通信方法实例总结
- 浅谈chuck-lua中的多线程
- C#简单多线程同步和优先权用法实例
- C#多线程学习之(四)使用线程池进行多线程的自动管理
- C#多线程编程中的锁系统(三)
- 解析C#多线程编程中异步多线程的实现及线程池的使用
- C#多线程学习之(六)互斥对象用法实例
- 基于一个应用程序多线程误用的分析详解
- C#多线程学习之(三)生产者和消费者用法分析
- C#多线程学习之(一)多线程的相关概念分析
- C#多线程之Thread中Thread.IsAlive属性用法分析
- 分享我在工作中遇到的多线程下导致RCW无法释放的问题
- C#多线程编程之使用ReaderWriterLock类实现多用户读与单用户写同步的方法
- C#多线程传递参数及任务用法示例
- C#控制台下测试多线程的方法
- 21天学习android开发教程之SurfaceView与多线程的混搭
- Ruby 多线程的潜力和弱点分析
- java和c#使用hessian通信的方法