Java实现生产者消费者的三种方式

一、使用synchronize以及wait()、notify() /notifyAll()

[Java] 纯文本查看 复制代码

?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

package com.zhb.juc;

import java.util.ArrayList;

import java.util.List;

import java.util.concurrent.atomic.AtomicInteger;

/**

* 使用synchronize wait notify/notifyall实现生产者消费者模式

*/

class ShareDataV1 {

public static AtomicInteger atomicInteger = new AtomicInteger();

public volatile boolean flag = true;

public static final int MAX_COUNT = 10;

public static final List<Integer> pool = new ArrayList<>();

public void produce() {

// 判断,干活,通知

while (flag) {

// 每隔 1000 毫秒生产一个商品

try {

Thread.sleep(100);

} catch (InterruptedException e) {

}

synchronized (pool) {

//池子满了,生产者停止生产

//埋个坑,这里用的if

//TODO 判断

if (pool.size() == MAX_COUNT) {

try {

System.out.println("pool is full, wating...");

pool.wait();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

//干活

pool.add(atomicInteger.incrementAndGet());

System.out.println("produce number:" + atomicInteger.get() + "\t" + "current size:" + pool.size());

//通知

pool.notifyAll();

}

}

}

public void consumue() {

// 判断,干活,通知

while (flag) {

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

}

synchronized (pool) {

//池子满了,生产者停止生产

//埋个坑,这里用的if

//TODO 判断

if (pool.size() == 0) {

try {

System.out.println("pool is empty, wating...");

pool.wait();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

//干活

int temp = pool.get(0);

pool.remove(0);

System.out.println("cousume number:" + temp + "\t" + "current size:" + pool.size());

//通知

pool.notifyAll();

}

}

}

public void stop() {

flag = false;

}

}

public class ProducerConsumer_V1 {

public static void main(String[] args) {

ShareDataV1 shareDataV1 = new ShareDataV1();

new Thread(() -> {

shareDataV1.produce();

}, "AAA").start();

new Thread(() -> {

shareDataV1.consumue();

}, "BBB").start();

try {

Thread.sleep(5000);

} catch (InterruptedException e) {

e.printStackTrace();

}

shareDataV1.stop();

}

}


上面的程序在只有两个线程时(一个生产者,一个消费者)可以正常工作。打印的log如下:
produce number:1 current size:1
produce number:2 current size:2
produce number:3 current size:3
produce number:4 current size:4
produce number:5 current size:5
produce number:6 current size:6
produce number:7 current size:7
produce number:8 current size:8
produce number:9 current size:9
cousume number:1 current size:8
produce number:10 current size:9
produce number:11 current size:10
pool is full, wating...
cousume number:2 current size:9
produce number:12 current size:10
pool is full, wating...
cousume number:3 current size:9
produce number:13 current size:10
pool is full, wating...
cousume number:4 current size:9
produce number:14 current size:10
pool is full, wating...
cousume number:5 current size:9
produce number:15 current size:10
Process finished with exit code 0
但是我们把生产者和消费者线程扩展至多个。就出错了。例如再增加CCC和DDD线程分别生产和消费。只改动了main方法:

[Java] 纯文本查看 复制代码

?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

public class ProducerConsumer_V1 {

public static void main(String[] args) {

ShareDataV1 shareDataV1 = new ShareDataV1();

new Thread(() -> {

shareDataV1.produce();

}, "AAA").start();

new Thread(() -> {

shareDataV1.consumue();

}, "BBB").start();

new Thread(() -> {

shareDataV1.produce();

}, "CCC").start();

new Thread(() -> {

shareDataV1.consumue();

}, "DDD").start();

try {

Thread.sleep(5000);

} catch (InterruptedException e) {

e.printStackTrace();

}

shareDataV1.stop();

}

}


输出的log如下:
produce number:1 current size:1
produce number:2 current size:2
produce number:3 current size:3
produce number:4 current size:4
produce number:5 current size:5
produce number:6 current size:6
produce number:7 current size:7
produce number:8 current size:8
produce number:9 current size:9
produce number:10 current size:10
pool is full, wating...
pool is full, wating...
cousume number:1 current size:9
cousume number:2 current size:8
produce number:11 current size:9
produce number:12 current size:10
pool is full, wating...
pool is full, wating...
cousume number:3 current size:9
produce number:13 current size:10
produce number:14 current size:11
cousume number:4 current size:10
pool is full, wating...
pool is full, wating...
cousume number:5 current size:9
produce number:15 current size:10
produce number:16 current size:11
cousume number:6 current size:10
pool is full, wating...
pool is full, wating...
cousume number:7 current size:9
produce number:17 current size:10
produce number:18 current size:11
cousume number:8 current size:10
pool is full, wating...
pool is full, wating...
cousume number:9 current size:9
produce number:19 current size:10
produce number:20 current size:11
cousume number:10 current size:10
pool is full, wating...
pool is full, wating...
cousume number:11 current size:9
produce number:21 current size:10
produce number:22 current size:11
Process finished with exit code 0
我们看到current size 能到11了。这肯定出错了。因为我们要求pool的最大容量为10。出现这个情况的原因是在多线程的环境下,要防止虚假唤醒。即判断条件不能用if,而是用while。接下来我们修改上面//TODO部分的代码,把if改成while再来测试。最终版正确的代码如下:

[Java] 纯文本查看 复制代码

?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

package com.zhb.juc;

import java.util.ArrayList;

import java.util.List;

import java.util.concurrent.atomic.AtomicInteger;

/**

* 使用synchronize wait notify/notifyall实现生产者消费者模式

*/

class ShareDataV1 {

public static AtomicInteger atomicInteger = new AtomicInteger();

public volatile boolean flag = true;

public static final int MAX_COUNT = 10;

public static final List<Integer> pool = new ArrayList<>();

public void produce() {

// 判断,干活,通知

while (flag) {

// 每隔 1000 毫秒生产一个商品

try {

Thread.sleep(100);

} catch (InterruptedException e) {

}

synchronized (pool) {

//池子满了,生产者停止生产

//埋个坑,这里用的if

//TODO 判断

while (pool.size() == MAX_COUNT) {

try {

System.out.println("pool is full, wating...");

pool.wait();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

//干活

pool.add(atomicInteger.incrementAndGet());

System.out.println("produce number:" + atomicInteger.get() + "\t" + "current size:" + pool.size());

//通知

pool.notifyAll();

}

}

}

public void consumue() {

// 判断,干活,通知

while (flag) {

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

}

synchronized (pool) {

//池子满了,生产者停止生产

//埋个坑,这里用的if

//TODO 判断

while (pool.size() == 0) {

try {

System.out.println("pool is empty, wating...");

pool.wait();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

//干活

int temp = pool.get(0);

pool.remove(0);

System.out.println("cousume number:" + temp + "\t" + "current size:" + pool.size());

//通知

pool.notifyAll();

}

}

}

public void stop() {

flag = false;

}

}

public class ProducerConsumer_V1 {

public static void main(String[] args) {

ShareDataV1 shareDataV1 = new ShareDataV1();

new Thread(() -> {

shareDataV1.produce();

}, "AAA").start();

new Thread(() -> {

shareDataV1.consumue();

}, "BBB").start();

new Thread(() -> {

shareDataV1.produce();

}, "CCC").start();

new Thread(() -> {

shareDataV1.consumue();

}, "DDD").start();

try {

Thread.sleep(5000);

} catch (InterruptedException e) {

e.printStackTrace();

}

shareDataV1.stop();

}

}


输出结果:
produce number:1 current size:1
produce number:2 current size:2
produce number:3 current size:3
produce number:4 current size:4
produce number:5 current size:5
produce number:6 current size:6
produce number:7 current size:7
produce number:8 current size:8
produce number:9 current size:9
produce number:10 current size:10
pool is full, wating...
pool is full, wating...
cousume number:1 current size:9
produce number:11 current size:10
pool is full, wating...
cousume number:2 current size:9
produce number:12 current size:10
pool is full, wating...
pool is full, wating...
cousume number:3 current size:9
produce number:13 current size:10
pool is full, wating...
cousume number:4 current size:9
produce number:14 current size:10
pool is full, wating...
pool is full, wating...
cousume number:5 current size:9
produce number:15 current size:10
pool is full, wating...
cousume number:6 current size:9
produce number:16 current size:10
pool is full, wating...
pool is full, wating...
cousume number:7 current size:9
produce number:17 current size:10
pool is full, wating...
cousume number:8 current size:9
produce number:18 current size:10
pool is full, wating...
pool is full, wating...
cousume number:9 current size:9
produce number:19 current size:10
pool is full, wating...
cousume number:10 current size:9
produce number:20 current size:10
cousume number:11 current size:9
Process finished with exit code 0
二、使用Lock,Condition的await和signal方法
JUC包下的锁Lock替代synchronize关键字。await方法代替wait,signal代替notifyall。
下面这个demo实现了pool的大小为1的生产者消费者模型。

[Java] 纯文本查看 复制代码

?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

package com.zhb.juc;

import java.util.concurrent.locks.Condition;

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;

class ShareData {

private int number = 0;

private Lock lock = new ReentrantLock();

private Condition condition = lock.newCondition();

public void increment() throws Exception {

lock.lock();

try {

while (number != 0) {

//等待,不能生产

condition.await();

}

number++;

System.out.println(Thread.currentThread().getName() + "\t" + number);

condition.signalAll();

} catch (Exception e) {

e.printStackTrace();

} finally {

lock.unlock();

}

}

public void decrement() throws Exception {

lock.lock();

try {

while (number == 0) {

//等待,不能消费

condition.await();

}

number--;

System.out.println(Thread.currentThread().getName() + "\t" + number);

condition.signalAll();

} catch (Exception e) {

e.printStackTrace();

} finally {

lock.unlock();

}

}

}

public class ProducerConsumer_V2{

public static void main(String[] args) {

ShareData shareData = new ShareData();

new Thread(()->{

for (int i = 0; i < 5; i++) {

try {

shareData.increment();

} catch (Exception e) {

e.printStackTrace();

}

}

}, "AA").start();

new Thread(()->{

for (int i = 0; i < 5; i++) {

try {

shareData.decrement();

} catch (Exception e) {

e.printStackTrace();

}

}

}, "BB").start();

new Thread(()->{

for (int i = 0; i < 5; i++) {

try {

shareData.increment();

} catch (Exception e) {

e.printStackTrace();

}

}

}, "CC").start();

new Thread(()->{

for (int i = 0; i < 5; i++) {

try {

shareData.decrement();

} catch (Exception e) {

e.printStackTrace();

}

}

}, "DD").start();

}

}


三、终极版使用阻塞队列
首先谈谈阻塞队列:
当阻塞队列为空时,从阻塞队列中取数据的操作会被阻塞。
当阻塞队列为满时,往阻塞队列中添加数据的操作会被阻塞。
ArrayBlockingQueue:
基于数组的阻塞队列实现,其内部维护一个定长的数组,用于存储队列元素。线程阻塞的实现是通过ReentrantLock来完成的,数据的插入与取出共用同一个锁,因此ArrayBlockingQueue并不能实现生产、消费同时进行。而且在创建ArrayBlockingQueue时,我们还可以控制对象的内部锁是否采用公平锁,默认采用非公平锁。
LinkedBlockingQueue:
基于单向链表的阻塞队列实现,在初始化LinkedBlockingQueue的时候可以指定对立的大小,也可以不指定,默认类似一个无限大小的容量(Integer.MAX_VALUE),不指队列容量大小也是会有风险的,一旦数据生产速度大于消费速度,系统内存将有可能被消耗殆尽,因此要谨慎操作。另外LinkedBlockingQueue中用于阻塞生产者、消费者的锁是两个(锁分离),因此生产与消费是可以同时进行的。


参考:https://www.jianshu.com/p/f4eefb069e27
下面是使用阻塞队列实现生产者消费者模式:
package com.zhb.juc;
/*
使用阻塞队列实现生产者消费者模型
*/
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
//资源类
class ShareDataV3{
private static final int MAX_CAPACITY = 10; //阻塞队列容量
private static BlockingQueue<Integer> blockingQueue= new ArrayBlockingQueue<>(MAX_CAPACITY); //阻塞队列
private volatile boolean FLAG = true;
private AtomicInteger atomicInteger = new AtomicInteger();
public void produce() throws InterruptedException {
while (FLAG){
boolean retvalue = blockingQueue.offer(atomicInteger.incrementAndGet(), 2, TimeUnit.SECONDS);
if (retvalue==true){
System.out.println(Thread.currentThread().getName()+"\t 插入队列"+ atomicInteger.get()+"成功"+"资源队列大小= " + blockingQueue.size());
}else {
System.out.println(Thread.currentThread().getName()+"\t 插入队列"+ atomicInteger.get()+"失败"+"资源队列大小= " + blockingQueue.size());
}
TimeUnit.SECONDS.sleep(1);
}
System.out.println(Thread.currentThread().getName()+"FLAG变为flase,生产停止");
}
public void consume() throws InterruptedException {
Integer result = null;
while (true){
result = blockingQueue.poll(2, TimeUnit.SECONDS);
if (null==result){
System.out.println("超过两秒没有取道数据,消费者即将退出");
return;
}
System.out.println(Thread.currentThread().getName()+"\t 消费"+ result+"成功"+"\t\t"+"资源队列大小= " + blockingQueue.size());
Thread.sleep(1500);
}
}
public void stop() {
this.FLAG = false;
}
}
public class ProducerConsumer_V3 {
public static void main(String[] args) {
ShareDataV3 v3 = new ShareDataV3();
new Thread(()->{
try {
v3.produce();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "AAA").start();
new Thread(()->{
try {
v3.consume();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "BBB").start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
v3.stop();
}
}
可以看到使用阻塞队列根本不需要我们去加锁,通知什么的,完全解放了。
运行结果:
AAA 插入队列1成功资源队列大小= 0
BBB 消费1成功 资源队列大小= 0
AAA 插入队列2成功资源队列大小= 1
BBB 消费2成功 资源队列大小= 0
AAA 插入队列3成功资源队列大小= 1
BBB 消费3成功 资源队列大小= 1
AAA 插入队列4成功资源队列大小= 1
AAA 插入队列5成功资源队列大小= 2
BBB 消费4成功 资源队列大小= 1
AAA 插入队列6成功资源队列大小= 2
AAAFLAG变为flase,生产停止
BBB 消费5成功 资源队列大小= 1
BBB 消费6成功 资源队列大小= 0
超过两秒没有取道数据,消费者即将退出
Process finished with exit code 0
发布了667 篇原创文章 · 获赞 6 · 访问量 8991

猜你喜欢

转载自blog.csdn.net/heima201907/article/details/104413310