多线程生产者消费者问题处理

一、比较低级的办法是用wait和notify来解决这个问题。

消费者生产者问题:

这个问题是一个多线程同步问题的经典案例,生产者负责生产对象,消费者负责将生成者产生的对象取出,两者不断重复此过程。这过程需要注意几个问题:

不论生产者和消费者有几个,必须保证:

1.生产者每次产出的对象必须不一样,产生的对象有且仅有出现一次;

2.消费者每次取出的对象必须不一样,取出的对象有且仅有出现一次;

3.一定是先产生该对象,然后对象才能被取出,顺序不能乱;

第一种情况:
多个生产者轮流负责生产,多个消费者负责取出。一旦生产者产生一个对象,其他生产者不能生产,只能由消费者执行取出操作;

需要的对象有商品类、消费者、生产者;

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
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
//测试类
public class ProducerConsumer {
  
    public static void main(String[] args) {
        // 定义资源对象
        Resource r = new Resource();
  
        //定义一个生产者和一个消费者
        Producer p = new Producer(r);
        Consumer c = new Consumer(r);
  
        //启动四个线程,2个负责生产者,两个消费者
        Thread t1 = new Thread(p);
        Thread t2 = new Thread(p);
        Thread t3 = new Thread(c);
        Thread t4 = new Thread(c);
  
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
  
}
  
//商品类
class Resource{
    private String name;
    private int count = 1;
    private  boolean flag = false;
  
    //产生商品
    public synchronized void set(String name) {
        while (flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.name = name + "---" + count++;
        System.out.println(Thread.currentThread().getName() + " 生产者" + this.name);
        flag = true;
        //唤醒所有线程
        this.notifyAll();
  
    }
    //取出商品
    public synchronized void out() {
            while (!flag) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + " 消费者________" + this.name);
            flag = false;
            this.notifyAll();
    }
}
  
//定义生产者
class Producer implements Runnable{
  
    private Resource res;
  
    public Producer(Resource res) {
        this.res = res;
    }
  
    @Override
    public void run() {
        while (true) {
            res.set("+商品+");
        }
    }
}
  
//定义消费者
class Consumer implements Runnable{
  
    private Resource res;
  
    Consumer(Resource res) {
        this.res = res;
    }
  
    @Override
    public void run() {
        while (true) {
            res.out();
        }
    }
}

  


运行结果是产生一个,随即取出一个,循环往复,其运行结果的部分如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Thread-2 消费者________+商品+---67821
Thread-1 生产者+商品+---67822
Thread-3 消费者________+商品+---67822
Thread-0 生产者+商品+---67823
Thread-2 消费者________+商品+---67823
Thread-1 生产者+商品+---67824
Thread-3 消费者________+商品+---67824
Thread-0 生产者+商品+---67825
Thread-2 消费者________+商品+---67825
Thread-1 生产者+商品+---67826
Thread-3 消费者________+商品+---67826
Thread-0 生产者+商品+---67827
Thread-2 消费者________+商品+---67827
Thread-1 生产者+商品+---67828
Thread-3 消费者________+商品+---67828
Thread-0 生产者+商品+---67829
Thread-2 消费者________+商品+---67829
Thread-1 生产者+商品+---67830
Thread-3 消费者________+商品+---67830
Thread-0 生产者+商品+---67831
Thread-2 消费者________+商品+---67831
Thread-1 生产者+商品+---67832

  


第二种情况:
目标:生产者与消费者轮换着抢夺执行权,但是生产者最多可以库存5个,消费者最多可以连续取出5个

此时需要定义一种中间对象:仓库类。该类是生产者和消费者共享的一块区域,里面数据类型选择链表结果存放产生的对象。仓库是有容量上限的,当数量达到上限后,生产者不允许继续生产产品.当前线程进入等待状态,等待其他线程唤醒。当仓库没有产品时,消费者不允许继续消费,当前线程进入等待状态,等待其他线程唤醒。

第一种解决方式,采用同步代码块(synchronized),结合着 wait() 和 notifyAll() 的方法,具体代码如下:

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
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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
package Thread;
/**
 * 2个消费者,3个生产者
 */
  
import java.util.LinkedList;
  
public class ProConThreadDemo {
    public static void main(String[] args) {
        Respository res = new Respository();
  
        //定义2个消费者,3个生产者
        Worker p1 = new Worker(res,"手机");
        Worker p2 = new Worker(res,"电脑");
        Worker p3 = new Worker(res,"鼠标");
        Constomer c1 = new Constomer(res);
        Constomer c2 = new Constomer(res);
  
        Thread t1 = new Thread(p1,"甲");
        Thread t2 = new Thread(p2,"乙");
        Thread t3 = new Thread(p3,"丙");
        Thread t4 = new Thread(c1,"aaa");
        Thread t5 = new Thread(c2,"bbb");
  
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
  
    }
  
  
  
}
//仓库类
class Respository{
  
