我们知道,在声明为volatile之前,long和double赋值在Java中不是原子的。我的问题是它在我们的编程实践中是如何真正重要的。 例如,如果您看到下面的类,其对象在多个线程之间共享。
/**
* The below class is not thread safe. the assignments to int values would be
* atomic but at the same time it not guaranteed that changes would be visible to
* other threads.
**/
public final class SharedInt {
private int value;
public void setValue(int value) {
this.value = value;
}
public int getValue() {
return this.value;
}
}
现在考虑另一个SharedLong
/**
* The below class is not thread safe because here the assignments to long
* are not atomic as well as changes are not
* guaranteed to be visible to other threads.
*/
public final class SharedLong {
private long value;
public void setValue(long value) {
this.value = value;
}
public long getValue() {
return this.values;
}
}
现在我们可以看到上述两个版本都不是线程安全的。在int
的情况下,这是因为线程可能会看到整数的陈旧值。如果是long
,他们可以看到长变量的损坏和陈旧值。
在这两种情况下,如果实例不在多个线程之间共享,那么这些类是安全的。
为了使上述类的线程安全,我们需要声明 int和long都是volatile或使方法同步。
这让我想知道:如果long
和double
的赋值在我们的正常编程过程中不是原子的,那么真正重要的是因为两者都需要声明为volatile或者为多线程访问同步所以我的问题是< strong>长期分配不是原子的事实会产生什么影响?
答案 0 :(得分:8)
我刚刚做了一个很酷的小例子
public class UnatomicLong implements Runnable {
private static long test = 0;
private final long val;
public UnatomicLong(long val) {
this.val = val;
}
@Override
public void run() {
while (!Thread.interrupted()) {
test = val;
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new UnatomicLong(-1));
Thread t2 = new Thread(new UnatomicLong(0));
System.out.println(Long.toBinaryString(-1));
System.out.println(pad(Long.toBinaryString(0), 64));
t1.start();
t2.start();
long val;
while ((val = test) == -1 || val == 0) {
}
System.out.println(pad(Long.toBinaryString(val), 64));
System.out.println(val);
t1.interrupt();
t2.interrupt();
}
// prepend 0s to the string to make it the target length
private static String pad(String s, int targetLength) {
int n = targetLength - s.length();
for (int x = 0; x < n; x++) {
s = "0" + s;
}
return s;
}
}
一个帖子不断尝试将0
分配给test
,而另一个尝试分配-1
。最终你会得到一个0b1111111111111111111111111111111100000000000000000000000000000000
或者0b0000000000000000000000000000000011111111111111111111111111111111
的数字。
(假设你不在64位JVM上。大多数,如果并非所有64位JVM实际上都会对long
和double
进行原子分配。)
答案 1 :(得分:3)
如果使用int
进行不正确的编程可能会导致观察到陈旧的值,那么使用long
进行不正确的编程可能会导致从未实际存在的值被观察到。
理论上,这对于只需要最终正确且不是时间点正确的系统来说很重要,因此跳过性能同步。虽然为了表现而跳过一个不稳定的字段声明似乎是偶然的检查,如愚蠢。
答案 2 :(得分:2)
如果同时访问SharedInt或SharedLong会有所不同。正如你所说,一个线程可能会读取陈旧的int,或陈旧或损坏的长。
如果该值用于引用数组,这可能很重要。
或在GUI中显示。
如何通过网络写入一些值并发送错误数据。现在客户感到困惑或崩溃。
可以将不正确的值存储到数据库中。
重复计算可能已损坏......
正如您在评论中所要求的那样,具体而言:
长值经常用于时间计算。这可能会导致您在执行某些操作之前等待一段时间的循环,例如网络应用程序中的心跳。
您可以向客户报告与您同步时钟的时间过去是80年或1000年。
longs和int通常用于bitpacked字段以指示许多不同的东西。你的旗帜将完全被破坏。
Longs经常被用作唯一ID。这可能会破坏您正在创建的哈希表。
显然很多坏事,坏事都可能发生。如果此值需要是线程安全的,并且您希望软件非常可靠,请声明这些变量是volatile,使用Atomic变量,或同步访问和设置方法。