线程通信
为了提高程序的运行效率,我们常常使用多线程进行开发程序,那么多线程之间有哪些方式可以进行通信呢?下文将一一到来,如下所示:
线程A 中设置变量hasDataToProcess的值
线程B 中读取变量hasDataToProcess的值
如下例所示:
例:
java.lang.Object类定义三个方法:wait(),notify(),notifyAll()都可实现这个等待机制
MySingal使用了wait()和notify()的MyWaitNotify
而唤醒线程将调用doNotify()
当一个线程调用一个对象的notify()方法
正在等待该对象的所有线程中将有一个线程被唤醒并允许执行
同时也提供了一个notifyAll()方法来唤醒正在等待一个给定对象的所有线程。
当这两个方法被调用时,有可能没有线程处于等待状态
通知信号过后便丢弃了
即:一个线程先于被通知线程调用wait()前调用了notify(),等待的线程将错过这个信号
不过,在某些情况下,这可能使等待线程永远在等待,不再醒来,因为线程错过了唤醒信号。 为了避免丢失信号,必须把它们保存在信号类里
在MyWaitNotify的例子中,通知信号应被存储在MyWaitNotify实例的一个成员变量里
例:MyWaitNotify的修改版本:
这就是所谓的假唤醒(spurious wakeups) 如果在MyWaitNotify2的doWait()方法里发生了假唤醒,等待线程即使没有收到正确的信号,也能够执行后续的操作。这可能导致你的应用程序出现严重问题
为了防止假唤醒,保存信号的成员变量将在一个while循环里接受检查,而不是在if表达式里。这样的一个while循环叫做自旋锁
被唤醒的线程会自旋直到自旋锁(while循环)里的条件变为false
例:
但只有一个被允许继续执行,使用while循环也是个好方法。每次只有一个线程可以获得监视器对象锁
则只有一个线程可以退出wait()调用并清除wasSignalled标志(设为false)
一旦这个线程退出doWait()的同步块,其他线程退出wait()调用
并在while循环里检查wasSignalled变量值
这个标志已经被第一个唤醒的线程清除了,所以其余醒来的线程将回到等待状态,直到下次信号到来
使用共享对象通信
使用共享变量,使每个线程中都可以访问此变量,如下所示:线程A 中设置变量hasDataToProcess的值
线程B 中读取变量hasDataToProcess的值
如下例所示:
public class MyClass{ protected boolean hasDataToProcess = false; public synchronized boolean hasDataToProcess(){ return this.hasDataToProcess; } public synchronized void setHasDataToProcess(boolean hasData){ this.hasDataToProcess = hasData; } } /* 线程A和线程B同时指向MyClass实例, 此时我们就可以获取其hasDataToProcess或设置setHasDataToProcess达到 hasDataToProcess信息共享的目的 */
忙等待(Busy Wait)
线程B中一直循环等待“线程A中指定值”发生变化,当线程A修改指定值时,则线程B可收到线程A的指令例:
protected MySignal sharedSignal = ... while(!sharedSignal.hasDataToProcess()){ //线程B处于等待状态 }
wait(),notify()和notifyAll()
在Java中有一个内部等待机制来允许线程在等待信号的时候变为非运行状态,如:java.lang.Object类定义三个方法:wait(),notify(),notifyAll()都可实现这个等待机制
当一个线程一旦调用任意对象的wait()方法 就会变为非运行状态 直到另一个线程调用了同一个对象的notify()方法 ------------------------------------------------------------------------------------------------------------ 为了调用wait()或者notify(),线程必须先获得那个对象的锁,即:线程必须在同步块里调用wait()或者notify()例:
MySingal使用了wait()和notify()的MyWaitNotify
public class MonitorObject{ } public class MyWaitNotify{ MonitorObject myMonitorObject = new MonitorObject(); public void doWait(){ synchronized(myMonitorObject){ try{ myMonitorObject.wait(); } catch(InterruptedException e){...} } } public void doNotify(){ synchronized(myMonitorObject){ myMonitorObject.notify(); } } }等待线程将调用doWait()
而唤醒线程将调用doNotify()
当一个线程调用一个对象的notify()方法
正在等待该对象的所有线程中将有一个线程被唤醒并允许执行
同时也提供了一个notifyAll()方法来唤醒正在等待一个给定对象的所有线程。
不管是等待线程还是唤醒线程都在必须在同步块里调用wait()和notify() 一个线程如果没有持有对象锁,将不能调用wait(),notify()或者notifyAll() 如果调用,会抛出IllegalMonitorStateException异常
丢失的信号(Missed Signals)
notify()和notifyAll()方法不会保存调用它们的方法当这两个方法被调用时,有可能没有线程处于等待状态
通知信号过后便丢弃了
即:一个线程先于被通知线程调用wait()前调用了notify(),等待的线程将错过这个信号
不过,在某些情况下,这可能使等待线程永远在等待,不再醒来,因为线程错过了唤醒信号。 为了避免丢失信号,必须把它们保存在信号类里
在MyWaitNotify的例子中,通知信号应被存储在MyWaitNotify实例的一个成员变量里
例:MyWaitNotify的修改版本:
public class MyWaitNotify2{ MonitorObject myMonitorObject = new MonitorObject(); boolean wasSignalled = false; public void doWait(){ synchronized(myMonitorObject){ if(!wasSignalled){ try{ myMonitorObject.wait(); } catch(InterruptedException e){...} } //clear signal and continue running. wasSignalled = false; } } public void doNotify(){ synchronized(myMonitorObject){ wasSignalled = true; myMonitorObject.notify(); } } } /* doNotify()方法在调用notify()前把wasSignalled变量设为true 同时doWait()方法在调用wait()前会检查wasSignalled变量 当没有信号在前一次doWait()调用和这次doWait()调用之间的时间段里被接收到,它将只调用wait() */
假唤醒
线程有可能在没有调用过notify()和notifyAll()的情况下醒来这就是所谓的假唤醒(spurious wakeups) 如果在MyWaitNotify2的doWait()方法里发生了假唤醒,等待线程即使没有收到正确的信号,也能够执行后续的操作。这可能导致你的应用程序出现严重问题
为了防止假唤醒,保存信号的成员变量将在一个while循环里接受检查,而不是在if表达式里。这样的一个while循环叫做自旋锁
被唤醒的线程会自旋直到自旋锁(while循环)里的条件变为false
例:
public class MyWaitNotify3{ MonitorObject myMonitorObject = new MonitorObject(); boolean wasSignalled = false; public void doWait(){ synchronized(myMonitorObject){ while(!wasSignalled){ try{ myMonitorObject.wait(); } catch(InterruptedException e){...} } //clear signal and continue running. wasSignalled = false; } } public void doNotify(){ synchronized(myMonitorObject){ wasSignalled = true; myMonitorObject.notify(); } } }
多个线程等待相同信号
当多个线程在等待,被notifyAll()唤醒,但只有一个被允许继续执行,使用while循环也是个好方法。每次只有一个线程可以获得监视器对象锁
则只有一个线程可以退出wait()调用并清除wasSignalled标志(设为false)
一旦这个线程退出doWait()的同步块,其他线程退出wait()调用
并在while循环里检查wasSignalled变量值
这个标志已经被第一个唤醒的线程清除了,所以其余醒来的线程将回到等待状态,直到下次信号到来
不要在字符串常量或全局对象中调用wait()
public class MyWaitNotify{ String myMonitorObject = ""; boolean wasSignalled = false; public void doWait(){ synchronized(myMonitorObject){ while(!wasSignalled){ try{ myMonitorObject.wait(); } catch(InterruptedException e){...} } //clear signal and continue running. wasSignalled = false; } } public void doNotify(){ synchronized(myMonitorObject){ wasSignalled = true; myMonitorObject.notify(); } } }
版权声明
本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。