    private LinkedList<Product> store = new LinkedList<Product>();
  
    //生产者的方法,用于向仓库存货
    //最多只能有一个线程同时访问该方法.
    public synchronized void push(Product p,String ThreadName){
        //设置仓库库存最多能存5个商品
        /* 仓库容量最大值为5,当容量等于5的时候进入等待状态.等待其他线程唤醒
         * 唤醒后继续循环,等到仓库的存量小于5时,跳出循环继续向下执行准备生产产品.
         */
        while (store.size()==5){
            try {
                System.out.println(ThreadName+" 发现:仓库已满,赶紧叫人运走");
                //因为仓库容量已满,无法继续生产,进入等待状态,等待其他线程唤醒.
                this.wait();
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
        this.notifyAll();
        store.addLast(p);
        System.out.println(ThreadName+" 给仓库添加 "+p.Name+p.Id+"号名称为 "+" 当前库存量为:"+store.size());
        //为了方便观察运行结果,每次生产完后等待0.1秒
        try {
            Thread.sleep(100);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
  
    }
  
  
    //消费者的方法,用于仓库出货
    //最多只能有一个线程同时访问该方法.
    public synchronized void pop(String ThreadName){
        /* 当仓库没有存货时,消费者需要进行等待.等待其他线程来唤醒
         * 唤醒后继续循环,等到仓库的存量大于0时,跳出循环继续向下执行准备消费产品.
         */
        while (store.size()==0){
            try {
                System.out.println(ThreadName+" 发现:仓库空了,赶紧安排生产");
                //因为仓库容量已空,无法继续消费,进入等待状态,等待其他线程唤醒.
                this.wait();
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
        this.notifyAll();
        //定义对象。存放pollFirst()方法删除的对象,
        Product p = store.pollFirst();
        System.out.println(ThreadName+"买走 "+p.Name+p.Id+" 当前库存量为:"+store.size());
        //为了方便观察运行结果,每次取出后等待0.1秒
        try {
            Thread.sleep(100);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
  
    }
  
  
}
  
  
//产品类
class Product{
    //产品的唯一标识Id
    public int Id;
    //产品的名称
    public String Name;
  
    public Product(String name, int id) {
        Name = name;
        Id = id;
    }
  
}
  
//生产者
class Worker implements Runnable{
    //关键字volatile 是为了保持 Id 的可见性,一旦Id被修改,其他任何线程用到Id的地方,都会相应修改
    //否则下方run方法容易出问题,生产商品的Id和名称 与到时候消费者取出商品的Id和名称不一致
    public volatile Integer Id = 0;
  
    public volatile String name;
  
    //引用一个产品
    private Product p;
    //引用一个仓库
    Respository res;
  
    boolean flag = true;
  
    public Worker(Respository res,String name) {
        this.res = res;
        this.name = name;
    }
  
    @Override
    public void run() {
        while (flag){
            p  = new Product(name,Id);
            res.push(new Product(this.p.Name,Id++),Thread.currentThread().getName());
        }
    }
}
  
class Constomer implements Runnable{
    boolean flag = true;
  
    //引用一个仓库
    Respository res;
  
    public Constomer(Respository res) {
        this.res = res;
    }
  
    @Override
    public void run() {
        while (flag) {
  
            res.pop(Thread.currentThread().getName());
  
  
        }
  
    }
}

  


运行结果如下,可见仓库最多库存为5个,接近于实际生产

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
aaa 发现:仓库空了,赶紧安排生产
乙 给仓库添加 电脑0号名称为  当前库存量为:1
丙 给仓库添加 鼠标0号名称为  当前库存量为:2
甲 给仓库添加 手机0号名称为  当前库存量为:3
bbb买走 电脑0 当前库存量为:2
bbb买走 鼠标0 当前库存量为:1
甲 给仓库添加 手机1号名称为  当前库存量为:2
丙 给仓库添加 鼠标1号名称为  当前库存量为:3
乙 给仓库添加 电脑1号名称为  当前库存量为:4
aaa买走 手机0 当前库存量为:3
aaa买走 手机1 当前库存量为:2
aaa买走 鼠标1 当前库存量为:1
aaa买走 电脑1 当前库存量为:0
aaa 发现:仓库空了,赶紧安排生产
乙 给仓库添加 电脑2号名称为  当前库存量为:1
丙 给仓库添加 鼠标2号名称为  当前库存量为:2
甲 给仓库添加 手机2号名称为  当前库存量为:3
bbb买走 电脑2 当前库存量为:2
bbb买走 鼠标2 当前库存量为:1
甲 给仓库添加 手机3号名称为  当前库存量为:2
丙 给仓库添加 鼠标3号名称为  当前库存量为:3
乙 给仓库添加 电脑3号名称为  当前库存量为:4
aaa买走 手机2 当前库存量为:3
乙 给仓库添加 电脑4号名称为  当前库存量为:4
乙 给仓库添加 电脑5号名称为  当前库存量为:5

  


第二种方法,利用 lock类 替代 synchronized的使用,这样可以优化代码,主要是在唤醒的时候可以根据条件去唤醒指定的某些线程。例如:当库存为空的时候,第一种方法是唤醒所有等待的线程,也包括取出的线程;而此时lock类 可以设置在库存为空的时候,只唤醒生产线程,取出的线程依旧处于等待状态,具体代码如下:

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
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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
package Thread;
  
import java.util.LinkedList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
  
public class ProConThreadPool {
  
    public static void main(String[] args) {
  
       Respository res = new Respository();
  
       Worker p1 = new Worker(res,"手机");
       Worker p2 = new Worker(res,"电脑");
       Worker p3 = new Worker(res,"鼠标");
  
       Constomer c1 = new Constomer(res);
       Constomer c2 = new Constomer(res);
  
       Thread t1 = new Thread(p1,"甲");
       Thread t2 = new Thread(p2,"乙");
       Thread t3 = new Thread(p3,"丙");
       Thread t4 = new Thread(c1,"aaa");
       Thread t5 = new Thread(c2,"bbb");
  
       t1.start();
       t2.start();
       t3.start();
       t4.start();
       t5.start();
  
    }
  
  
  
}
//仓库类
class Respository{
  
    private Lock lock = new ReentrantLock();
  
    private LinkedList<Product> store = new LinkedList<Product>();
  
    private Condition condition_pro = lock.newCondition();
    private Condition condition_con = lock.newCondition();
  
    public LinkedList<Product> getStore() {
        return store;
    }
  
    public void setStore(LinkedList<Product> store) {
        this.store = store;
    }
    //向仓库存货
    public  void push(Product p,String ThreadName) throws InterruptedException{
        lock.lock();
        try {
            //设置仓库库存最多能存5个商品
            while (store.size()==5){
                    System.out.println(ThreadName+" 发现:仓库已满,赶紧叫人运走");
                    condition_pro.await();
            }
            condition_con.signalAll();
            store.addLast(p);
            System.out.println(ThreadName+" 给仓库添加 "+p.Name+p.Id+"号名称为 "+" 当前库存量为:"+store.size());
  
        }finally {
            lock.unlock();
        }
        try {
            Thread.sleep(100);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
  
    }
  
  
    //仓库出货
    public void pop(String ThreadName) throws InterruptedException
    {
        lock.lock();
        try{
            while (store.size()==0){
  
                    System.out.println(ThreadName+" 发现:仓库空了,赶紧安排生产");
                    condition_con.await();
            }
            condition_pro.signalAll();
            Product p = store.pollFirst();
            System.out.println(ThreadName+"买走 "+p.Name+p.Id+" 当前库存量为:"+store.size());
  
        }
        finally {
            lock.unlock();
        }
        try {
            Thread.sleep(100);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
  
    }
  
  
}
  
  
  
class Product{
    public int Id;
  
    public String Name;
  
    public Product(String name, int id) {
        Name = name;
        Id = id;
    }
  
}
  
class Worker implements Runnable{
  
    public volatile Integer Id = 0;
  
    public volatile String name;
  
    //引用一个产品
    private Product p;
    //引用一个仓库
    Respository res;
  
    boolean flag = true;
  
    public Worker(Respository res,String name) {
        this.res = res;
        this.name = name;
    }
  
    @Override
    public void run(){
        while (flag){
                p  = new Product(name,Id);
                try {
                    res.push(new Product(this.p.Name,Id++),Thread.currentThread().getName());
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
  
            }
    }
}
  
class Constomer implements Runnable{
    boolean flag = true;
  
    //引用一个仓库
    Respository res;
  
    public Constomer(Respository res) {
        this.res = res;
    }
  
    @Override
    public void run() {
        while (flag) {
            try {
                res.pop(Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
  
    }
}

  


运行结果与上面类似:

复制代码
aaa 发现:仓库空了,赶紧安排生产
bbb 发现:仓库空了,赶紧安排生产
丙 给仓库添加 鼠标0号名称为  当前库存量为:1
乙 给仓库添加 电脑0号名称为  当前库存量为:2
甲 给仓库添加 手机0号名称为  当前库存量为:3
aaa买走 鼠标0 当前库存量为:2
bbb买走 电脑0 当前库存量为:1
bbb买走 手机0 当前库存量为:0
乙 给仓库添加 电脑1号名称为  当前库存量为:1
甲 给仓库添加 手机1号名称为  当前库存量为:2
aaa买走 电脑1 当前库存量为:1
丙 给仓库添加 鼠标1号名称为  当前库存量为:2
aaa买走 手机1 当前库存量为:1
甲 给仓库添加 手机2号名称为  当前库存量为:2
乙 给仓库添加 电脑2号名称为  当前库存量为:3
bbb买走 鼠标1 当前库存量为:2
丙 给仓库添加 鼠标2号名称为  当前库存量为:3
aaa买走 手机2 当前库存量为:2
甲 给仓库添加 手机3号名称为  当前库存量为:3
bbb买走 电脑2 当前库存量为:2
乙 给仓库添加 电脑3号名称为  当前库存量为:3
丙 给仓库添加 鼠标3号名称为  当前库存量为:4
复制代码

 

二、比较赞的办法是用Semaphore 或者 BlockingQueue来实现生产者消费者模型。

 BlockingQueue 是线程安全的,并且在调用 put,take 方法时会阻塞线程。

基于以上特性,可以不加任何锁解决生产者消费者问题。

直接上代码:

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 static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> bq = new LinkedBlockingQueue<>(2);
  
        CountDownLatch cdl = new CountDownLatch(2);
  
        Thread t1 = new Thread(()->{ // 生产者线程
                try {
                    for (int i = 0; i < 100; i++)
                        bq.put("z" + i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    cdl.countDown();
                }
        });
  
        Thread t2 = new Thread(()->{ // 消费者线程
                try {
                    for (int i = 0; i < 100; i++)
                        System.out.println(bq.take());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    cdl.countDown();
                }
        });
        t2.start();
        t1.start();
        cdl.await(); // 等待两个线程结束
        System.out.println(bq.size());
  
    }

  

参考:https://blog.csdn.net/zjt980452483/article/details/81348668

     https://blog.csdn.net/qq_29697901/article/details/90405141

 

posted @   天际星痕  阅读(633)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示