首页 > 编程知识 正文

java 多线程通信,查看java进程中的线程

时间:2023-05-05 10:44:49 阅读:59160 作者:2704

线程通信的目标是在线程之间发送信号。 另一方面,线程通信允许线程等待来自其他线程的信号。

例如,线程b可以等待线程a的信号,该信号通知线程b数据已经准备好。 本文介绍了以下JAVA线程间通信主题:

1、共享对象通信

在线程之间发送信号的一种简单方法是为共享对象的变量设置信号值。 线程a在一个同步块中将布尔型成员变量hasDataToProcess设置为true,线程b也将名为hasDataToProcess的成员变量读取到同步块中。 此简单示例使用具有信号的对象,并提供set和check方法:

公共类我的信号{

protectedbooleanhasdatatoprocess=false;

publicsynchronizedbooleanhasdatatoprocess (

return this.hasDataToProcess;

}

publicsynchronizedvoidsethasdatatoprocess (蓝牙) {

this.hasDataToProcess=hasData;

}

}

线程a和b必须获取对MySignal共享实例的引用以进行通信。 如果它们所具有的引用指向不同的MySingal实例,则无法检测到彼此的信号。 需要处理的数据可以存储在与MySignal实例不同的共享缓存中。

2、忙碌等待(Busy Wait ) ) ) ) ) ) )。

准备处理数据的线程b正在等待数据可用。 也就是说,hasDataToProcess ) )正在等待线程a将返回true的信号。 线程b在循环中运行,并等待此信号。

保护性my signal shared signal=.

.

while (! sharedSignal.hasDataToProcess (

//do nothing. busy waiting

}

3、等待()、通知()、通知全部) )。

除非平均等待时间非常短,否则繁忙等待没有有效利用运行等待线程的CPU。 否则,将等待的线程置于休眠或非运行状态更明智。 等到接收到等待中的信号。

Java有一个内置的等待机制,允许线程在等待信号时进入非运行状态。 java.lang.Object类定义了三个方法: wait (、notify )和notifyAll )以实现此侦听机制。

一个线程调用任何对象的wait ) )方法,然后处于非运行状态,直到另一个线程调用同一对象的notify ) )方法。 为了调用wait (或notify ),线程必须首先获取该对象的锁定。 这意味着线程必须在同步块中调用wait (或notify )。 以下是使用wait (和notify )的mywaitnotify mysingal ——修订版:

公共类监视器对象{

}

公共类mywaitnotify {

monitorobjectmymonitorobject=newmonitorobject (;

公共语音等待(

同步(mymonitorobject ) {

try{

myMonitorObject.wait (;

}catch(interruptedexceptione )…}

}

}

公共语音待办事项通知(

同步(mymonitorobject ) {

myMonitorObject.notify (;

}

}

}

等待线程调用doWait (,唤醒线程调用doNotify )。 线程调用对象的notify )方法时,等待该对象的所有线程中有一个线程将被唤醒并允许执行(校注:被唤醒的线程是随机的,尽管它还提供了notifyAll ) )方法,用于启动等待特定对象的所有线程。

如您所见,无论是等待线程还是唤醒线程,都在同步块中调用wait () )和notify () )。 这是强制性的! 如果线程没有对象锁,则不能调用wait (、notify )或notifyAll )。 否则,抛出IllegalMonitorStateException异常。

(校注: JVM是这样实施的。 badxbc在调用wait时,首先检查当前线程是否是锁定的所有者,否则抛出IllegalMonitorStateExcept以引用JVM源代码中的1422行。 )

但这怎么可能呢? 等待线程在同步块中

面执行的时候,不是一直持有监视器对象(myMonitor对象)的锁吗?等待线程不能阻塞唤醒线程进入doNotify()的同步块吗?答案是:的确不能。一旦线程调用了wait()方法,它就释放了所持有的监视器对象上的锁。这将允许其他线程也可以调用wait()或者notify()。

一旦一个线程被唤醒,不能立刻就退出wait()的方法调用,直到调用notify()的线程退出了它自己的同步块。换句话说:被唤醒的线程必须重新获得监视器对象的锁,才可以退出wait()的方法调用,因为wait方法调用运行在同步块里面。如果多个线程被notifyAll()唤醒,那么在同一时刻将只有一个线程可以退出wait()方法,因为每个线程在退出wait()前必须获得监视器对象的锁。

4、丢失的信号(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前,设置自己已经被通知过。在wait后,设置自己没有被通知过,需要等待通知。)

5、假唤醒

由于莫名其妙的原因,线程有可能在没有调用过notify()和notifyAll()的情况下醒来。这就是所谓的假唤醒(spurious wakeups)。无端端地醒过来了。

