在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作。比如说最经典的生产者-消费者模型:当队列满时,生产者需要等待队列有空间才能继续往里面放入商品,而在等待的期间内,生产者必须释放对临界资源(即队列)的占用权。因为生产者如果不释放对临界资源的占用权,那么消费者就无法消费队列中的商品,就不会让队列有空间,那么生产者就会一直无限等待下去。因此,一般情况下,当队列满时,会让生产者交出对临界资源的占用权,并进入挂起状态。然后等待消费者消费了商品,然后消费者通知生产者队列有空间了。同样地,当队列空时,消费者也必须等待,等待生产者通知它队列中有商品了。这种互相通信的过程就是线程间的协作
Java中线程通信协作的最常见的两种方式:
一.syncrhoized加锁的线程的Object类的wait()/notify()/notifyAll()
二.ReentrantLock类加锁的线程的Condition类的await()/signal()/signalAll()
本篇先说第一种方式。
wait()、notify()和notifyAll()是Object类中的方法:
1)wait()、notify()和notifyAll()方法是本地方法,并且为final方法,无法被重写。
2)调用某个对象的wait()方法能让当前线程阻塞,并且当前线程必须拥有此对象的monitor(即锁)
3)调用某个对象的notify()方法能够唤醒一个正在等待这个对象的monitor的线程,如果有多个线程都在等待这个对象的monitor,则只能唤醒其中一个线程;
4)调用notifyAll()方法能够唤醒所有正在等待这个对象的monitor的线程;
wait与notify实现线程间的通信实例:
面试题:子线程循环10次,接着主线程循环100,接着又回到子线程循环10次,接着再回到主线程又循环100,如此循环50次,请写出程序:
package ThreadDemo;
public class ThreadTest {
private static boolean bShouldMain = false;//这里相当于定义了控制该谁执行的一个信号灯
public static void main(String[] args) {
//因为用到了匿名内部类,内部类访问的局部变量需要用final修饰
final Business business = new Business();
new Thread(
new Runnable(){
public void run() {
for(int i=1;i<=50;i++){
//这里使用类的字节码对象作为锁进行同步,但是当需要对同步进行分组时就不科学了
business.sub(i);
}
}
}
).start();
//main方法本身是一个线程,这里是主线程的运行代码
for(int i=1;i<=50;i++){
business.main(i);
}
}
}
class Business {
private boolean bShouldSub = true;//最开始该子线程走
public synchronized void sub(int i){
while(!bShouldSub){//用while而不是if线程醒来还会再次进行判断,防止代码被伪唤醒,代码更健壮。还可以防止生产者消费者问题
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for(int j=1;j<=10;j++){
System.out.println("sub thread sequence of "+j+", loop of "+i);
}
bShouldSub = false;
this.notify();
}
public synchronized void main(int i){
while(bShouldSub){
try {
this.wait();//这里的锁是this
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for(int j=1;j<=100;j++){
System.out.println("main thread sequence of "+j+", loop of "+i);
}
bShouldSub = true;
this.notify();
}
}
经验:
- 要用到共同数据(包括同步锁)或共同算法的若干方法应该归在同一个类身上,这种设计正好体现了高类聚和程序的健壮性
- 锁是上在要操作的资源的类的内部方法中,而不是线程代码中!好处是以后该类交给任何线程自然就同步了,而不需要考虑互斥同步的问题。
- Eclipse中将运行结果保存至文件的操作:Run as-->Run Configuration-->Common-->File处打钩然后选择一个文件