1、介绍
-
1、condition:
一般是和锁绑定的,通过lock.newCondition()产生 可以生产多个,分别控制不同的线程角色(生产者和消费者)
-
2、object:
任何对象都有,一般使用临界资源(queue数组)进行控制
由于queue只有一个,所以notifyall的时候会唤醒所有线程(生产者和消费)不能够“分类”控制
-
3、使用共同点==>都必须先加锁,然后在操作,否则会报await被打断的异常。
-
4、加锁锁定后,如果线程一直在运行,其他线程申请这个锁,肯定会被阻塞,但是如果此线程await之后,就会释放锁,让其他线程进来
-
5、为什么要加锁?看总结
2、优缺点:
-
1、Condition:
由于可创建多个,可以精确唤醒一组线程,其他线程继续等待,免得浪费资源。 比如队列为null,则wait当前消费者,唤醒所有生产者,而不会唤醒其他消费者 因为即使唤醒其他的消费者,由于此时队列为null,也会即刻进入wait状态 同时还会进行锁竞争,损耗性能
-
2、object:
只有一个,同样如果队列为null时,wait当前消费者,然后notifyall(),唤醒其他所有线程(生产者和消费者)
其他消费者,第一竞争锁,然后进来后,发现队列还是为null,立马await
也就是没有做事情,还竞争锁,损耗性能。
-
3、ConditionCPU负载
-
4、ObjectCPU负载
Condition,100W ==> 5753毫秒
Object,100W ==> 5797毫秒
2、代码,注意:一定要先锁住
两种方式加锁,模拟实现消息队列
-
1、QueueImpl.java
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; /** * 模拟消息队列,分别使用condition和object对象进行加锁,同步控制。 * * condition: * 一般是和锁绑定的,通过lock.newCondition()产生 * 可以生产多个,分别控制不同的线程角色(生产者和消费者) * * object: * 任何对象都有,一般使用临界资源(queue数组)进行控制 * 由于queue只有一个,所以notifyall的时候会唤醒所有线程(生产者和消费) * 不能够分开控制 * * 使用共同点: * 都必须先加锁,然后在操作,否则会报await被打断的异常 * 线程执行await、yieldhi释放锁,并且进行则塞等待 * sleep不会释放锁 * * [@Author](https://my.oschina.net/arthor) liufu * [@Company](https://my.oschina.net/u/3478402) 任子行网络技术股份有限公司 * @CreateTime 2018/4/19 9:03 */ public class QueueImpl<T> { private Object[] queue; private int size; //队列数组长度 private int msgSize = 0; //队列中的数据量 private int productIndex = 0; //生产者位置 private int consumerIndex = 0; //消费者已经消费了的记录 private ReentrantLock lock = new ReentrantLock(true); //用于锁定生产者,队列满的时候,notFull锁定,等待消费者唤醒 private Condition notFull = lock.newCondition(); //用于消费者,数组空的时候,notEmtry锁住,等待生产者唤醒 private Condition notEmtry = lock.newCondition(); public QueueImpl(int size) { queue = new Object[size]; this.size = size; } /** * 生产者插入数据, await、signalAll写法 * * [@param](https://my.oschina.net/u/2303379) msg * [@return](https://my.oschina.net/u/556800) */ public boolean conditionPut(T msg) { lock.lock(); //必须加锁,否则会报wait会被打断异常 try { //之所以用while,是因为被唤醒后,队列可能又被其他生产者写满了,继续阻塞 while (msgSize == size) { //如果放在这里,那么就是等生产者填满队列之后,在唤醒消费者 //notEmtry.signalAll(); notFull.await(); } if (productIndex > 100000) { productIndex = productIndex % size; consumerIndex = consumerIndex % size; } queue[productIndex % size] = msg; productIndex++; msgSize++; //放在这里,那么就是生产一条,消费一条 notEmtry.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); return false; } finally { lock.unlock(); } return true; } /** * 消费者拿取数据, await、signalAll写法 * * @return */ public T conditionTake() { T result = null; lock.lock(); //必须加锁,否则会报wait会被打断异常 try { //之所以用while,是因为队列可能又被其他线程消费完了,继续阻塞 while (msgSize == 0) { //放在这里,表示把数据都消费完了,在唤醒生产者进行生产 //notFull.signalAll(); notEmtry.await(); } result = (T) queue[consumerIndex % size]; consumerIndex++; msgSize--; //放在这里,消费一条,就唤醒生产者 notFull.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } return result; } /** * 生产者插入数据, wait、notifyall写法 * * @param msg * @return */ public boolean objectPut(T msg) { synchronized (queue) { //必须加锁,否则会报wait会被打断异常 try { //之所以用while,是因为被唤醒后,队列可能又被其他生产者写满了,继续阻塞 while (msgSize == size) { //写在这里,表示队列写满了,才唤醒消费者消费 //queue.notifyAll(); queue.wait(); } if (productIndex > 10000) { productIndex = productIndex % size; consumerIndex = consumerIndex % size; } queue[productIndex % size] = msg; productIndex++; msgSize++; //写在这里,表示队列写满了,生产一条,就唤醒消费者消费 queue.notify(); } catch (InterruptedException e) { e.printStackTrace(); return false; } } return true; } /** * 消费者拿取数据, wait、notifyall写法 * * @return */ public T objectTake() { T result = null; synchronized (queue) { //必须加锁,否则会报wait会被打断异常 try { //之所以用while,是因为队列可能又被其他线程消费完了,继续阻塞 while (msgSize == 0) { //写在这里,表示消费者把队列中的信息全部消费了,才唤醒生产者生产 //queue.notifyAll(); queue.wait(); } result = (T) queue[consumerIndex % size]; consumerIndex++; msgSize--; //写在这里,表示消费一条,就唤醒生产者进行生产 queue.notify(); } catch (InterruptedException e) { e.printStackTrace(); } } return result; } }
-
2、ProductAndConsumer.java
import java.util.concurrent.atomic.AtomicInteger; /** * Condition测试 * Condition一般是和锁绑定的,通过lock.newCondition()产生 * Condition只能锁定和唤醒相同相同condition对象的值 * <p> * 本例子实现消费者和生产者对一个数组进行生产和消费操作 * * @Author liufu * @CreateTime 2018/4/18 20:31 */ public class ProductAndConsumer { public static void main(String[] args) { QueueImpl<String> queue = new QueueImpl<String>(200); AtomicInteger atomicInteger = new AtomicInteger(0); //condition生产者 new Thread(() -> { while (true) { queue.conditionPut("i am msg:" + atomicInteger.getAndIncrement()); } }).start(); //condition消费者 new Thread(() -> { while (true) { System.out.println(queue.conditionTake()); } }).start(); //============================================================= //object生产者 new Thread(() -> { while (true) { queue.objectPut("i am msg:" + atomicInteger.getAndIncrement()); } }).start(); //object消费者 new Thread(() -> { while (true) { System.out.println(queue.objectTake()); } }).start(); } }
3、总结
-
1、必须加锁,lock.lock()或者synchronized (queue)。否则会报线程被打断异常
-
2、线程调用wait()之后,会释放已经获得的锁给其他线程进来,并且将其自身放置在对象的等待集中,等待notify或者notifyall
-
3、那么问题来了:大家都在wait,如果执行了notifyall,所有线程立马同时执行,不就会导致线程不安全吗?
答案是:不会,因为
notifyall确实是通知了所有执行此对象wait而等待的线程,他们都会被唤醒,但是执行之前它们都需要重新竞争获得锁(这就是为什么要加锁的原因)。这就限制了同一时刻,只会有一个线程获得锁执行,其他线程继续等待。拿到锁后,会从上一次wait()方法之后执行(恢复现场)。
也就是说必须满足两个条件,缺一不可
1、被notify到 2、重新获得锁
-
4、notify和notifyall有什么区别?
notify只会唤醒等待中的一个线程,其他的不会被唤醒,这个时候唤醒的线程执行完成后,必须继续调用notify方法唤醒另外一个线程。
而notifyall会唤醒所有等待线程,只是由于有“锁”的原因,只有一个线程执行,其他线程继续阻塞,只是这个时候不需要再继续notify而已。
所以什么时候用notifyall,什么时候用notify,就要看情况了。
-
5、NotifyallTest.java
import java.util.concurrent.atomic.AtomicInteger; /** * 主要是为了测试wait和notifyall到底是什么原理 * * @Author liufu * @CreateTime 2018/5/9 10:20 */ public class NotifyallTest { public static void main(String[] args) throws InterruptedException { String[] queue = new String[100]; AtomicInteger atomicInteger = new AtomicInteger(0); for (int i = 0; i < 10; i++) { new Thread(() -> { int index = atomicInteger.getAndIncrement(); synchronized (queue) { System.out.println(String.format("thread : %s wait", index)); try { queue.wait(); System.out.println(String.format("thread : %s runing, 10秒内没有其他线程运行!", index)); Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } //让上面的所有线程都启动,在notifyall Thread.sleep(100); //这个也要加锁 synchronized (queue) { queue.notifyAll(); } } }