问题描述
当我为变量分配一个新值时,它在 start() 后不会改变,但是在我使用 join() 后它会改变。为什么会发生这种情况,在这种情况下,int a 是否应该是 volatile?
class SampleThread extends Thread {
private static int a = 0;
@Override
public void run() {
a = 3;
}
public static void main(String[] args) throws InterruptedException {
Thread t2 = new Thread(new SampleThread());
t2.start();
System.out.println(a);
}
}
解决方法
要查看发生了什么,请尝试以下操作:
...
@Override
public void run() {
System.out.println("start of Thread");
a = 3;
System.out.println("end of Thread");
}
...
只更改了 run
方法,其余代码不变
是的,它需要 volatile。
每个线程都有一个邪恶的硬币。线程在读取或写入字段时翻转硬币:Heads,并且线程使用它自己的本地(对线程)它的副本;如果写入,该更新根本不会反映到所有其他线程,他们仍然会看到“旧”值,如果读取,同样的处理:读取它所拥有的任何内容,即使其他线程已经更新了它。即使他们在一小时前这样做了。 Tails,它确实刷新了其他线程对这个东西的看法,不会使用本地副本。
硬币是邪恶的:它不是一个公平的硬币。它在今天的每个时间、明天的每个时间以及在测试套件期间的每个时间都可以使用,并且在整个那一周内,它都可以为早期采用的客户提供服务。然后 juuust 当那个大客户进来时,你正在提供演示?它每次都会翻转以破坏您的应用程序。那种邪恶。
因此,您必须消除所有抛硬币的情况,或者至少确保抛硬币的结果不会对您的应用产生任何影响。
这样做的方法是建立先于关系。在 VM 执行的任意两行 java 代码之间,有一组规则来确定这两行是否有这样的关系:保证一个接一个运行。这不是关于他们是否做了(他们跑的时间戳完全无关),而是 Java Memory Model 是否规定这种关系存在。
如果是,则没有硬币被翻转:任何“JMM 之前出现的线”所做的任何事情,对于后面的线肯定是可见的。但如果 JMM 没有明确说明这种关系存在,抛硬币,你就输了。
一个微不足道的“先于”关系在单个线程中:x = 5; System.out.println(x);
微不足道地具有这样的关系;他们在同一个线程中运行,一个接一个。那是免费赠品。
但是在线程之间,天哪。您需要 synchronized
、volatile
或调用代码在内部执行这些操作或具有其他机制来确保它(提示:java.util 中有很多很棒的东西) .concurrent 包,顾名思义,它通常以非常有效的方式线程安全。例如,AtomicInteger
几乎总是比 volatile int
好得多,并且可以做更多的事情,例如 CAS操作,这是 volatile int 不能做的。
如果你希望不同线程中的事情以特定的顺序发生——在这种情况下,a = 2
在 `System.out.println(a)' 之前执行——那么你必须编写代码来执行该命令。
在这种微不足道的情况下,没有真正的工作被完成,几乎任何你能做的事情都会使线程的使用变得毫无意义。主线程可以“加入”将 a
设置为 2 的线程,但是您所获得的只是一种昂贵的方式来执行可以在单个线程中执行的代码。
主线程应该等待另一个被执行 一种解决方案是使用 join()
class SampleThread extends Thread {
private static int a = 0;
@Override
public void run() {
a = 3;
}
public static void main(String[] args) throws InterruptedException {
Thread t2 = new Thread(new SampleThread());
t2.start();
// wait for completion of t2
t2.join()
System.out.println(a);
}
}