多线程间通信——生产者消费者问题
生产和消费同时进行,需要多线程。但是执行的任务不相同,处理的资源却是相同的:线程间的通信。
1、存在的问题及解决
期望生产一个商品就被消费掉,再生产下一个商品。
1 /** 2 * 案例:期望生产一个商品就被消费掉,再生产下一个商品。 3 */ 4 //1、描述资源 5 class Resource{ 6 private String name; 7 private int count=1; 8 public Object lock=new Object(); 9 public void set(String name) throws InterruptedException { 10 //给成员变量赋值并加上编号 11 this.name = name+count; 12 //编号自增 13 count++; 14 //打印生产了哪个商品 15 Thread.sleep(500); 16 System.out.println(Thread.currentThread().getName()+"...生产者.."+this.name); 17 } 18 19 public void out() { 20 System.out.println(Thread.currentThread().getName()+"...消费者.."+this.name); 21 } 22 } 23 24 //2、描述生产者 25 class Producer implements Runnable{ 26 private Resource r; 27 public Producer(Resource r){ 28 this.r=r; 29 } 30 @Override 31 public void run() { 32 while(true) { 33 try { 34 r.set("面包"); 35 } catch (InterruptedException e) { 36 // TODO Auto-generated catch block 37 e.printStackTrace(); 38 } 39 } 40 } 41 } 42 //3、描述消费者 43 class Consumer implements Runnable{ 44 private Resource r; 45 public Consumer(Resource r){ 46 this.r=r; 47 } 48 @Override 49 public void run() { 50 while(true) { 51 r.out(); 52 } 53 } 54 } 55 56 public class ThreadMain { 57 public static void main(String[] args) { 58 //1、创建资源对象 59 Resource r=new Resource(); 60 //2、创建线程任务 61 Producer p=new Producer(r); 62 Consumer c=new Consumer(r); 63 //3、创建线程对象 64 Thread t1=new Thread(p); 65 Thread t2=new Thread(c); 66 67 t1.start(); 68 t2.start(); 69 } 70 }
出现的问题;
问题一:消费早期商品。使用同步函数:将set方法和out方法加上synchronized关键字,问题解决。
1 /** 2 * 案例:期望生产一个商品就被消费掉,再生产下一个商品。 3 * 问题一:消费早期商品。使用同步函数:将set方法和out方法加上synchronized关键字,问题解决。 4 * 问题二:同一个商品重复消费,疯狂生产和疯狂消费。 5 * 6 */ 7 //1、描述资源 8 class Resource{ 9 private String name; 10 private int count=1; 11 public Object lock=new Object(); 12 public synchronized void set(String name) throws InterruptedException { 13 //给成员变量赋值并加上编号 14 this.name = name+count; 15 //编号自增 16 count++; 17 //打印生产了哪个商品 18 Thread.sleep(500); 19 System.out.println(Thread.currentThread().getName()+"...生产者.."+this.name); 20 } 21 22 public synchronized void out() { 23 System.out.println(Thread.currentThread().getName()+"...消费者.."+this.name); 24 } 25 } 26 27 //2、描述生产者 28 class Producer implements Runnable{ 29 private Resource r; 30 public Producer(Resource r){ 31 this.r=r; 32 } 33 @Override 34 public void run() { 35 while(true) { 36 try { 37 r.set("面包"); 38 } catch (InterruptedException e) { 39 // TODO Auto-generated catch block 40 e.printStackTrace(); 41 } 42 } 43 } 44 } 45 //3、描述消费者 46 class Consumer implements Runnable{ 47 private Resource r; 48 public Consumer(Resource r){ 49 this.r=r; 50 } 51 @Override 52 public void run() { 53 while(true) { 54 r.out(); 55 } 56 } 57 } 58 59 public class ThreadMain { 60 public static void main(String[] args) { 61 //1、创建资源对象 62 Resource r=new Resource(); 63 //2、创建线程任务 64 Producer p=new Producer(r); 65 Consumer c=new Consumer(r); 66 //3、创建线程对象 67 Thread t1=new Thread(p); 68 Thread t2=new Thread(c); 69 70 t1.start(); 71 t2.start(); 72 } 73 }
问题二:同一个商品重复消费,疯狂生产和疯狂消费。
分析:生产者什么时候生产呢?当容器中没有面包时,就生产,否则不生产;
消费者什么时候消费呢?当容器中有面包时,就消费,否则不消费。
生产者生产了商品后,通知消费者来消费,这时的生产者应该处于等待状态;
消费者消费了商品后,通知生产者生产,这时消费者应该处于等待状态。
等待:wait(); 通知:notify();
以下代码中标红的为增加修改的部分。
1 /** 2 * 案例:期望生产一个商品就被消费掉,再生产下一个商品。 3 */ 4 //1、描述资源 5 class Resource{ 6 private String name; 7 private int count=1; 8 //定义标记 9 private boolean flag=false; 10 public synchronized void set(String name) { 11 if(flag) { 12 try {wait();} catch (InterruptedException e) {} 13 } 14 //给成员变量赋值并加上编号 15 this.name = name+count; 16 //编号自增 17 count++; 18 //打印生产了哪个商品 19 System.out.println(Thread.currentThread().getName()+"...生产者.."+this.name); 20 flag=true; 21 //唤醒消费者 22 notify(); 23 } 24 25 public synchronized void out() { 26 if(!flag) { 27 try {wait();} catch (InterruptedException e) {} 28 } 29 System.out.println(Thread.currentThread().getName()+"...消费者.."+this.name); 30 flag=false; 31 //唤醒生产者 32 notify(); 33 } 34 } 35 36 //2、描述生产者 37 class Producer implements Runnable{ 38 private Resource r; 39 public Producer(Resource r){ 40 this.r=r; 41 } 42 @Override 43 public void run() { 44 while(true) { 45 r.set("面包"); 46 } 47 } 48 } 49 //3、描述消费者 50 class Consumer implements Runnable{ 51 private Resource r; 52 public Consumer(Resource r){ 53 this.r=r; 54 } 55 @Override 56 public void run() { 57 while(true) { 58 r.out(); 59 } 60 } 61 } 62 63 public class ThreadMain2 { 64 public static void main(String[] args) { 65 //1、创建资源对象 66 Resource r=new Resource(); 67 //2、创建线程任务 68 Producer p=new Producer(r); 69 Consumer c=new Consumer(r); 70 //3、创建线程对象 71 Thread t1=new Thread(p); 72 Thread t2=new Thread(c); 73 74 t1.start(); 75 t2.start(); 76 } 77 }
2、等待唤醒机制
wait(): 会让线程处于等待状态,其实就是将线程临时存储到了线程池中。
当前线程必须拥有此对象的监视器(锁),否则抛出java.lang.IllegalMonitorStateException
notify(): 会唤醒线程池中任意一个等待的线程。
notifyAll(): 会唤醒线程池中所有的等待的线程。
这些方法必须使用在同步中,因为必须要标识wait、notify等方法所属的锁。同一个锁上的notify,只能唤醒改锁上wait的线程。默认是this.wait();this.notify();this.notifyAll()。
为什么这些方法定义在Object类中,而不是Thread类中呢?
因为这些方法必须标识所属的锁,而锁可以是任意对象,任意对象可以调用的方法必然是Object类中的方法。
3、多生产多消费问题及解决方案
1 /** 2 * 案例:多生产多消费问题 3 * 问题一:生产了商品没有被消费,同一个商品被消费多次 4 * 产生问题原因:被唤醒的线程没有判断标记,造成问题一的产生。 5 * 解决:将if判断改为while.只要是多生产,必须是while判断条件。 6 * 7 * 问题二:while判断后死锁 8 * 原因:生产方唤醒了生产方的线程。本方唤醒了本方。 9 * 解决方法:希望本方唤醒对方。没有对应方法,则唤醒所有。 10 * 但是唤醒所有效率有点低。 11 */ 12 13 //1、描述资源 14 class Resource{ 15 private String name; 16 private int count=1; 17 //定义标记 18 private boolean flag=false; 19 public synchronized void set(String name) {//t1,t2 20 while(flag) { 21 try {wait();} catch (InterruptedException e) {} 22 } 23 //给成员变量赋值并加上编号 24 this.name = name+count; 25 //编号自增 26 count++; 27 //打印生产了哪个商品 28 System.out.println(Thread.currentThread().getName()+"...生产者.."+this.name); 29 flag=true; 30 //唤醒消费者 31 notifyAll(); 32 } 33 34 public synchronized void out() {//t3,t4 35 while(!flag) { 36 try {wait();} catch (InterruptedException e) {} 37 } 38 System.out.println(Thread.currentThread().getName()+"...消费者.."+this.name); 39 flag=false; 40 //唤醒生产者 41 notifyAll(); 42 } 43 } 44 45 //2、描述生产者 46 class Producer implements Runnable{ 47 private Resource r; 48 public Producer(Resource r){ 49 this.r=r; 50 } 51 @Override 52 public void run() { 53 while(true) { 54 r.set("面包"); 55 } 56 } 57 } 58 //3、描述消费者 59 class Consumer implements Runnable{ 60 private Resource r; 61 public Consumer(Resource r){ 62 this.r=r; 63 } 64 @Override 65 public void run() { 66 while(true) { 67 r.out(); 68 } 69 } 70 } 71 72 public class ThreadMain3 { 73 public static void main(String[] args) { 74 //1、创建资源对象 75 Resource r=new Resource(); 76 //2、创建线程任务 77 Producer p=new Producer(r); 78 Consumer c=new Consumer(r); 79 //3、创建线程对象 80 Thread t1=new Thread(p); 81 Thread t2=new Thread(c); 82 Thread t3=new Thread(p); 83 Thread t4=new Thread(c); 84 85 t1.start(); 86 t2.start(); 87 t3.start(); 88 t4.start(); 89 } 90 }
4、多线程间通信-jdk1.5-Lock接口
jdk1.5以后提供多生产多消费的解决方案。在java.util.concurrent.locks软件包中提供了解决方案。
Lock接口:Lock
实现提供了比使用 synchronized
方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition
对象。
随着灵活性的增加,也带来了更多的责任。不使用块结构锁就失去了使用 synchronized
方法和语句时会出现的锁自动释放功能。在大多数情况下,应该使用以下语句:
Lock l = ...; l.lock(); try { // access the resource protected by this lock } finally { l.unlock(); }Lock
方法:
void lock() |
获取锁。 |
void lockInterruptibly() |
如果当前线程未被中断,则获取锁。 |
Condition newCondition() | 返回绑定到此 Lock 实例的新 Condition 实例。 |
boolean tryLock() | 仅在调用时锁为空闲状态才获取该锁。 |
boolean tryLock(long time, TimeUnit unit) |
如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁。 |
void unlock() |
释放锁 |
多生产多消费的效率问题解决:
1 import java.util.concurrent.locks.Condition; 2 import java.util.concurrent.locks.Lock; 3 import java.util.concurrent.locks.ReentrantLock; 4 5 /** 6 jdk1.5将原有的监视器方法wait(),notify(),notifyAll封装到Condition对象中。 7 Condition对象的出现其实是替代了Object中的监视器方法。 8 await(); signal(); signalAll(); 9 旧版中唤醒所有的方法效率低。 10 jdk1.5以后,可以在一个lock锁上加上多个监视器对象。 11 */ 12 13 //1、描述资源 14 class Resource{ 15 private String name; 16 private int count=1; 17 //定义一个锁对象 18 private Lock lock=new ReentrantLock(); 19 //获取锁上的Condition对象,为了解决本方唤醒对象问题,可以一个锁创建两个监视器对象。 20 private Condition consu=lock.newCondition();//获取lock上的监视器方法,负责消费 21 private Condition produ=lock.newCondition();//获取lock上的监视器方法,负责生产 22 23 //定义标记 24 private boolean flag=false; 25 26 public void set(String name) {//t1,t2 27 //获取锁 28 lock.lock(); 29 try { 30 while(flag) { 31 try {produ.await();} catch (InterruptedException e) {} 32 } 33 //给成员变量赋值并加上编号 34 this.name = name+count; 35 //编号自增 36 count++; 37 //打印生产了哪个商品 38 System.out.println(Thread.currentThread().getName()+"...生产者.."+this.name); 39 flag=true; 40 //唤醒消费者 41 consu.signal(); 42 }finally { 43 lock.unlock();//一定要执行 44 } 45 46 } 47 48 public void out() {//t3,t4 49 //获取锁 50 lock.lock(); 51 try { 52 while(!flag) { 53 try {consu.await();} catch (InterruptedException e) {} 54 } 55 System.out.println(Thread.currentThread().getName()+"...消费者.."+this.name); 56 flag=false; 57 //唤醒生产者 58 produ.signal(); 59 }finally{ 60 lock.unlock();//一定要执行 61 } 62 } 63 } 64 65 //2、描述生产者 66 class Producer implements Runnable{ 67 private Resource r; 68 public Producer(Resource r){ 69 this.r=r; 70 } 71 @Override 72 public void run() { 73 while(true) { 74 r.set("面包"); 75 } 76 } 77 } 78 //3、描述消费者 79 class Consumer implements Runnable{ 80 private Resource r; 81 public Consumer(Resource r){ 82 this.r=r; 83 } 84 @Override 85 public void run() { 86 while(true) { 87 r.out(); 88 } 89 } 90 } 91 92 public class ThreadMain4 { 93 public static void main(String[] args) { 94 //1、创建资源对象 95 Resource r=new Resource(); 96 //2、创建线程任务 97 Producer p=new Producer(r); 98 Consumer c=new Consumer(r); 99 //3、创建线程对象 100 Thread t1=new Thread(p); 101 Thread t2=new Thread(p); 102 Thread t3=new Thread(c); 103 Thread t4=new Thread(c); 104 105 t1.start(); 106 t2.start(); 107 t3.start(); 108 t4.start(); 109 } 110 }