一、概述
1、在等待 Condition 时,允许发生“虚假唤醒”,这通常作为对基础平台语义的让步。对于大多数应用程序,这带来的实际影响很小,因为 Condition 应该总是在一个循环中被等待,并测试正被等待的状态声明。某个实现可以随意移除可能的虚假唤醒,但建议应用程序程序员总是假定这些虚假唤醒可能发生,因此总是在一个循环中等待。
2、一个锁内部可以有多个Condition,即有多路等待和通知,可以参看jdk1.5提供的Lock与Condition实现的可阻塞队列的应用案例,从中除了要体味算法,还要体味面向对象的封装。在传统的线程机制中一个监视器对象上只能有一路等待和通知,要想实现多路等待和通知,必须嵌套使用多个同步监视器对象。(如果只用一个Condition,两个放的都在等,一旦一个放的进去了,那么它通知可能会导致另一个放接着往下走。)
二、代码描述
1、ConditionCommunication.java
将传统wait()和notify() 改写成Condition方式,需要Lock配合使用。
/**
* @Title: TraditionalThreadCommunication.java
* @Package com.lh.threadtest.t4
* @Description: TODO
* @author Liu
* @date 2018年1月15日 下午9:28:23
* @version V1.0
*/
package com.lh.threadtest.t10;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @ClassName: TraditionalThreadCommunication
* @Description: 传统线程同步通信技术
*
* 子线程循环10次,接着主线程循环100次,接着又回到子线程循环10次,接着再回到主线程
* 又循环100次,如此循环50次,请写出程序。
*
* 注意:
* Lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象。
* 两个线程执行的代码片段要实现同步互斥的效果,它们必须用同一个Lock对象。锁是上在代表要操作的资源的类的内部方法中,
* 而不是线程代码中!
*
* 总结:
* 需要用到共同数据(包括同步锁)或共同算法的若干个方法应该归在同一个类中,这种设计正好体现了
* 高类聚和程序的健壮性。
*
* @author Liu
* @date 2018年1月15日 下午9:28:23
*
*/
public class ConditionCommunication {
public static void main(String[] args) {
final Business business = new Business();
new Thread(new Runnable() {
public void run() {
for(int i = 1; i <= 50; i++){
try {
business.sub(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
for(int i = 1; i <= 50; i++){
try {
business.main(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//不同点:
//condition.business
//business
//将具有类似功能的业务代码抽象到同一个类中
//下面两个方法(sub/main)是互斥的,均是独立的一个同步模块,处于同一个互斥组(通过synchronized表明均属于同一个类,同一时刻只能有一个线程持有该锁)
static class Business{
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
//主线程和子线程切换执行的一个标识
boolean flag = true;
void sub(int i) throws InterruptedException{
lock.lock();
try {
// if(!flag){
//循环和if功能相仿,但更加完备
while(!flag){
//使当前线程等待...
// this.wait();
condition.await();
}
for(int j = 1; j <= 10; j++){
System.out.println("sub thread sequence of " + j + ", loop of " + i);
}
flag = false;
//唤醒其它正在等待的线程
// this.notify();
condition.signal();
} finally {
lock.unlock();
}
}
void main(int i) throws InterruptedException{
lock.lock();
try {
// if(flag){
//循环和if功能相仿,但更加完备
while(flag){
//使当前线程等待...
// this.wait();
condition.await();
}
for(int j = 1; j <= 100; j++){
System.out.println("main thread sequence of " + j + ", loop of " + i);
}
flag = true;
//唤醒其它正在等待的线程
// this.notify();
condition.signal();
} finally {
lock.unlock();
}
}
}
}
2、ThreeConditionCommunication.java
老大循环100次,然后老二循环10次,接着老三循环20次,又接着老大循环100次,老二又循环10次,老三又循环20次, 如此循环50次,请写出程序。
/**
* @Title: TraditionalThreadCommunication.java
* @Package com.lh.threadtest.t4
* @Description: TODO
* @author Liu
* @date 2018年1月15日 下午9:28:23
* @version V1.0
*/
package com.lh.threadtest.t10;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @ClassName: TraditionalThreadCommunication
* @Description: java5条件阻塞Condition的应用
*
* 老大循环100次,然后老二循环10次,接着老三循环20次,
* 又接着老大循环100次,老二又循环10次,老三又循环20次,
* 如此循环50次,请写出程序。
*
* @author Liu
* @date 2018年1月15日 下午9:28:23
*
*/
public class ThreeConditionCommunication {
public static void main(String[] args) {
final Business business = new Business();
new Thread(new Runnable() {
public void run() {
for(int i = 1; i <= 50; i++){
try {
business.sub2(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable() {
public void run() {
for(int i = 1; i <= 50; i++){
try {
business.sub3(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
for(int i = 1; i <= 50; i++){
try {
business.main(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//不同点:
//condition.business
//business
//将具有类似功能的业务代码抽象到同一个类中
//下面两个方法(sub/main)是互斥的,均是独立的一个同步模块,处于同一个互斥组(通过synchronized表明均属于同一个类,同一时刻只能有一个线程持有该锁)
static class Business{
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
//主线程和子线程切换执行的一个标识
int flag = 1;
void sub3(int i) throws InterruptedException{
lock.lock();
try {
// if(!flag){
//循环和if功能相仿,但更加完备
while(flag != 3){
//使当前线程等待...
// this.wait();
condition3.await();
}
for(int j = 1; j <= 20; j++){
System.out.println("sub3 thread sequence of " + j + ", loop of " + i);
}
flag = 1;
//唤醒其它正在等待的线程
// this.notify();
condition.signal();
} finally {
lock.unlock();
}
}
void sub2(int i) throws InterruptedException{
lock.lock();
try {
// if(!flag){
//循环和if功能相仿,但更加完备
while(flag != 2){
//使当前线程等待...
// this.wait();
condition2.await();
}
for(int j = 1; j <= 10; j++){
System.out.println("sub2 thread sequence of " + j + ", loop of " + i);
}
flag = 3;
//唤醒其它正在等待的线程
// this.notify();
condition3.signal();
} finally {
lock.unlock();
}
}
void main(int i) throws InterruptedException{
lock.lock();
try {
// if(flag){
//循环和if功能相仿,但更加完备
while(flag != 1){
//使当前线程等待...
// this.wait();
condition.await();
}
for(int j = 1; j <= 100; j++){
System.out.println("main thread sequence of " + j + ", loop of " + i);
}
flag = 2;
//唤醒其它正在等待的线程
// this.notify();
condition2.signal();
} finally {
lock.unlock();
}
}
}
}
三、jdk1.5提供的Lock与Condition实现的可阻塞队列的应用案例
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
}
finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
}
finally {
lock.unlock();
}
}
}
思考:
1、为什么要用两个Condition,而不是一个?
唤醒的是取的,而不是放的,每次只能存取一个。如果只用一个Condition,两个放的都在等,一旦一个放的进去了,那么它通知可能会导致另一个放接着往下走。
四、关键点
1、Condition一般需要依赖Lock使用,不能独立发挥作用。
2、Condition作用相当于传统线程中的wait()和notify()——线程间通信。
3、wait()和notify()只能在synchronized块中使用,同样地,Condition的await()和signal()方法只能在lock.lock()和lock.unlock()内部使用。