5

我们知道,long 和 double 赋值在 Java 中不是原子的,除非它们被声明为 volatile。我的问题是它在我们的编程实践中真的很重要。例如,如果您看到下面的类,其对象在多个线程之间共享。

/**
*  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,这是因为线程可能会看到过时的整数值。在 if 的情况下long,他们可以看到 long 变量的损坏和陈旧值。

在这两种情况下,如果一个实例没有在多个线程之间共享,那么这些类是安全的

为了使上述类线程安全,我们需要将int 和 long 都声明为 volatile 或使方法同步long这让我想知道:在我们正常的编程过程中,如果分配和不是原子的,这真的很重要double,因为两者都需要声明为易失的或同步的以进行多线程访问,所以我的问题是什么情况下,长分配的事实是不是原子的可能会有所作为?

4

3 回答 3

11

不久前我做了一个很酷的小例子

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;
    }
}

一个线程不断尝试分配给0test而另一个尝试分配-1。最终你会得到一个要么 要么 的0b1111111111111111111111111111111100000000000000000000000000000000
数字
0b0000000000000000000000000000000011111111111111111111111111111111
(假设您不在 64 位 JVM 上。大多数(如果不是全部)64 位 JVM 实际上会为longs 和doubles 进行原子分配。)

于 2013-07-05T04:55:50.393 回答
3

如果使用 a 进行不当编程int可能会导致观察到陈旧的值,而使用 a 进行不当编程long可能会导致观察到从未实际存在的值。

从理论上讲,这对于只需要最终正确而不是时间点正确的系统来说可能很重要,因此跳过了同步以提高性能。尽管为了性能而跳过一个 volatile 字段声明在随意检查时似乎很愚蠢。

于 2013-07-05T04:52:46.993 回答
2

如果要同时访问 SharedInt 或 SharedLong 会有所不同。正如您所说,一个线程可能会读取过时的 int,或者过时或损坏的 long。

如果该值用于引用数组,这可能很重要。

或者在 GUI 中显示。

如何通过网络写入一些值并发送错误数据。现在客户感到困惑或崩溃。

不正确的值可以存储到数据库中。

重复计算可能会损坏...

正如您在评论中要求的那样,特别是长期:

长值经常用于时间计算。这可能会导致您在执行某些操作之前等待一段时间的循环,例如网络应用程序中的心跳。

您可以向客户报告与您同步时钟的时间是过去 80 年或 1000 年。

Long 和 int 通常用于位压缩字段以指示许多不同的事物。您的标志将完全损坏。

Long 经常被用作唯一 ID。这可能会损坏您正在创建的哈希表。

显然,可能会发生很多糟糕的、糟糕的事情。如果该值需要线程安全,并且您希望您的软件非常可靠,请将这些变量声明为 volatile,使用 Atomic 变量,或同步访问和设置方法。

于 2013-07-05T04:43:09.400 回答