长期和双重任务不是原子的 - 它如何重要?

时间:2013-07-05 04:37:58

标签: java multithreading concurrency atomic volatile

我们知道,在声明为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或使方法同步。 这让我想知道:如果longdouble的赋值在我们的正常编程过程中不是原子的,那么真正重要的是因为两者都需要声明为volatile或者为多线程访问同步所以我的问题是< strong>长期分配不是原子的事实会产生什么影响?

3 个答案:

答案 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实际上都会对longdouble进行原子分配。)

答案 1 :(得分:3)

如果使用int进行不正确的编程可能会导致观察到陈旧的值,那么使用long进行不正确的编程可能会导致从未实际存在的值被观察到。

理论上,这对于只需要最终正确且不是时间点正确的系统来说很重要,因此跳过性能同步。虽然为了表现而跳过一个不稳定的字段声明似乎是偶然的检查,如愚蠢。

答案 2 :(得分:2)

如果同时访问SharedInt或SharedLong会有所不同。正如你所说,一个线程可能会读取陈旧的int,或陈旧或损坏的长。

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

或在GUI中显示。

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

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

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

正如您在评论中所要求的那样,具体而言:

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

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

longs和int通常用于bitpacked字段以指示许多不同的东西。你的旗帜将完全被破坏。

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

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