如果在MyWaitNotify2的doWait()方法里发生了假唤醒,等待线程即使没有收到正确的信号,也能够执行后续的操作。这可能导致你的应用程序出现严重问题。

为了防止假唤醒,保存信号的成员变量将在一个while循环里接受检查,而不是在if表达式里。这样的一个while循环叫做自旋锁(校注:这种做法要慎重,目前的JVM实现自旋会消耗CPU,如果长时间不调用doNotify方法,doWait方法会一直自旋,CPU会消耗太大)。被唤醒的线程会自旋直到自旋锁(while循环)里的条件变为false。以下MyWaitNotify2的修改版本展示了这点:

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();

}

}

}

留意wait()方法是在while循环里,而不在if表达式里。如果等待线程没有收到信号就唤醒,wasSignalled变量将变为false,while循环会再执行一次,促使醒来的线程回到等待状态。

6、多个线程等待相同信号

如果你有多个线程在等待,被notifyAll()唤醒,但只有一个被允许继续执行,使用while循环也是个好方法。每次只有一个线程可以获得监视器对象锁,意味着只有一个线程可以退出wait()调用并清除wasSignalled标志(设为false)。一旦这个线程退出doWait()的同步块,其他线程退出wait()调用,并在while循环里检查wasSignalled变量值。但是,这个标志已经被第一个唤醒的线程清除了,所以其余醒来的线程将回到等待状态,直到下次信号到来。

7、不要在字符串常量或全局对象中调用wait()

(校注:本章说的字符串常量指的是值为常量的变量)

本文早期的一个版本在MyWaitNotify例子里使用字符串常量(”")作为直率的日记本对象。以下是那个例子:

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();

}

}

}

在空字符串作为锁的同步块(或者其他常量字符串)里调用wait()和notify()产生的问题是,JVM/编译器内部会把常量字符串转换成同一个对象。这意味着,即使你有2个不同的MyWaitNotify实例,它们都引用了相同的空字符串实例。同时也意味着存在这样的风险:在第一个MyWaitNotify实例上调用doWait()的线程会被在第二个MyWaitNotify实例上调用doNotify()的线程唤醒。这种情况可以画成以下这张图:

起初这可能不像个大问题。毕竟,如果doNotify()在第二个MyWaitNotify实例上被调用,真正发生的事不外乎线程A和B被错误的唤醒了 。这个被唤醒的线程(A或者B)将在while循环里检查信号值,然后回到等待状态,因为doNotify()并没有在第一个MyWaitNotify实例上调用,而这个正是它要等待的实例。这种情况相当于引发了一次假唤醒。线程A或者B在信号值没有更新的情况下唤醒。但是代码处理了这种情况,所以线程回到了等待状态。记住,即使4个线程在相同的共享字符串实例上调用wait()和notify(),doWait()和doNotify()里的信号还会被2个MyWaitNotify实例分别保存。在MyWaitNotify1上的一次doNotify()调用可能唤醒MyWaitNotify2的线程,但是信号值只会保存在MyWaitNotify1里。

问题在于,由于doNotify()仅调用了notify()而不是notifyAll(),即使有4个线程在相同的字符串(空字符串)实例上等待,只能有一个线程被唤醒。所以,如果线程A或B被发给C或D的信号唤醒,它会检查自己的信号值,看看有没有信号被接收到,然后回到等待状态。而C和D都没被唤醒来检查它们实际上接收到的信号值,这样信号便丢失了。这种情况相当于前面所说的丢失信号的问题。C和D被发送过信号,只是都不能对信号作出回应。

如果doNotify()方法调用notifyAll(),而非notify(),所有等待线程都会被唤醒并依次检查信号值。线程A和B将回到等待状态,但是C或D只有一个线程注意到信号,并退出doWait()方法调用。C或D中的另一个将回到等待状态,因为获得信号的线程在退出doWait()的过程中清除了信号值(置为false)。

看过上面这段后,你可能会设法使用notifyAll()来代替notify(),但是这在性能上是个坏主意。在只有一个线程能对信号进行响应的情况下,没有理由每次都去唤醒所有线程。

所以:在wait()/notify()机制中,不要使用全局对象,字符串常量等。应该使用对应唯一的对象。例如,每一个MyWaitNotify3的实例(前一节的例子)拥有一个属于自己的监视器对象,而不是在空字符串上调用wait()/notify()。

校注:

<

p>直率的日记本 (英语:Monitors,也称为监视器) 是对多个工作线程实现互斥访问共享资源的对象或模块。这些共享资源一般是硬件设备或一群变量。直率的日记本实现了在一个时间点,最多只有一个线程在执行它的某个子程序。与那些通过修改数据结构实现互斥访问的并发程序设计相比,直率的日记本很大程度上简化了程序设计。

<

p>

原文 Thread Signaling

译者:jddlt 校对:方腾飞

via ifeve

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。