我有以下代码:-
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应该由主线程打印?
发布于 2022-02-24 05:18:25
每个对象都有一个内部锁,线程可以使用同步方式获取该锁。当线程无法执行任何操作时,线程调用等待,直到某些事情发生变化(例如,它可能试图插入到当前已满的有界队列中),它调用等待对象,该对象的锁是通过同步获得的。当主线程调用b.wait()时,这意味着主线程是处于休眠状态的线程。
当代码等待注释掉时,ThreadB线程仍在启动过程中,主线程可以接受锁,然后在ThreadB获得锁之前释放锁并打印总计,此时锁的总数仍为0。从技术上讲,这是一场比赛,并不能保证哪个线程是第一个线程,但是主线程有一个很好的领先优势。
当代码使用等待时,主线程获取ThreadB上的锁(再次领先于ThreadB),然后等待直到收到通知为止。当线程终止时,它会导致调度程序通知其等待集中的任何线程,所以当threadB完成时,主线程会唤醒并继续执行。由于ThreadB已经完成,到主线程开始打印它时,总数为10。
如果主线程在ThreadB之前没有得到锁,那么(因为ThreadB在它运行的整个过程中一直持有锁),直到ThreadB完成之后,它才能获得锁。这意味着,在即将死亡的线程发送通知时,它不在等待中,而是会在稍后等待,并且不会有任何通知来唤醒它并将其挂起。
这种问题--导致丢失通知和线程挂起的竞赛--可能在等待/通知被滥用时发生。有关使用等待和通知的正确方式,请阅读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方法。建议应用程序不要对线程实例使用等待、通知或notifyAll。
注意对线程对象进行同步的警告。它不能很好地处理JDK代码,比如锁定线程的join代码。
发布于 2022-02-28 01:03:01
我很困惑为什么我总是得到10的答案。有两个线程,ThreadB和主线程,所以当我执行等待方法时,ThreadB应该按照定义等待,不应该添加值,因此0应该由主线程打印?
由于Java线程终止的方式,您得到了一个结果。当Thread完成时,会通知Thread对象本身。因此,wait()方法会导致线程等待后台线程的终止。join()方法是通过调用wait()实现的,您应该直接使用join()而不是wait()。
如果对等待行进行注释,则输出始终为0。
如果您没有wait()行,那么主线程很可能会在b线程启动之前完成并得到b的值。启动线程需要一些时间,您需要使用b.join()来确保等待b线程完成,并与其他线程所做的任何内存更改同步,在本例中为b.total。
如果您放置一个睡眠而不是等待,那么即使0线程已经将其设置为10,您仍然可能会看到它的值,因为没有同步内存的东西,并且可能缓存0的b.total值。join()方法等待线程完成并同步内存,以便您能够看到线程的结果。
https://stackoverflow.com/questions/71247244
复制相似问题