线程同步通信:
- Lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象。两个线程执行的代码片段要实现同步互斥的效果,它们必须用同一个Lock对象。
- 在等待 Condition 时,允许发生“虚假唤醒”,这通常作为对基础平台语义的让步。对于大多数应用程序,这带来的实际影响很小,因为 Condition 应该总是在一个循环中被等待,并测试正被等待的状态声明。某个实现可以随意移除可能的虚假唤醒,但建议应用程序程序员总是假定这些虚假唤醒可能发生,因此总是在一个循环中等待。
- 一个锁内部可以有多个Condition,即有多路等待和通知,可以参看jdk1.5提供的Lock与Condition实现的可阻塞队列的应用案例,从中除了要体味算法,还要体味面向对象的封装。在传统的线程机制中一个监视器对象上只能有一路等待和通知,要想实现多路等待和通知,必须嵌套使用多个同步监视器对象。(如果只用一个Condition,两个放的都在等,一旦一个放的进去了,那么它通知可能会导致另一个放接着往下走。)
看一个例子,
子线程循环3次,接着主线程循环5,接着又回到子线程循环3次,接着再回到主线程又循环3,如此循环10次。
1.使用传统的线程同步通信方法,wait()和notify().
public class ThreadCommunication {
public static void main(String[] args) throws InterruptedException {
final Bussiness bussiness=new Bussiness();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i <10; i++) {
bussiness.sub(i);
}
}
}).start();
for (int i = 0; i <10; i++) {
bussiness.main(i);
}
}
}
class Bussiness{
private boolean isSub=true;//用于判断是不是轮到子线程执行了
synchronized void sub(int i){ //同步加锁,子线程循环中的一个线程执行的时候,其他的子线程不能执行
if (!isSub) {
try {
this.wait();//通信,如果没轮到子线程执行,就等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int j = 0; j <3; j++) {
System.out.println("sub.."+i+" thread of "+j+" loop");
}
isSub=false;//子线程执行完了,置为false,表示轮到主线程执行了
this.notify();//线程通信,通知主线程执行
}
synchronized void main(int i){
if (isSub) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int j = 0; j < 5; j++) {
System.out.println("main.."+i+" thread of "+j+" loop");
}
isSub=true;
this.notify();
}
}

2.使用java.util.concurrent.locks.Condition
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionCommunication {
/**
* @param args
*/
public static void main(String[] args) {
final Business business = new Business();
new Thread(
new Runnable() {
@Override
public void run() {
for(int i=1;i<=10;i++){
business.sub(i);
}
}
}
).start();
for(int i=1;i<=10;i++){
business.main(i);
}
}
static class Business {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
private boolean bShouldSub = true;
public void sub(int i){
lock.lock();
try{
while(!bShouldSub){
try {
condition.await();
} catch (Exception e) {
e.printStackTrace();
}
}
for(int j=1;j<=3;j++){
System.out.println("sub thread sequence of " + j + ",loop of " + i);
}
bShouldSub = false;
condition.signal();//通知主线程
}finally{
lock.unlock();
}
}
public void main(int i){
lock.lock();
try{
while(bShouldSub){
try {
condition.await();
} catch (Exception e) {
e.printStackTrace();
}
}
for(int j=1;j<=5;j++){
System.out.println("main thread sequence of " + j + ",loop of " + i);
}
bShouldSub = true;
condition.signal();
}finally{
lock.unlock();
}
}
}
}
看Condition类中有一个示例:
假定有一个绑定的缓冲区,它支持 put
和 take
方法。如果试图在空的缓冲区上执行take
操作,则在某一个项变得可用之前,线程将一直阻塞;如果试图在满的缓冲区上执行put
操作,则在有空间变得可用之前,线程将一直阻塞。
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();
}
}
}
该示例用了两个condition,
notFull和notEmpty,这样取完时候只通知写的 线程,写完的时候只通知取的线程,如果只用一个condtion,比如有3个线程取数据,3个线程写数据,若其中一个取数据的线程完成取数据,此时缓冲队列没有数据了,这个时候应该通知写线程执行,插入数据,但是conditon.signal()的时候,会通知到另外5个线程中的一个,可能是取线程,也可能是写线程,如果是通知到了取线程,还是会等待,因为没有数据可以取,。
所以采用上面的两个conditon,如果出现上面描述的情况,只会通知到写线程,而不会通知取的线程,避免了这种情况的发生。
接下来看下面的这个例子:如果希望子线程1循环3次,接着子线程2执行4次,接着主线程循环5次,接着又回到子线程1循环3次,接着再回到子线程2又循环4,接着又到主线程中执行5次,如此循环10次。
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ThreeConditionCommunication {
/**
* @param args
*/
public static void main(String[] args) {
final Business business = new Business();
new Thread(
new Runnable() {
@Override
public void run() {
for(int i=1;i<=10;i++){
business.sub2(i);
}
}
}
).start();
new Thread(
new Runnable() {
@Override
public void run() {
for(int i=1;i<=10;i++){
business.sub3(i);
}
}
}
).start();
for(int i=1;i<=10;i++){
business.main(i);
}
}
static class Business {
Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
private int shouldSub = 1;//1表示轮到主线程1执行,2表示轮到sub2执行,3表示轮到sub3执行
public void sub2(int i){
lock.lock();
try{
while(shouldSub!=2){//没轮到sub2执行,就等待
try {
condition2.await();
} catch (Exception e) {
e.printStackTrace();
}
}
for(int j=1;j<=3;j++){
System.out.println("sub2 thread sequence of " + j + ",loop of " + i);
}
shouldSub = 3;//sub2 执行完了,通知sub3执行
condition3.signal();
}finally{
lock.unlock();
}
}
public void sub3(int i){
lock.lock();
try{
while(shouldSub!=3){//没轮到sub3执行,就等待
try {
condition3.await();
} catch (Exception e) {
e.printStackTrace();
}
}
for(int j=1;j<=4;j++){
System.out.println("sub3 thread sequence of " + j + ",loop of " + i);
}
shouldSub = 1;//sub2 执行完了,通知主线程执行
condition1.signal();
}finally{
lock.unlock();
}
}
public void main(int i){
lock.lock();
try{
while(shouldSub!=1){//没轮到主线程执行,就等待
try {
condition1.await();
} catch (Exception e) {
e.printStackTrace();
}
}
for(int j=1;j<=5;j++){
System.out.println("main thread sequence of " + j + ",loop of " + i);
}
shouldSub = 2;//主线程执行完了,通知sub2执行
condition2.signal();
}finally{
lock.unlock();
}
}
}
}