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

ver*_*tas 4 java concurrency multithreading 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;
   }

}
Run Code Online (Sandbox Code Playgroud)

现在考虑另一个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;
    }
}
Run Code Online (Sandbox Code Playgroud)

现在我们可以看到上述两个版本都不是线程安全的.在这种情况下int,这是因为线程可能会看到整数的陈旧值.在万一的情况下long,他们可以看到长变量的腐败和陈旧值.

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

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

Jef*_*rey 9

我刚才做了一个很酷的小例子

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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


Aff*_*ffe 3

如果对 a 的不正确编程int可能会导致观察到过时的值,则对 a 进行不正确的编程long可能会导致观察到从未实际存在过的值。

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