了解线程Java中的等待



我有以下代码:-

class ThreadB extends Thread {
int total;
@Override public void run() {
synchronized (this){
for(int i=0; i<5; i++){
total+=i;
}
}
}
}
public class Solution {
public static void main(String[] args) throws InterruptedException {
ThreadB b = new ThreadB();
b.start();
synchronized (b){
b.wait();
}
System.out.println(b.total);
}
}

每当我运行这个时,我的输出都是10。如果我评论等待行,我得到的输出总是0。

我很困惑,为什么我的答案总是10。有两个线程,ThreadB和主线程,所以当我执行等待方法时,ThreadB应该根据定义等待,并且不应该添加值,因此主线程应该打印0?

每个对象都有一个内部锁,线程可以使用synchronized获取该锁。线程在无法执行任何操作时调用wait,直到发生更改(例如,它可能正在尝试插入到当前已满的有界队列中(,它对它使用synchronized获取其锁的对象调用wait。当主线程调用b.wait((时,意味着主线程是处于休眠状态的线程。

当代码等待注释结束时,ThreadB线程仍在启动过程中,主线程可以获取锁,然后释放锁并打印总数,然后ThreadB才能获取锁,此时总数仍为0。从技术上讲,这是一场比赛,不能保证哪个线程先发,但主线程有一个良好的领先优势。

当代码使用wait时,主线程获取ThreadB上的锁(再次提前于ThreadB(,然后等待,直到收到通知。当线程终止时,它会导致调度程序通知其等待集中的任何线程,因此当线程B完成时,它将导致主线程唤醒并从那里继续。由于ThreadB已经完成,到主线程开始打印时,总数为10。

如果主线程在ThreadB之前没有获得锁,那么(因为ThreadB在运行的整个时间里都保持着锁(直到ThreadB完成后才能获得锁。这意味着,当垂死的线程发送通知时,它不在等待集中,但稍后会等待,并且不会有任何通知来唤醒它,它会挂起。

当wait/notify被滥用时,可能会发生这种问题——导致通知丢失和线程挂起的竞争。要获得正确的使用方法,请等待并通知阅读https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html.

如果您特别希望一个线程等待另一个线程完成,那么thread有一个名为join的实例方法用于此方法,主线程在这里会像一样调用此方法

b.join();

顺便说一句,终止通知行为记录在java.lang.Thread#join的api文档中,它说:

此实现使用This.wait调用的循环,该调用以This.isAlive为条件。当线程终止This.notifyAll方法时,调用该方法。建议应用程序不要在线程实例上使用wait、notify或notifyAll。

注意不要在线程对象上同步的警告。对于像join这样锁定线程的JDK代码来说,这并不好用。

我很困惑,为什么我的答案总是10。有两个线程,ThreadB和主线程,所以当我执行等待方法时,ThreadB应该根据定义等待,并且不应该添加值,因此主线程应该打印0?

由于Java线程的终止方式,您得到了一个结果。当Thread结束时,会通知Thread对象本身。因此,wait()方法会导致线程等待后台线程的终止。join()方法是通过调用wait()来实现的,您应该直接使用join()而不是wait()

如果我对等待行进行注释,我将始终得到0的输出。

如果没有wait()行,那么主线程很可能会在启动b线程之前完成并获得b.total的值。启动线程需要一点时间,您需要使用b.join()来确保等待b线程完成以与其他线程所做的任何内存更改同步,在本例中为b.total

如果您设置睡眠而不是等待,即使b线程已经将其设置为10,您仍然可能看到0的值,因为没有任何东西正在同步内存,并且b.total的值0可能会被缓存。join()方法等待线程完成同步内存,以便您可以看到线程的结果。

最新更新