14.1 同步的练习
两个储户。到同一个银行,每个人存了3次,一个100元。
- 描述银行(属性:储钱数; 行为:可以存钱)
- 描述储户任务
分析多线程是否存在安全隐患:
- 线程任务中是否存在共享数据
- 是否有多条操作共享数据的代码
package day14;
class Bank{
private int sum;
public void add(int n){
sum += n;
System.out.println("sum = "+sum);
}
}
class Depositor implements Runnable{
private Bank bank = new Bank();
public void run(){
for(int i=0; i<3; i++){
bank.add(100);
}
}
}
public class Test1 {
public static void main(String[] args){
Depositor d = new Depositor();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
t2.start();
}
}
输出结果有很多种,其中一种是:
sum = 200
sum = 200
sum = 300
sum = 400
sum = 500
sum = 600
解决方案,使用同步。
package day14;
class Bank{
private int sum;
Object obj = new Object();
public void add(int n){
synchronized (obj){
sum += n;
System.out.println("sum = "+sum);
}
}
}
class Depositor implements Runnable{
private Bank bank = new Bank();
public void run(){
for(int i=0; i<3; i++){
bank.add(100);
}
}
}
public class Test1 {
public static void main(String[] args){
Depositor d = new Depositor();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
t2.start();
}
}
输出:
sum = 100
sum = 200
sum = 300
sum = 400
sum = 500
sum = 600
14.2 同步函数
同步函数:其实就是在函数上加上了同步关键字进行修饰。
同步表现形式有两种:1.同步代码块 2. 同步函数
之前写的都是同步代码块:
synchronized(对象){
}
下面代码,是改写成同步函数:
package day14;
class Bank{
private int sum;
Object obj = new Object();
public synchronized void add(int n){
sum += n;
System.out.println("sum = "+sum);
}
}
class Depositor implements Runnable{
private Bank bank = new Bank();
public void run(){
for(int i=0; i<3; i++){
bank.add(100);
}
}
}
public class Test1 {
public static void main(String[] args){
Depositor d = new Depositor();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
t2.start();
}
}
同步函数使用的锁是什么呢?函数需要被对象调用,哪个对象不确定,但是都用this来表示。
同步函数使用的锁就是this。
14.3 验证同步函数锁是this
验证需求:
启动两个线程:
一个线程负责执行同步代码块(使用明锁)
另一个线程使用同步代码块(使用this锁)
两个线程执行的任务是一样的,都是卖票。如果,他们没有使用相同的锁说明他们没有同步,会出现数据错误。
怎么能让一个线程一直在同步代码块中,一个线程在同步函数呢?
可以通过切换的方式。
package day13;
//1. 定义一个类实现Runnable
class SaleTicket1 implements Runnable{
private static int tickets = 10;
//定义一个boolean标记
boolean flag = true;
Object obj = new Object();
//卖票的代码需要被多个线程执行,所以要将这些代码定义在线程任务中。run方法。
public void run(){
if(flag){
while (true){
synchronized (obj){
if (tickets>0){
try{
Thread.sleep(10); //让线程到这里稍微停一下。
}catch (InterruptedException e){
}
System.out.println(Thread.currentThread().getName()+".....code...."+tickets--);
}
}
}
}
else{
while (true){
sale();
}
}
}
public synchronized void sale(){
while (true){
if (tickets>0){
try{
Thread.sleep(10); //让线程到这里稍微停一下。
}catch (InterruptedException e){
}
System.out.println(Thread.currentThread().getName()+".....function...."+tickets--);
}
}
}
}
public class TicketsDemo2 {
public static void main(String[] args) throws InterruptedException{
SaleTicket1 s1 = new SaleTicket1();
//SaleTicket1 s2 = new SaleTicket1();
Thread t1 = new Thread(s1);
Thread t2 = new Thread(s1);
t1.start();
Thread.sleep(10);
s1.flag = false;
t2.start();
}
}
输出结果:
Thread-1.....function....9
Thread-0.....code....10
Thread-1.....function....8
Thread-0.....code....7
Thread-0.....code....5
Thread-1.....function....6
Thread-0.....code....4
Thread-1.....function....4
Thread-1.....function....3
Thread-0.....code....2
Thread-0.....code....1
Thread-1.....function....0
出现了0号票,出现了安全问题,原因是使用的锁不是同一个,如果将同步代码块的锁改成this,:
synchronized(this){
}
输出结果:
Thread-0.....code....10
Thread-0.....code....9
Thread-1.....function....8
Thread-1.....function....7
Thread-1.....function....6
Thread-1.....function....5
Thread-1.....function....4
Thread-1.....function....3
Thread-1.....function....2
Thread-1.....function....1
14.4 验证static同步函数锁是类名.class
如果同步函数被静态修饰呢?
static方法随着类加载,这时不一定有该类的对象,也就不一定有this。但是一定有一个该类的字节码对象文件,这个对象简单的表示方法就是 类名.class
注意:这个类名是同步代码块所在的这个类的名字,即SaleTicket1 ,而不是有主函数的类的名字。
synchronized(类名.class){
}
具体代码如下:
package day13;
//1. 定义一个类实现Runnable
class SaleTicket1 implements Runnable{
private static int tickets = 10;
//定义一个boolean标记
boolean flag = true;
//卖票的代码需要被多个线程执行,所以要将这些代码定义在线程任务中。run方法。
public void run(){
if(flag){
while (true){
synchronized (SaleTicket1.class){
if (tickets>0){
try{
Thread.sleep(10); //让线程到这里稍微停一下。
}catch (InterruptedException e){
}
System.out.println(Thread.currentThread().getName()+".....code...."+tickets--);
}
}
}
}
else{
while (true){
sale();
}
}
}
public static synchronized void sale(){
while (true){
if (tickets>0){
try{
Thread.sleep(10); //让线程到这里稍微停一下。
}catch (InterruptedException e){
}
System.out.println(Thread.currentThread().getName()+".....function...."+tickets--);
}
}
}
}
public class TicketsDemo2 {
public static void main(String[] args) throws InterruptedException{
SaleTicket1 s = new SaleTicket1();
Thread t1 = new Thread(s);
Thread t2 = new Thread(s);
t1.start();
Thread.sleep(10);
s.flag = false;
t2.start();
}
}
14.5 单例模式的并发访问.class
单例设计模式分为懒汉式和饿汉式,代码如下:
//饿汉式
class Single{
private static final Single SINGLE_INSATNCE= new Single();
private Single(){}
public static Single getInstance(){
return SINGLE_INSATNCE;
}
}
//懒汉式,延迟加载模式
class Single{
private static Single s = null;
private Single(){}
public static Single getInstance(){
if(s == null)
s = new Single();
return s;
}
}
饿汉式,相对与多线程并发,安全。
懒汉式,在多线程并发访问时,会出现线程安全问题。
class Single2{
private static Single2 s = null;
private Single2(){}
public static Single2 getInstance(){
if (s == null) {
s = new Single2();
System.out.println("single");
}
return s;
}
}
class Demo implements Runnable{
public void run(){
Single2.getInstance();
}
}
public class ThreadSingleTest {
public static void main(String[] args){
Demo d = new Demo();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
t2.start();
}
}
输出结果:
single
single
从结果来看,产生了两个对象。怎么解决这个问题呢?
加了同步就可以解决问题,无论是同步函数还是同步代码块都行。
class Single2{
private static Single2 s = null;
private Single2(){}
public static synchronized Single2 getInstance(){
if(s == null)
s = new Single2();
return s;
}
}
class Demo implements Runnable{
public void run(){
Single2.getInstance();
}
}
public class ThreadSingleTest {
public static void main(String[] args){
Demo d = new Demo();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
t2.start();
}
}
输出:
single
多次对是否同步进行判断,效率变低了。
怎么解决效率低的问题?
可以通过if对单例对象的双重判断的形式解决。
//懒汉式
class Single2{
private static Single2 s = null;
private Single2(){}
public static Single2 getInstance(){
if(s == null){
synchronized (Single2.class){
if(s == null)
s = new Single2();
}
}
return s;
}
}
class Demo implements Runnable{
public void run(){
Single2.getInstance();
}
}
public class ThreadSingleTest {
public static void main(String[] args){
Demo d = new Demo();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
t2.start();
}
}
14.6 同步函数和同步代码块区别
同步代码块使用的任意的对象作为锁
同步函数只能使用this或者字节码对象作为锁。
如果说:一个类只需要一个锁,这时可以考虑同步函数,使用this,写法简单。
但是,一个类如果需要多个锁,还有多各类中使用同一个锁,这时只能使用同步代码块。
建议使用同步代码块。
14.7 死锁演示
死锁:
场景一:同步嵌套
package day14;
//1. 定义一个类实现Runnable
class SaleTicket1 implements Runnable{
private static int tickets = 10;
//定义一个boolean标记
boolean flag = true;
Object obj = new Object();
//卖票的代码需要被多个线程执行,所以要将这些代码定义在线程任务中。run方法。
public void run(){
if(flag){
while (true){
synchronized (obj){ // obj lock
sale();
}
}
}
else{
while (true){
sale();
}
}
}
public synchronized void sale(){ //this lock
synchronized (obj){
if (tickets>0){
try{
Thread.sleep(10); //让线程到这里稍微停一下。
}catch (InterruptedException e){
}
System.out.println(Thread.currentThread().getName()+".....function...."+tickets--);
}
}
}
}
public class DeadLockDemo {
public static void main(String[] args) throws InterruptedException{
SaleTicket1 s1 = new SaleTicket1();
//SaleTicket1 s2 = new SaleTicket1();
Thread t1 = new Thread(s1);
Thread t2 = new Thread(s1);
t1.start();
Thread.sleep(10);
s1.flag = false;
t2.start();
}
}
输出结果:
14.8 死锁示例
package day14;
//同步嵌套,死锁
// 描述线程任务
class Task implements Runnable{
private boolean flag;
public Task(boolean flag){
this.flag = flag;
}
public void run(){
if(flag){
synchronized (MyLock.LOCKA){
System.out.println("if............locka");
synchronized (MyLock.LOCKB){
System.out.println("if............lockb");
}
}
}
else{
synchronized (MyLock.LOCKB){
System.out.println("else............lockb");
synchronized (MyLock.LOCKA){
System.out.println("else............locka");
}
}
}
}
}
class MyLock{
public static final Object LOCKA = new Object();
public static final Object LOCKB = new Object();
}
public class DeadLockDemo1 {
public static void main(String[] args){
Task task1 = new Task(true);
Task task2 = new Task(false);
Thread t1 = new Thread(task1);
Thread t2 = new Thread(task2);
t1.start();
t2.start();
}
}
输出结果:
else............lockb
if............locka
14.9 生成者消费者演示
多线程间的通信。多个线程都在处理同一资源,但是处理的任务却是不一样的。
最典型的例子:生产者和消费者。
一个简单的情况:
产者生产一个,消费者消费一个;产者生产再一个,消费者再消费一个…如此循环
package day14;
import java.io.Console;
//描述资源
class Res{
private String name;
private int count;
//提供了商品赋值的方法
public void set(String name){
this.name = name + "--" + count;
count++;
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
}
//提供了获取商品的方法
public void out(){
System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);
count--;
}
}
//生产者
class Producer implements Runnable{
private Res r;
public Producer(Res r){
this.r = r;
}
public void run(){
while(true)
r.set("面包");
}
}
//消费者
class Consumer implements Runnable{
private Res r;
public Consumer(Res r){
this.r = r;
}
public void run(){
while (true)
r.out();
}
}
public class ProducerConsumerDemo {
public static void main(String[] args){
// 1.创建资源
Res r = new Res();
// 2.创建两个任务
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
// 3.创建线程
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
// 4。开启线程
t1.start();
t2.start();
}
}
输出结果不尽人意,竟然有先消费后生产的情况,结果非常混乱,以下输出结果的前一小部分:
Thread-1...消费者...null
Thread-0...生产者...面包--0
Thread-1...消费者...面包--0
Thread-0...生产者...面包--0
Thread-1...消费者...面包--0
Thread-0...生产者...面包--0
Thread-1...消费者...面包--0
Thread-0...生产者...面包--0
Thread-1...消费者...面包--0
首先解决一个问题:如何保证先生产后消费呢?
观察到执行生产者任务的线程和执行消费者任务的线程使用是同一个锁,那就是this,具体说都是资源这一个对象。所以可以将执行生产者的代码和执行消费者的代码设置成同步函数,这样他们就是按顺序执行了.
修改后的代码如下:
package day14;
import java.io.Console;
//描述资源
class Res{
private String name;
private int count;
//提供了商品赋值的方法
public synchronized void set(String name){
this.name = name + "--" + count;
count++;
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
}
//提供了获取商品的方法
public synchronized void out(){
System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);
count--;
}
}
//生产者
class Producer implements Runnable{
private Res r;
public Producer(Res r){
this.r = r;
}
public void run(){
while(true)
r.set("面包");
}
}
//消费者
class Consumer implements Runnable{
private Res r;
public Consumer(Res r){
this.r = r;
}
public void run(){
while (true)
r.out();
}
}
public class ProducerConsumerDemo {
public static void main(String[] args){
// 1.创建资源
Res r = new Res();
// 2.创建两个任务
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
// 3.创建线程
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
// 4.开启线程
t1.start();
t2.start();
}
}
从结果看,通过同步,解决了没生产就消费的问题,但是出现了连续的生产,但是没有消费的情况,和需求生产一个就消费一个不符。下面是结果的前一小部分:
Thread-0...生产者...面包--0
Thread-0...生产者...面包--1
Thread-0...生产者...面包--2
Thread-0...生产者...面包--3
Thread-0...生产者...面包--4
Thread-0...生产者...面包--5
Thread-0...生产者...面包--6
Thread-0...生产者...面包--7
Thread-0...生产者...面包--8
Thread-0...生产者...面包--9
Thread-0...生产者...面包--10
Thread-1...消费者...面包--10
Thread-1...消费者...面包--10
Thread-1...消费者...面包--10
Thread-1...消费者...面包--10
Thread-1...消费者...面包--10
Thread-1...消费者...面包--10
Thread-1...消费者...面包--10
Thread-1...消费者...面包--10
Thread-1...消费者...面包--10
Thread-1...消费者...面包--10
Thread-1...消费者...面包--10
14.10 等待唤醒机制体现
通过同步,解决了没生产就消费的问题,但是出现了连续的生产,但是没有消费的情况,和需求生产一个就消费一个不符。怎么解决:
使用等待唤醒机制:
wait(); 该方法可以让线程处于冻结状态,并将线程临时存储在线程池中。
notify(); 唤醒指定线程池中的任意一个线程。
notifyAll(); 唤醒指定线程池中的所有线程。
原理图:
代码实现:
package day14;
import java.io.Console;
//描述资源
class Res{
private String name;
private int count;
// 定义标记
private boolean flag;
//提供了商品赋值的方法
public synchronized void set(String name){
// 判断标记为true,执行wait,等待;否则,就生产。
if(flag)
try{ wait(); }catch (InterruptedException e){}
this.name = name + "--" + count;
count++;
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
// 生产完毕,就要将标记改为true,并唤醒消费者
flag = true;
notify();
}
//提供了获取商品的方法
public synchronized void out(){
// 判断标记为false,执行wait,等待;否则,就消费。
if(!flag)
try{ wait(); }catch (InterruptedException e){}
System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);
// 消费完毕,要将标记改为false,并唤醒生产者
flag = false;
notify();
}
}
//生产者
class Producer implements Runnable{
private Res r;
public Producer(Res r){
this.r = r;
}
public void run(){
while(true)
r.set("面包");
}
}
//消费者
class Consumer implements Runnable{
private Res r;
public Consumer(Res r){
this.r = r;
}
public void run(){
while (true)
r.out();
}
}
public class ProducerConsumerDemo {
public static void main(String[] args){
// 1.创建资源
Res r = new Res();
// 2.创建两个任务
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
// 3.创建线程
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
// 4.开启线程
t1.start();
t2.start();
}
}
以下是输出结果的前一小部分:
Thread-0...生产者...面包--0
Thread-1...消费者...面包--0
Thread-0...生产者...面包--1
Thread-1...消费者...面包--1
Thread-0...生产者...面包--2
Thread-1...消费者...面包--2
Thread-0...生产者...面包--3
Thread-1...消费者...面包--3
Thread-0...生产者...面包--4
Thread-1...消费者...面包--4
Thread-0...生产者...面包--5
Thread-1...消费者...面包--5
14.11 等待唤醒机制原理理解
wait();
notify();
notifyAll();
这些方法必须使用在同步中,因为它们同来操作同步锁上的线程的状态的。
在使用这些方法时,必须表示他们所属的锁。标识方法就是
锁对象.wait();
锁对象.notify();
锁对象.notifyAll();
相同锁的notify(),可以获取相同的锁的wait();
14.12 多生产多消费问题以及解决
多生产多消费
如果开启两个生产者,两个消费者,结果会是怎么样呢:
package day14;
import java.io.Console;
//描述资源
class Res{
private String name;
private int count;
// 定义标记
private boolean flag;
//提供了商品赋值的方法
public synchronized void set(String name){
// 判断标记为true,执行wait,等待;否则,就生产。
if(flag)
try{ wait(); }catch (InterruptedException e){}
this.name = name + "--" + count;
count++;
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
// 生产完毕,就要将标记改为true,并唤醒消费者
flag = true;
notify();
}
//提供了获取商品的方法
public synchronized void out(){
// 判断标记为false,执行wait,等待;否则,就消费。
if(!flag)
try{ wait(); }catch (InterruptedException e){}
System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);
// 消费完毕,要将标记改为false,并唤醒生产者
flag = false;
notify();
}
}
//生产者
class Producer implements Runnable{
private Res r;
public Producer(Res r){
this.r = r;
}
public void run(){
while(true)
r.set("面包");
}
}
//消费者
class Consumer implements Runnable{
private Res r;
public Consumer(Res r){
this.r = r;
}
public void run(){
while (true)
r.out();
}
}
public class ProducerConsumerDemo {
public static void main(String[] args){
// 1.创建资源
Res r = new Res();
// 2.创建两个任务
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
// 3.创建线程
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
Thread t3 = new Thread(pro);
Thread t4 = new Thread(con);
// 4.开启线程
t1.start();
t2.start();
t3.start();
t4.start();
}
}
输出的部分结果:
Thread-2...生产者...面包--16699
Thread-3...消费者...面包--16699
Thread-1...消费者...面包--16699
Thread-2...生产者...面包--16700
Thread-3...消费者...面包--16700
Thread-1...消费者...面包--16700
Thread-2...生产者...面包--16701
Thread-3...消费者...面包--16701
Thread-1...消费者...面包--16701
Thread-2...生产者...面包--16702
Thread-3...消费者...面包--16702
Thread-1...消费者...面包--16702
Thread-2...生产者...面包--16703
重复生成,重复消费
原因:经过复杂(等待,获得资格)的分析,发现被唤醒的线程没有判断标记就开始工作了(生成 or 消费)了
导致了重复的生成和消费的发生。
解决:那就是被唤醒的线程必须判断标记。使用while循环。
修改的代码如下:
package day14;
import java.io.Console;
//描述资源
class Res{
private String name;
private int count;
// 定义标记
private boolean flag;
//提供了商品赋值的方法
public synchronized void set(String name){
// 判断标记为true,执行wait,等待;否则,就生产。
while(flag)
try{ wait(); }catch (InterruptedException e){}
this.name = name + "--" + count;
count++;
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
// 生产完毕,就要将标记改为true,并唤醒消费者
flag = true;
notify();
}
//提供了获取商品的方法
public synchronized void out(){
// 判断标记为false,执行wait,等待;否则,就消费。
while(!flag)
try{ wait(); }catch (InterruptedException e){}
System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);
// 消费完毕,要将标记改为false,并唤醒生产者
flag = false;
notify();
}
}
//生产者
class Producer implements Runnable{
private Res r;
public Producer(Res r){
this.r = r;
}
public void run(){
while(true)
r.set("面包");
}
}
//消费者
class Consumer implements Runnable{
private Res r;
public Consumer(Res r){
this.r = r;
}
public void run(){
while (true)
r.out();
}
}
public class ProducerConsumerDemo {
public static void main(String[] args){
// 1.创建资源
Res r = new Res();
// 2.创建两个任务
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
// 3.创建线程
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
Thread t3 = new Thread(pro);
Thread t4 = new Thread(con);
// 4.开启线程
t1.start();
t2.start();
t3.start();
t4.start();
}
}
结果死锁了,输出结果:
Thread-0...生产者...面包--0
Thread-3...消费者...面包--0
Thread-0...生产者...面包--1
问题2:
死锁了,所有线程都处于冻结状态。
原因: 本方线程在唤醒时,又一次唤醒了本方线程。而本方线程循环判断标记,又继续等待。
而导致所有的线程都等待了。
解决:希望本方如果唤醒了对方线程,就可以解决。
可以使用notifyAll()方法。
那不是全唤醒了吗?是的,既有本方,又有对方,但是本方醒后,会判断标记继续等待。
package day14;
import java.io.Console;
//描述资源
class Res{
private String name;
private int count;
// 定义标记
private boolean flag;
//提供了商品赋值的方法
public synchronized void set(String name){
// 判断标记为true,执行wait,等待;否则,就生产。
while(flag)
try{ wait(); }catch (InterruptedException e){}
this.name = name + "--" + count;
count++;
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
// 生产完毕,就要将标记改为true,并唤醒消费者
flag = true;
notifyAll();
}
//提供了获取商品的方法
public synchronized void out(){
// 判断标记为false,执行wait,等待;否则,就消费。
while(!flag)
try{ wait(); }catch (InterruptedException e){}
System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);
// 消费完毕,要将标记改为false,并唤醒生产者
flag = false;
notifyAll();
}
}
//生产者
class Producer implements Runnable{
private Res r;
public Producer(Res r){
this.r = r;
}
public void run(){
while(true)
r.set("面包");
}
}
//消费者
class Consumer implements Runnable{
private Res r;
public Consumer(Res r){
this.r = r;
}
public void run(){
while (true)
r.out();
}
}
public class ProducerConsumerDemo {
public static void main(String[] args){
// 1.创建资源
Res r = new Res();
// 2.创建两个任务
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
// 3.创建线程
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
Thread t3 = new Thread(pro);
Thread t4 = new Thread(con);
// 4.开启线程
t1.start();
t2.start();
t3.start();
t4.start();
}
}
输出结果的前一部分:
Thread-0...生产者...面包--0
Thread-3...消费者...面包--0
Thread-2...生产者...面包--1
Thread-1...消费者...面包--1
Thread-0...生产者...面包--2
Thread-3...消费者...面包--2
Thread-2...生产者...面包--3
Thread-1...消费者...面包--3
Thread-0...生产者...面包--4
Thread-3...消费者...面包--4
Thread-2...生产者...面包--5
Thread-1...消费者...面包--5
Thread-0...生产者...面包--6
Thread-3...消费者...面包--6
Thread-2...生产者...面包--7
Thread-1...消费者...面包--7