我有以下代码:-
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()
方法等待线程完成和同步内存,以便您可以看到线程的结果。