多线程——生产者和消费者
Posted Snny Bill
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程——生产者和消费者相关的知识,希望对你有一定的参考价值。
通过前面三篇博客的介绍,基本上对Java的多线程有了一定的了解了,然后这篇博客根据生产者和消费者的模型来介绍Java多线程的一些其他知识。
我们这里的生产者和消费者模型为:
生产者Producer 生产某个对象(共享资源),放在缓冲池中,然后消费者从缓冲池中取出这个对象。也就是生产者生产一个,消费者取出一个。这样进行循环。
第一步:我们先创建共享资源的类 Person,它有两个方法,一个生产对象,一个消费对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
public class Person { private String name; private int age; /** * 生产数据 * @param name * @param age */ public void push(String name, int age){ this .name = name; this .age = age; } /** * 取数据,消费数据 * @return */ public void pop(){ System.out.println( this .name+ "---" + this .age); } } |
第二步:创建生产者线程,并在 run() 方法中生产50个对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public class Producer implements Runnable{ //共享资源对象 Person p = null ; public Producer(Person p){ this .p = p; } @Override public void run() { //生产对象 for ( int i = 0 ; i < 50 ; i++){ //如果是偶数,那么生产对象 Tom--11;如果是奇数,则生产对象 Marry--21 if (i% 2 == 0 ){ p.push( "Tom" , 11 ); } else { p.push( "Marry" , 21 ); } } } } |
第三步:创建消费者线程,并在 run() 方法中消费50个对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public class Consumer implements Runnable{ //共享资源对象 Person p = null ; public Consumer(Person p) { this .p = p; } @Override public void run() { for ( int i = 0 ; i < 50 ; i++){ //消费对象 p.pop(); } } } |
由于我们的模型是生产一个,马上消费一个,那期望的结果便是 Tom---11,Marry--21,Tom---11,Mary---21...... 连续这样交替出现50次
但是结果却是:
1
2
3
4
5
6
7
8
9
10
11
|
Marry--- 21 Marry--- 21 Marry--- 21 Marry--- 21 Marry--- 21 ...... Marry--- 21 Marry--- 21 Marry--- 21 Marry--- 21 Marry--- 21 |
为了让结果产生的更加明显,我们在共享资源的 pop() 和 push() 方法中添加一段延时代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
/** * 生产数据 * @param name * @param age */ public void push(String name, int age){ this .name = name; try { //这段延时代码的作用是可能只生产了 name,age为nul,消费者就拿去消费了 Thread.sleep( 10 ); } catch (InterruptedException e) { e.printStackTrace(); } this .age = age; } /** * 取数据,消费数据 * @return */ public void pop(){ try { Thread.sleep( 10 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println( this .name+ "---" + this .age); } |
这个时候,结果如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
Marry--- 11 Tom--- 21 Marry--- 11 Tom--- 21 Marry--- 11 Tom--- 21 Marry--- 11 Tom--- 21 ...... Tom--- 11 Tom--- 21 Marry--- 11 Tom--- 21 Marry--- 11 Marry--- 21 |
结果分析:这时候我们发现结果全乱套了,Marry--21是固定的,Tom--11是固定的,但是上面的结果全部乱了,那这又是为什么呢?而且有很多重复的数据连续出现,那这又是为什么呢?
原因1:出现错乱数据,是因为先生产出Tom--21,但是消费者没有消费,然后生产者继续生产出name为Marry,而消费者这个时候拿去消费了,那么便出现 Marry--11。同理也会出现 Tom--21
原因2:出现重复数据,是因为生产者生产一份数据了,消费者拿去消费了,但是第二次生产者生产数据了,但是消费者没有去消费;而第三次生产者继续生产数据,消费者才开始消费,这便会产生重复
解决办法1:生产者生产name和age必须要是一个整体一起完成,即同步。生产的中间不能让消费者来消费即可。便不会产生错乱的数据。如何同步可以参考:
Java 多线程详解(三)------线程的同步:http://www.cnblogs.com/ysocean/p/6883729.html
这里我们选择同步方法(在方法前面加上 synchronized)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
public class Person { private String name; private int age; /** * 生产数据 * @param name * @param age */ public synchronized void push(String name, int age){ this .name = name; try { //这段延时代码的作用是可能只生产了 name,age为nul,消费者就拿去消费了 Thread.sleep( 10 ); } catch (InterruptedException e) { e.printStackTrace(); } this .age = age; } /** * 取数据,消费数据 * @return */ public synchronized void pop(){ try { Thread.sleep( 10 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println( this .name+ "---" + this .age); } } |
结果如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
Marry--- 21 Marry--- 21 Marry--- 21 Marry--- 21 Marry--- 21 Tom--- 11 Tom--- 11 ...... Tom--- 11 Tom--- 11 Tom--- 11 Tom--- 11 Tom--- 11 |
问题:还是没有解决上面的问题2,出现重复的问题。期望的结果是 Tom---11,Marry--21,Tom---11,Mary---21...... 连续这样交替出现50次。那如何解决呢?
解决办法:生产者生产一次数据了,就暂停生产者线程,等待消费者消费;消费者消费完了,消费者线程暂停,等待生产者生产数据,这样来进行。
这里我们介绍一个同步锁池的概念:
同步锁池:同步锁必须选择多个线程共同的资源对象,而一个线程获得锁的时候,别的线程都在同步锁池等待获取锁;当那个线程释放同步锁了,其他线程便开始由CPU调度分配锁
关于让线程等待和唤醒线程的方法,如下:(这是 Object 类中的方法)
wait():执行该方法的线程对象,释放同步锁,JVM会把该线程放到等待池中,等待其他线程唤醒该线程
notify():执行该方法的线程唤醒在等待池中等待的任意一个线程,把线程转到锁池中等待(注意锁池和等待池的区别)
notifyAll():执行该方法的线程唤醒在等待池中等待的所有线程,把线程转到锁池中等待。
注意:上述方法只能被同步监听锁对象来调用,这也是为啥wait() 和 notify()方法都在 Object 对象中,因为同步监听锁可以是任意对象,只不过必须是需要同步线程的共同对象即可,否则别的对象调用会报错: java.lang.IllegalMonitorStateException
假设 A 线程和 B 线程同时操作一个 X 对象,A,B 线程可以通过 X 对象的 wait() 和 notify() 方法来进行通信,流程如下:
①、当线程 A 执行 X 对象的同步方法时,A 线程持有 X 对象的 锁,B线程在 X 对象的锁池中等待
②、A线程在同步方法中执行 X.wait() 方法时,A线程释放 X 对象的锁,进入 X 对象的等待池中
③、在 X 对象的锁池中等待锁的 B 线程获得 X 对象的锁,执行 X 的另一个同步方法
④、B 线程在同步方法中执行 X.notify() 方法,JVM 把 A 线程从等待池中移动到 X 对象的锁池中,等待获取锁
⑤、B 线程执行完同步方法,释放锁,等待获取锁的 A 线程获得锁,继续执行同步方法
那么为了解决上面重复的问题,修改代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 以上是关于多线程——生产者和消费者的主要内容,如果未能解决你的问题,请参考以下文章 java利用多线程实现生产者和消费者功能————美的掉渣的代码 |