上一篇中用Condition实现了生产者和消费者,其中用到了Condition的await和singal,这个实现和Object的锁特别像,如果用Object的锁机制实现生产者和消费者,如下:
import java.util.Date; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * Created by lizhiqiang on 2016/12/23. */ public class testObjectLock2 { public static void main(String[] args){ final BoundedBuffer buffer = new BoundedBuffer(); ExecutorService service = Executors.newFixedThreadPool(2); service.execute(new Runnable() { @Override public void run() { while(true){ try { Thread.currentThread().sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } buffer.put(1); } } }); service.execute(new Runnable() { @Override public void run() { while(true) { System.out.println(new Date() + ":" + buffer.take()); } } }); service.shutdown(); } public static class BoundedBuffer{ Object notEmpty = new Object(); Object notFull = new Object(); final Object[] items = new Object[3]; int count; public BoundedBuffer(){ count=0; } public void put(Object x){ synchronized (notFull){ if(count==2){ try { notFull.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } synchronized (notEmpty){ ++count; items[count] = x; notEmpty.notify(); } } } public Object take(){ Object result; synchronized (notEmpty){ if(count==0) { try { notEmpty.wait(); } catch (InterruptedException e) { } } synchronized (notFull){ result = items[count--]; notFull.notify(); } return result; } } } }执行结果:
Fri Dec 23 11:07:20 CST 2016:1
Fri Dec 23 11:07:21 CST 2016:1
Fri Dec 23 11:07:22 CST 2016:1
Fri Dec 23 11:07:23 CST 2016:1
Fri Dec 23 11:07:24 CST 2016:1
Fri Dec 23 11:07:25 CST 2016:1
Fri Dec 23 11:07:26 CST 2016:1
发现效果和上一篇一样。
下面开始讲两个问题:
1.用Condition或者Object怎么实现多消费者、多生产者?
2.Condition和Object到底有什么不同?
1.用Condition或者Object怎么实现多消费者、多生产者?
Condition实现:
import java.util.Date; 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; /** * Created by lizhiqiang on 2016/12/23. */ public class testCondition3 { public static void main(String[] args){ final BoundedBuffer buffer = new BoundedBuffer(); ExecutorService service = Executors.newFixedThreadPool(4); service.execute(new Runnable() { @Override public void run() { while(true){ buffer.put(1); } } }); service.execute(new Runnable() { @Override public void run() { while(true) { System.out.println("消费者"+Thread.currentThread().getName()+":"+new Date() + ":" + buffer.take()); } } }); service.execute(new Runnable() { @Override public void run() { while(true) { System.out.println("消费者"+Thread.currentThread().getName()+":"+new Date() + ":" + buffer.take()); } } }); service.execute(new Runnable() { @Override public void run() { while(true) { System.out.println("消费者"+Thread.currentThread().getName()+":"+new Date() + ":" + buffer.take()); } } }); service.shutdown(); } public static class BoundedBuffer{ final Lock lock = new ReentrantLock(); Condition notEmpty = lock.newCondition(); Condition notFull = lock.newCondition(); final Object[] items = new Object[3]; int count; public BoundedBuffer(){ count=0; } public void put(Object x){ lock.lock(); if(count==2){ try { notFull.await(); } catch (InterruptedException e) { e.printStackTrace(); } } ++count; items[count] = x; notEmpty.signal(); lock.unlock(); } public Object take(){ lock.lock(); Object result; if(count==0) { try { notEmpty.await(); } catch (InterruptedException e) { } } result = items[count--]; notFull.signal(); lock.unlock(); return result; } } }Object实现:
import java.util.Date; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * Created by lizhiqiang on 2016/12/23. */ public class testObjectLock3 { public static void main(String[] args){ final BoundedBuffer buffer = new BoundedBuffer(); ExecutorService service = Executors.newFixedThreadPool(4); service.execute(new Runnable() { @Override public void run() { while(true){ buffer.put(1); } } }); service.execute(new Runnable() { @Override public void run() { while(true) { System.out.println("消费者"+Thread.currentThread().getName()+":"+new Date() + ":" + buffer.take()); } } }); service.execute(new Runnable() { @Override public void run() { while(true) { System.out.println("消费者"+Thread.currentThread().getName()+":"+new Date() + ":" + buffer.take()); } } }); service.execute(new Runnable() { @Override public void run() { while(true) { System.out.println("消费者"+Thread.currentThread().getName()+":"+new Date() + ":" + buffer.take()); } } }); service.shutdown(); } public static class BoundedBuffer{ Object notEmpty = new Object(); Object notFull = new Object(); final Object[] items = new Object[3]; int count; public BoundedBuffer(){ count=0; } public void put(Object x){ synchronized (notFull){ if(count==2){ try { notFull.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } synchronized (notEmpty){ ++count; items[count] = x; notEmpty.notify(); } } } public Object take(){ Object result; synchronized (notEmpty){ if(count==0) { try { notEmpty.wait(); } catch (InterruptedException e) { } } synchronized (notFull){ result = items[count--]; notFull.notify(); } return result; } } } }以上两个实现方式,均实现了一个生产者多个消费者的问题,但是执行会报错:数组越界
为什么?
原因其实出在singal和notify后,对应的线程不是立即执行,而是cpu从等待池中拿到对应的线程,然后再进行上下文切换,才开始执行对应的线程。比如生产者生产一个产品,通知消费者A,此时,生产者开始生产第二个产品,通知消费者B,消费者A和B同时执行,而A执行比较快,一下消费了两个产品,导致B一个产品也拿不到,造成数组越界。
解决方法:
让生产者等一会再通知另一个消费者,让消费者有充足的的时间执行,并再次进入等待。但是这种思路导致生产者效率低下,因为同一时间它只是让一个消费者在执行。
在生产者代码中假如等待时间:
service.execute(new Runnable() { @Override public void run() { while(true){ try { Thread.currentThread().sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } buffer.put(1); } } });另一个思路:
让消费者take一次之后休眠一会,这个比较符合实际的场景。
多生产者多消费者的场景呢?
加等待时间已经不能解决这类数组越界的问题了,只能用try catch将数组越界给吃掉了。
下面一起讨论下第二个内容:
Synchronize和lock的区别是什么呢,有了Synchronize为什么还需要有lock?
首先,synchronize无法解决多个对象锁出现的死锁问题。
比如我们上面的事例中,
public void put(Object x){ synchronized (notFull){ if(count==2){ try { System.out.println("产品库满了"); notFull.wait(); System.out.println("可以添加产品了,当前有"+count+"个"); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("生产者"+Thread.currentThread().getName()); synchronized (notEmpty){ System.out.println("要添加产品,当前有"+count+"个"); ++count; items[count] = x; System.out.println("添加完产品,当前有"+count+"个"); notEmpty.notify(); } } } public Object take(){ Object result; synchronized (notEmpty){ if(count==0) { try { System.out.println("没有产品了"); notEmpty.wait(); System.out.println("有产品了"); } catch (InterruptedException e) { } } System.out.println("消费者"+Thread.currentThread().getName()); synchronized (notFull){ System.out.println("要获取产品,当前有"+count+"个"); result = items[count--]; System.out.println("取走产品,当前有"+count+"个"); notFull.notify(); } return result; } }以上两个方法,一个是生产者调用的,一个是消费者调用的,如果生产者和消费者是一对多的关系,那么极为可能出现死锁。
检查死锁的方法就是将所有的步骤的执行时间都可以看成无限长(主要是和cpu切换上下文以及时间片分配有关)。因为notify方法实现的是将锁交给任意一个,比如:消费者A拿到notEmpty,它在等待notFull锁,而生产者拿到notFull,这时候生产者释放notEmpty锁,将锁交给消费者B,生产者自身开始等消费者A释放notEmpty锁,这样就进入了死锁状态了。(一个生产者和一个消费者一样会出现死锁:只是可能性不是很高)
但是如果改为Lock实现:
public static class BoundedBuffer{ final Lock lock = new ReentrantLock(); Condition notEmpty = lock.newCondition(); Condition notFull = lock.newCondition(); final Object[] items = new Object[3]; int count; public BoundedBuffer(){ count=0; } public void put(Object x){ lock.lock(); if(count==2){ try { System.out.println("产品库满了"); notFull.await(); System.out.println("可以添加产品了,当前有" + count + "个"); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("要添加产品,当前有"+count+"个"); ++count; items[count] = x; notEmpty.signal(); System.out.println("添加完产品,当前有" + count + "个"); lock.unlock(); } public Object take(){ lock.lock(); Object result; if(count==0) { try { System.out.println("没有产品了"); notEmpty.await(); System.out.println("有产品了"); } catch (InterruptedException e) { } } System.out.println("要获取产品,当前有"+count+"个"); result = items[count--]; notFull.signal(); System.out.println("取走产品,当前有" + count + "个"); lock.unlock(); return result; } }就不会出现死锁的问题,因为Lock锁可以有多个condition,两个线程不同方法进入lock代码块是同步的。
这也就是为什么又出现Lock,或者Lock为什么更高级的地方!Lock可以实现不同代码块的同步执行!(而Synchronize做不到,它只是可以做到对象的使用同步)
如果只是一个对象进行加锁,那么无所谓用Lock还是Synchronize,但是如果是多个对象,那么只能用Lock。