我尝试按特定顺序在三个不同的Threads
中的单个实例上执行三个方法。那个methodA
应该在methodB
和methodB
methodC
之前调用。
我最初尝试过一种不复杂的代码方法:
public void testFoo()
{
Foo foo = new Foo();
ExecutorService service = Executors.newFixedThreadPool(3);
service.submit(() -> foo.callMethodB());
service.submit(() -> foo.callMethodC());
service.submit(() -> foo.callMethodA());
service.shutdown();
}
class Foo
{
boolean methodAcompleted;
boolean methodBcompleted;
void callMethodA()
{
this.methodA();
methodAcompleted = true;
}
void callMethodB()
{
while (true)
{
if (methodAcompleted)
{
this.methodB();
methodBcompleted = true;
break;
}
}
}
void callMethodC()
{
while (true)
{
if (methodBcompleted)
{
this.methodC();
break;
}
}
}
void methodA()
{
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Method A completed!");
}
void methodB()
{
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Method B completed!");
}
void methodC()
{
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Method C completed!");
}
}
我进入了以下奇怪的观察:
1)仅打印"Method A completed!"
执行上述代码。即使我将布尔变量更改为static
,我也会得到相同的结果。
2)如果我将布尔变量更改为volatile
,则所有语句都按正确的顺序打印:
"Method A completed!"
"Method B completed!"
"Method C completed!"
据我所知,这是预期的,因为无法保证(或不可能?)Thread
通知同一实例上的另一个Thread
操作的更改。即使在static
个变量上执行了更改,它也是如此吗?
3)当我从运行它们的方法中删除延迟Thread.sleep()
时会变得很奇怪:
void methodA()
{
System.out.println("Method A completed!");
}
void methodB()
{
System.out.println("Method B completed!");
}
void methodC()
{
System.out.println("Method C completed!");
}
在这种情况下,语句总是以正确的顺序打印,如案例2:
"Method A completed!"
"Method B completed!"
"Method C completed!"
Thread
可以/应该看到另一个Thread
对同一个非volatile
变量的更改吗? case 1
和case 3
之间不同行为/结果的解释是什么?
更新
我认为如果上述方法不可或缺,最好的解决方案是使用AtomicBoolean
代替原语:
class Foo {
AtomicBoolean methodAcompleted = new AtomicBoolean(false);
AtomicBoolean methodBcompleted = new AtomicBoolean(false);
...
}
我被说服atomic types
的目的是保证在这些情况下的一致行为。
答案 0 :(得分:2)
您需要一个内存屏障来阻止JIT内联boolean
字段的值。
JIT可以检测到你没有在当前线程中更改你的标志并且可以内联它,防止你看到变化。
如果添加volatile
或synchronized
方法(即使是空的同步块),也会阻止此优化。注意:System.out.println已同步。
线程可以/应该看到来自另一个线程的更改是否作用于相同的非易失性变量?
它可以并且将看到其他类型的更改,因为此优化可能不适用于其他类型(取决于您的JVM实现)
此外,如果您在JIT优化代码之前更改了标志,您将看到更改。
几年前我写了一篇关于此的文章,其中有更多细节http://vanillajava.blogspot.co.uk/2012/01/demonstrating-when-volatile-is-required.html