您的位置:首页 > 编程语言 > Java开发

Java多线程㈤—②线程间通讯(wait、notify、notifyAll)

2015-02-13 09:35 591 查看
Object对线程操作的方法有五个

void

notify
()


唤醒在此对象监视器上等待的单个线程。

void

notifyAll
()


唤醒在此对象监视器上等待的所有线程。

void

wait
()


在其他线程调用此对象的
notify()
方法或
notifyAll()
方法前,导致当前线程等待。

void

wait
(long timeout)


在其他线程调用此对象的
notify()
方法或
notifyAll()
方法,或者超过指定的时间量前,导致当前线程等待。

void

wait
(long timeout, int nanos)


在其他线程调用此对象的
notify()
方法或
notifyAll()
方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量前,导致当前线程等待。

为什么操作线程的方法wait(),notify() ,notifyAll()定义在了Object类中?

因为这些方法是监视器的方法。监视器其实就是锁。

锁可以是任意的对象,任意的对象调用的方式一定定义在Object类中。

方法详解:

1、wait()

public finalvoid wait() throwsInterruptedException,IllegalMonitorStateException

wait导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。当前的线程必须拥有此对象监视器。该线程发布对此监视器的所有权并等待,直到其他线程通过调用 notify 方法,或 notifyAll 方法通知在此对象的监视器上等待的线程醒来。然后该线程将等到重新获得对监视器的所有权后才能继续执行。

IllegalMonitorStateException
- 如果当前线程不是此对象监视器的所有者。

InterruptedException
- 如果在当前线程等待通知之前或者正在等待通知时,任何线程中断了当前线程。在抛出此异常时,当前线程的中断状态被清除。

2、notify()

public final nativevoid notify() throws IllegalMonitorStateException

该方法也要在同步方法或同步块中调用,即在调用前,线程也必须要获得该对象的对象级别锁,的如果调用notify()时没有持有适当的锁,也会抛出IllegalMonitorStateException。

该方法用来通知那些可能等待该对象的对象锁的其他线程。如果有多个线程等待,则线程规划器任意挑选出其中一个wait()状态的线程来发出通知,并使它等待获取该对象的对象锁(notify后,当前线程不会马上释放该对象锁,wait所在的线程并不能马上获取该对象锁,要等到程序退出synchronized代码块后,当前线程才会释放锁,wait所在的线程也才可以获取该对象锁),但不惊动其他同样在等待被该对象notify的线程们。当第一个获得了该对象锁的wait线程运行完毕以后,它会释放掉该对象锁,此时如果该对象没有再次使用notify语句,则即便该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,会继续阻塞在wait状态,直到这个对象发出一个notify或notifyAll。这里需要注意:它们等待的是被notify或notifyAll,而不是锁。这与下面的notifyAll()方法执行后的情况不同。

3、notifyAll()

public final nativevoid notifyAll() throws IllegalMonitorStateException

该方法与notify()方法的工作方式相同,重要的一点差异是:

notifyAll使所有原来在该对象上wait的线程统统退出wait的状态(即全部被唤醒,不再等待notify或notifyAll,但由于此时还没有获取到该对象锁,因此还不能继续往下执行),变成等待获取该对象上的锁,一旦该对象锁被释放(notifyAll线程退出调用了notifyAll的synchronized代码块的时候),他们就会去竞争。如果其中一个线程获得了该对象锁,它就会继续往下执行,在它退出synchronized代码块,释放锁后,其他的已经被唤醒的线程将会继续竞争获取该锁,一直进行下去,直到所有被唤醒的线程都执行完毕。

4、wait(long)和wait(long,int)

显然,这两个方法是设置等待超时时间的,后者在超值时间上加上ns,精度也难以达到,因此,该方法很少使用。对于前者,如果在等待线程接到通知或被中断之前,已经超过了指定的毫秒数,则它通过竞争重新获得锁,并从wait(long)返回。另外,需要知道,如果设置了超时时间,当wait()返回时,我们不能确定它是因为接到了通知还是因为超时而返回的,因为wait()方法不会返回任何相关的信息。但一般可以通过设置标志位来判断,在notify之前改变标志位的值,在wait()方法后读取该标志位的值来判断,当然为了保证notify不被遗漏,我们还需要另外一个标志位来循环判断是否调用wait()方法。

深入理解:

如果线程调用了对象的wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。

当有线程调用了对象的notifyAll()方法(唤醒所有wait线程)或notify()方法(只随机唤醒一个wait线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。

优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了synchronized代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。

问题解决一:加入等待与唤醒

如果想让生产者不重复生产,消费者不重复取走,则可以增加一个标志位,假设标志位为boolean型变量,如果标志位的内容为true,则表示可以生产,但是不能取走,此时程序执行到消费者线程则应该是等待;如果标志位的内容为flase,则表示可以取走,但是不能生产,如果生产者线程运行,则应该等待,流程如下图:



代码实例:

class Resource{
private String husband;
private String wife;
private boolean flag = false;

public synchronizedvoid set(String husband,String wife){
if(flag)
try{this.wait();}catch(InterruptedException e){}
this.husband = husband;
this.wife = wife;
flag = true;
this.notify();
}
public synchronizedvoid get(){
if(!flag)
try{this.wait();}catch(InterruptedException e){}
System.out.println(husband+"----------------"+wife);
flag = false;
this.notify();
}
}

//生产
class Producer implements Runnable{
private boolean flag = true;
private Resource r;
public Producer (Resource r){
this.r = r;
}
publicvoid run(){
while(true){
if(flag){
r.set("郭靖","黄蓉");
flag = false;
}else{
r.set("董永","七仙女");
flag = true;
}
}
}
}

//消费
class Consumer implements Runnable{
private Resource r;
public Consumer (Resource r){
this.r = r;
}
publicvoid run(){
while(true){
r.get();
}
}
}

public class ThreadCommunication3{
public staticvoid main(String[] args){
//创建资源。
Resource r = new Resource();
//创建任务。
Producer p = new Producer (r);
Consumer c = new Consumer (r);
//创建线程,执行路径。
Thread t1 = new Thread(p);
Thread t2 = new Thread(c);
//开启线程
t1.start();
t2.start();
}
}





从程序的运行结果中可以清楚地发现,生产者每生产一个就要等消费者取走,消费者每取走一个就要等待生产者生产,这样避免了重复生产和重复取走的问题。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