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

时间:2022-06-26 18:05:43

We know that long and double assignments are not atomic in Java until they are declared volatile. My question is how does it really matter in our programming practice. for instance if you the see below classes whose objects are being shared among multiple threads.

我们知道,在声明为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;
   }

}

Now consider another SharedLong

现在考虑另一个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;
    }
}

Now we can see the both of the above versions are not thread safe. In case of int, it is because threads may see stale values of integer. While in case if long, they can see corrupt as well as stale values of long variable.

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

In both cases, if an instance is not shared among multiple threads, then the classes are safe.

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

To make the above classes thread safe we need to declare int and long both to be volatile or make the method synchronized. This make me wonder: How does it really matter if assignments to long and double are not atomic during our normal course of programming because both need to be declared volatile or synchronized for multithreaded access so my Questions is What are the scenarios where the fact that long assignments are not atomic may make a difference?.

为了使上面的类线程安全,我们需要声明int和long都是volatile或使方法同步。这让我想知道:如果long和double的赋值在我们的正常编程过程中不是原子的,那么真正重要的是因为两者都需要被声明为volatile或者是多线程访问的同步因此我的问题是什么样的场景作业不是原子的可能会有所作为吗?

3 个解决方案

#1


3  

Where improper programming with an int may result in stale values being observed, improper programming with a long may result in values that never actually existed being observed.

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

This could theoretically matter for a system that only needs to be eventually-correct and not point-in-time correct, so skipped synchronization for performance. Although skipping a volatile field declaration in the interest of performance seems on casual inspection like foolishness.

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

#2


8  

I made a cool little example of this a while ago

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

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

One thread constantly tries to assign 0 to test while the other tries to assign -1. Eventually you'll end up with a number that's either 0b1111111111111111111111111111111100000000000000000000000000000000
or
0b0000000000000000000000000000000011111111111111111111111111111111.
(Assuming you aren't on a 64 bit JVM. Most, if not all, 64 bit JVMs will actually do atomic assignment for longs and doubles.)

一个线程不断尝试将0分配给测试,而另一个线程尝试分配-1。最终,您将获得一个0b1111111111111111111111111111111100000000000000000000000000000000或0b000000000000000000000000000000000000111111111111111111111111111111的数字。 (假设您不在64位JVM上。大多数(如果不是全部的话)64位JVM实际上会为lo​​ng和double进行原子分配。)

#3


2  

It makes a difference if SharedInt or SharedLong are going to be accessed simultaneously. As you said, one thread may read a stale int, or a stale or corrupted long.

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

This could be important if the value was being used to reference an array.

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

Or with display in a GUI.

或者在GUI中显示。

How about writing some values over a network and sending bad data. Now clients are confused or crashing.

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

Incorrect values could be stored to a database.

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

Repeated calculations could be corrupted...

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

As you requested in comments, For long specifically:

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

Long values are frequently used for time calculations. This could throw off loops where you are waiting for an amount of time before performing some operation, such as a heartbeat in a networking app.

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

You could report to a client synchronizing clocks with you time was 80 years or 1000 years in the past.

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

Longs and ints are commonly used for bitpacked fields to indicate many different things. Your flags would be entirely corrupted.

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

Longs are used as unique ID's frequently. This could corrupt hash tables you're creating.

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

Obviously lots of bad, bad stuff could happen. If this value needs to be thread safe, and you want your software to be very reliable, declare these variables volatile, use an Atomic variable, or synchronize access and set methods.

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

#1


3  

Where improper programming with an int may result in stale values being observed, improper programming with a long may result in values that never actually existed being observed.

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

This could theoretically matter for a system that only needs to be eventually-correct and not point-in-time correct, so skipped synchronization for performance. Although skipping a volatile field declaration in the interest of performance seems on casual inspection like foolishness.

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

#2


8  

I made a cool little example of this a while ago

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

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

One thread constantly tries to assign 0 to test while the other tries to assign -1. Eventually you'll end up with a number that's either 0b1111111111111111111111111111111100000000000000000000000000000000
or
0b0000000000000000000000000000000011111111111111111111111111111111.
(Assuming you aren't on a 64 bit JVM. Most, if not all, 64 bit JVMs will actually do atomic assignment for longs and doubles.)

一个线程不断尝试将0分配给测试,而另一个线程尝试分配-1。最终,您将获得一个0b1111111111111111111111111111111100000000000000000000000000000000或0b000000000000000000000000000000000000111111111111111111111111111111的数字。 (假设您不在64位JVM上。大多数(如果不是全部的话)64位JVM实际上会为lo​​ng和double进行原子分配。)

#3


2  

It makes a difference if SharedInt or SharedLong are going to be accessed simultaneously. As you said, one thread may read a stale int, or a stale or corrupted long.

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

This could be important if the value was being used to reference an array.

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

Or with display in a GUI.

或者在GUI中显示。

How about writing some values over a network and sending bad data. Now clients are confused or crashing.

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

Incorrect values could be stored to a database.

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

Repeated calculations could be corrupted...

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

As you requested in comments, For long specifically:

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

Long values are frequently used for time calculations. This could throw off loops where you are waiting for an amount of time before performing some operation, such as a heartbeat in a networking app.

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

You could report to a client synchronizing clocks with you time was 80 years or 1000 years in the past.

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

Longs and ints are commonly used for bitpacked fields to indicate many different things. Your flags would be entirely corrupted.

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

Longs are used as unique ID's frequently. This could corrupt hash tables you're creating.

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

Obviously lots of bad, bad stuff could happen. If this value needs to be thread safe, and you want your software to be very reliable, declare these variables volatile, use an Atomic variable, or synchronize access and set methods.

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