【详解】JUC之Condition
引出
在使用Lock之前,我们使用的最多的同步方式应该是synchronized关键字来实现同步方式了。配合Object的wait()、notify()系列方法可以实现等待/通知模式。
Condition接口也提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式,但是这两者在使用方式以及功能特性上还是有差别的。
初步使用
利用Condition实现一个简单的生产者和消费者
public class ConditionTest {
private final static Lock lock = new ReentrantLock(true);
private final static Condition condition = lock.newCondition();
private static int data = 0;
private static volatile boolean noUse = true;
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
while (true){
buildData();
}
}).start();
new Thread(()->{
while (true){
useData();
}
}).start();
}
/** * 生产数据 */
private static void buildData(){
try {
lock.lock(); //synchronized key word #moitor enter
while (noUse){
condition.await(); // monitor.wait()
}
data++;
System.out.println("P:" + data);
Thread.sleep(1000);
noUse = true;
condition.signal(); // monitor.notify()
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); // synchronized end #moitor end
}
}
/** * 消费数据 */
private static void useData(){
try {
lock.lock(); //synchronized key word #moitor enter
while (!noUse){
condition.await(); // monitor.wait()
}
System.out.println("C:" + data);
Thread.sleep(1000);
noUse = false;
condition.signal(); // monitor.notify()
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); // synchronized end #moitor end
}
}
}
结果
C:0
P:1
C:1
P:2
C:2
......
<mark>根据上面的代码有几个问题:</mark>
- 1.不使用Condition效果相同,是否可以不使用Condition?
- 2.生产者获得锁,但是陷入await时,锁还没有释放,生产者怎么获得到锁?
- 3.如果不使用lock只用Condition会怎么样?
问题解答
第一个问题
不使用Condition,依然会看起来一样的原因是:使用了公平锁,会尽可能保证两个线程的交替
第二个问题
在接口的上面有上述解释
The lock associated with this is atomically released and the current thread
In all cases, before this method can return the current thread must re-acquire the lock associated with this condition. When the thread returns it is guaranteed to hold this lock.
可以发现:await和Object.wait()类似,都会自动的释放锁,并且在唤起后需要重新获得锁
第三个问题
问题的提出是基于第二个问题,既然锁的获取没有意义,是否可以去掉?
如果不加lock,会抛出一个异常,说明:想要await必须需要获得到锁
Exception in thread "Thread-1" java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.signal(AbstractQueuedSynchronizer.java:1939)
at condition.ConditionTest.useData(ConditionTest.java:66)
at condition.ConditionTest.lambda$main$1(ConditionTest.java:27)
at java.lang.Thread.run(Thread.java:748)
wait和await的区别
等待队列
public class ConditionTest {
private final static Lock lock = new ReentrantLock();
private final static Condition condition = lock.newCondition();
private static int data = 0;
private static volatile boolean noUse = true;
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
creatThread();
}
Thread.sleep(3000);
lock.lock();
condition.signalAll();
lock.unlock();
Thread.sleep(1000);
}
public static void creatThread(){
new Thread(() -> {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + " await.");
condition.await();
System.out.println(Thread.currentThread().getName() + " no await.");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
结果:
Thread-0 await.
Thread-1 await.
Thread-2 await.
Thread-3 await.
Thread-4 await.
Thread-5 await.
Thread-6 await.
Thread-7 await.
Thread-8 await.
Thread-9 await.
Thread-0 no await.
Thread-1 no await.
Thread-2 no await.
Thread-3 no await.
Thread-4 no await.
Thread-5 no await.
Thread-6 no await.
Thread-7 no await.
Thread-8 no await.
Thread-9 no await.
可以发现await的等待队列是个先进先出的队列
总结
这是摘自《Java并发编程的艺术》
最大差异:
wait | await |
---|---|
是配合synchronized关键字的 | 是配合Lock锁的 |
等待队列的唤醒受到JVM的影响,是随机的唤醒 | 等待队列FIFO的,先进入先唤醒 |
不可以被打断 | 可以被打断 |
等待队列只有一个 | 每一个Condition都具有一个等待队列,可以创建多个Condition |
其他地方在使用上并无差异
利用Condition实现生产者和消费者
主要是创建两个Condition对象:第一个对象是消费者队列,第二个是生产者队列。这样可以实现两个队列的分离。
public class ConditionTest {
private final static Lock lock = new ReentrantLock(false);
private final static Condition PRODUCE_CONDITION = lock.newCondition();
private final static Condition CONSUMER_CONDITION = lock.newCondition();
private final static LinkedList<Long> TIMESTAMP_POOL = new LinkedList<>();
private final static int MAX_CAPACITY = 100;
private static int data = 0;
public static void main(String[] args) throws InterruptedException {
IntStream.range(0,6).boxed().forEach(ConditionTest::beginProduce);
IntStream.range(0,13).boxed().forEach(ConditionTest::beginConsume);
}
private static void beginProduce(int i){
new Thread(()->{
while (true){
produce();
sleep(2);
}
},"P-" + i).start();
}
private static void beginConsume(int i){
new Thread(()->{
while (true){
consum();
sleep(2);
}
},"C-" + i).start();
}
private static void produce(){
try {
lock.lock();
while (TIMESTAMP_POOL.size()>=MAX_CAPACITY){
PRODUCE_CONDITION.await();
}
long value = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + "P->" + value);
TIMESTAMP_POOL.addLast(value);
CONSUMER_CONDITION.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
private static void consum(){
try {
lock.lock();
while (TIMESTAMP_POOL.isEmpty()){
CONSUMER_CONDITION.await();
}
long value = TIMESTAMP_POOL.removeFirst();
System.out.println(Thread.currentThread().getName() + "C->" + value);
PRODUCE_CONDITION.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
private static void sleep(long sec){
try {
TimeUnit.SECONDS.sleep(sec);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
与 ReentrantLock的关系
protected Collection<Thread> getWaitingThreads(Condition condition)
- 获取此条件下,阻塞的线程集合
public int getWaitQueueLength(Condition condition)
- 获取此条件下,阻塞的线程数目
public boolean hasWaiters(Condition condition)
- 获取此条件是否有线程在阻塞
总结
- 可以发现Condition比较灵活,可以配合ReentrantLock,对阻塞的队列进行调试
- Condition可以创建多个,可以实现不同角色等待队列的分离
- Condition在阻塞时是可以被打断的,方便对线程进行关闭