最近我接受了一次面试测试,并且对这个问题摸不着头脑。我会把我解释的内容和我想知道的是什么是正确的行为。我想确保我的理解是正确的,不是为了采访而是为了变得更好。
** Que:**我们在下面有一个计数器(java代码),如果说20个线程并行运行这个代码,那么a和b的值是否相同?如果b不是勇敢的怎么办
我的回答:可能会也可能不会。为什么 - 当存在竞争条件时,不能保证不同的线程将看到b或a的更新值,在这种情况下,值将是不同的。在这里,我不认为挥发性有任何区别。
我编写了一个简单的客户端,并在16核心笔记本电脑上运行此代码,线程数达到50,当尝试运行500次时,我可以看到a和b的相同值。但是,当我将线程数增加到200时,有时我会看到不同的值。
问题 - 对此有何正确答案,我的理解是否正确?为什么我的笔记本电脑上看到了不同的结果?
public class VolatileCounter implements Runnable {
private static int a;
private volatile static int b;
public VolatileCounter() {
}
@Override
public void run() {
try{
b++;
a++;
}finally {
//some system println....
}
}
public void printNumbers() {
System.out.println(b);
System.out.println(a);
}
}
答案 0 :(得分:1)
即使有50个线程,该值也可能不同,所有这些都取决于竞争条件和上下文切换。由于a和b是原始类型,因此它是否是易失性无关紧要
答案 1 :(得分:1)
大多数问题是增量运算符由于采取了多个步骤而不是线程安全的:首先它们必须获取当前值,然后递增,然后使用新值更新字段,为其他线程提供机会交错操作和混淆结果。见Is the pre-increment operator thread-safe?。 (如果我向他们展示这样的话,这将是我希望听到我采访的人的主要观点。)
一些观察结果:
仅仅因为并发问题可以发生并不意味着它必然会发生。提高并发性水平确实会给问题带来更多机会,这就是你所观察到的。
这并不是说内存可见性不起作用,在本例中很难将其与非线程安全的操作符行为区分开来。如果没有volatile关键字,那么更新是否可见性是JVM实现的摆布。 PC上的JVM通常是相当宽容的。其他平台可能不是。
如果在代码中包含printlns,则会在控制台上获得锁定,以便它们可以影响多线程行为和更新的可见性。
有一种称为搭载的内存可见性技巧也可能起作用,影响您的类变量a
的更新是否可见,请参阅:Volatile piggyback. Is this enough for visiblity?
答案 2 :(得分:1)
如果你开始这样的50个线程,启动线程需要很长时间,以至于它们很可能不会同时运行。如果它们确实碰巧同时运行,您可能会发现在任何一种情况下这些数字都略低于预期。
注意:使用volatile
字段不会影响字段b
,但它也影响b
之后的所有内存访问,即它也影响{{1} }}
增量操作实际上是一个加载,增量和存储,因此实际上它不是线程安全的。