java原始整数是设计的还是偶然的原子?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1006655/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 22:11:57  来源:igfitidea点击:

Are java primitive ints atomic by design or by accident?

javamultithreading

提问by JustJeff

Are java primitive integers (int) atomic at all, for that matter? Some experimentation with two threads sharing an int seems to indicate that they are, but of course absence of evidence that they are notdoes not imply that they are.

就此而言,java 原始整数 (int) 是原子的吗?两个线程共享一个 int 的一些实验似乎表明它们,但当然没有证据表明它们不是并不意味着它们是。

Specifically, the test I ran was this:

具体来说,我运行的测试是这样的:

public class IntSafeChecker {
    static int thing;
    static boolean keepWatching = true;

    // Watcher just looks for monotonically increasing values   
    static class Watcher extends Thread {
        public void run() {
            boolean hasBefore = false;
            int thingBefore = 0;

            while( keepWatching ) {
                // observe the shared int
                int thingNow = thing;
                // fake the 1st value to keep test happy
                if( hasBefore == false ) {
                    thingBefore = thingNow;
                    hasBefore = true;
                }
                // check for decreases (due to partially written values)
                if( thingNow < thingBefore ) {
                    System.err.println("MAJOR TROUBLE!");
                }
                thingBefore = thingNow;
            }
        }
    }

    // Modifier just counts the shared int up to 1 billion
    static class Modifier extends Thread {
        public void run() {
            int what = 0;
            for(int i = 0; i < 1000000000; ++i) {
                what += 1;
                thing = what;
            }
            // kill the watcher when done
            keepWatching = false;
        }
    }

    public static void main(String[] args) {
        Modifier m = new Modifier();
        Watcher w = new Watcher();
        m.start();
        w.start();
    }
}

(and that was only tried with java jre 1.6.0_07 on a 32bit windows PC)

(并且仅在 32 位 Windows PC 上使用 java jre 1.6.0_07 进行了尝试)

Essentially, the Modifier writes a count sequence to the shared integer, while the Watcher checks that the observed values never decrease. On a machine where a 32 bit value had to be accessed as four separate bytes (or even two 16bit words), there would be a probability that Watcher would catch the shared integer in an inconsistent, half-updated state, and detect the value decreasing rather than increasing. This should work whether the (hypothetical) data bytes are collected/written LSB 1st or MSB 1st, but is only probablistic at best.

本质上,Modifier 将一个计数序列写入共享整数,而 Watcher 会检查观察到的值是否永远不会减少。在必须将 32 位值作为四个单独的字节(或什至两个 16 位字)访问的机器上,Watcher 可能会捕获处于不一致、半更新状态的共享整数,并检测到值减少而不是增加。无论(假设的)数据字节是收集/写入 LSB 1st 还是 MSB 1st,这都应该起作用,但充其量只是概率性的。

It would seem very probable given today's wide data paths that a 32 bit value could be effectively atomic, even if the java spec doesn't require it. In fact, with a 32 bit data bus it would seem that you might have to work harder to get atomic access to bytesthan to 32 bit ints.

考虑到今天的宽数据路径,即使 java 规范不需要它,32 位值也可能是有效的原子值。事实上,对于 32 位数据总线,与32 位整数相比,您似乎必须更努力地获得对字节的原子访问。

Googling on "java primitive thread safety" turns up loads of stuff on thread-safe classes and objects, but looking for the info on the primitives seems to be looking for the proverbial needle in a haystack.

谷歌搜索“java 原始线程安全”会发现大量关于线程安全类和对象的内容,但寻找关于原始数据的信息似乎是在大海捞针中寻找众所周知的针。

采纳答案by Jon Skeet

All memory accesses in Java are atomic by default, with the exception of longand double(which maybe atomic, but don't have to be). It's not put veryclearly to be honest, but I believe that's the implication.

默认情况下,Java 中的所有内存访问都是原子的,除了longand double可能是原子的,但不一定是)。老实说,这不是清楚,但我相信这就是含义。

From section 17.4.3of the JLS:

来自JLS第 17.4.3 节

Within a sequentially consistent execution, there is a total order over all individual actions (such as reads and writes) which is consistent with the order of the program, and each individual action is atomic and is immediately visible to every thread.

在顺序一致的执行中,所有单独的动作(例如读和写)都有一个与程序顺序一致的总顺序,每个单独的动作都是原子的,对每个线程都是立即可见的。

and then in 17.7:

然后在17.7 中

Some implementations may find it convenient to divide a single write action on a 64-bit long or double value into two write actions on adjacent 32 bit values. For efficiency's sake, this behavior is implementation specific; Java virtual machines are free to perform writes to long and double values atomically or in two parts.

某些实现可能会发现将 64 位 long 或 double 值上的单个写入操作分成两个相邻 32 位值上的写入操作很方便。为了效率,这种行为是特定于实现的;Java 虚拟机可以自由地以原子方式或分两部分执行对 long 和 double 值的写入。

Note that atomicity is very different to volatility though.

请注意,原子性与波动性非常不同。

When one thread updates an integer to 5, it's guaranteed that another thread won't see 1 or 4 or any other in-between state, but without any explicit volatility or locking, the other thread could see 0 forever.

当一个线程将整数更新为 5 时,可以保证另一个线程不会看到 1 或 4 或任何其他中间状态,但如果没有任何显式波动或锁定,另一个线程可能永远看到 0。

With regard to working hard to get atomic access to bytes, you're right: the VM may well have to try hard... but it does have to. From section 17.6of the spec:

关于努力获得对字节的原子访问,您是对的:VM 可能必须努力尝试……但它确实必须这样做。来自规范的第 17.6 节

Some processors do not provide the ability to write to a single byte. It would be illegal to implement byte array updates on such a processor by simply reading an entire word, updating the appropriate byte, and then writing the entire word back to memory. This problem is sometimes known as word tearing, and on processors that cannot easily update a single byte in isolation some other approach will be required.

某些处理器不提供写入单个字节的能力。在这样的处理器上通过简单地读取整个字、更新适当的字节,然后将整个字写回内存来实现字节数组更新是非法的。这个问题有时被称为字撕裂,在不能轻易单独更新单个字节的处理器上,将需要一些其他方法。

In other words, it's up to the JVM to get it right.

换句话说,这取决于 JVM 是否正确。

回答by Robert Munteanu

  • No amount of testing can prove thread safety - it can only disproveit;
  • I found a indirect reference in JLS 17.7which states
  • 再多的测试也无法证明线程安全——它只能反驳它;
  • 我在JLS 17.7 中找到了一个间接引用,其中指出

Some implementations may find it convenient to divide a single write action on a 64-bit long or double value into two write actions on adjacent 32 bit values.

某些实现可能会发现将 64 位 long 或 double 值上的单个写入操作分成两个相邻 32 位值上的写入操作很方便。

and further down

再往下

For the purposes of the Java programming language memory model, a single write to a non-volatile long or double value is treated as two separate writes: one to each 32-bit half.

就 Java 编程语言内存模型而言,对非易失性 long 或 double 值的单次写入被视为两次单独的写入:一次写入每个 32 位一半。

This seems to imply that writes to ints are atomic.

这似乎意味着对整数的写入是原子的。

回答by GaryF

This is somewhat complicated, and is related to system wordsize. Bruce Eckel discusses it in more detail: Java Threads.

这有点复杂,与系统字数有关。Bruce Eckel 更详细地讨论了它:Java 线程

回答by James

A read or write from integer or any smaller type should be atomic, but as Robert noted, longs and doubles may or may not depending on the implementation. However, any operation that uses both a read and a write, including all of the increment operators, are not atomic. Thus, if you have to threads operating on an integer i=0, one does i++ and the other does i=10, the result could be 1, 10, or 11.

对整数或任何较小类型的读取或写入应该是原子的,但正如罗伯特指出的那样,longs 和 doubles 可能会也可能不会取决于实现。但是,任何同时使用读取和写入的操作(包括所有增量运算符)都不是原子的。因此,如果您必须对整数 i=0 进行线程操作,一个执行 i++,另一个执行 i=10,结果可能是 1、10 或 11。

For operations like this, you should look at AtomicIntegerwhich has methods for atomically modifying a value while retrieving the old one or to atomically increment the value.

对于这样的操作,您应该查看AtomicInteger,它具有在检索旧值时原子地修改值或原子地增加值的方法。

Finally, threads may cache the value of the variable and won't see changes made to it from other threads. To make sure that both threads always see changes made by the other thread, you need to mark the variable as being volatile.

最后,线程可能会缓存变量的值,并且不会看到其他线程对其所做的更改。为了确保两个线程始终看到另一个线程所做的更改,您需要将该变量标记为 volatile。

回答by Kounavi

I agree with Jon Skeet and I would like to add that many people confuse the concept of atomicity, volatility and thread safety because sometimes the terms are used interchangeably.
For example, consider this:

我同意 Jon Skeet 的观点,我想补充一点,许多人混淆了原子性、易变性和线程安全的概念,因为有时这些术语可以互换使用。
例如,考虑这个:

private static int i = 0;
public void increment() { i++; }

While someone may argue that this operation is atomic, the referred hypothesis is wrong.
The statement i++;performs three operations:
1) Read
2) Update
3) Write

虽然有人可能会争辩说这个操作是原子的,但所提到的假设是错误的。
该语句i++;执行三个操作:
1) 读取
2) 更新
3) 写入

Therefore, the threads that operate on this variable should be synchronized like this:

因此,对这个变量进行操作的线程应该像这样同步:

private static int i = 0;
private static final Object LOCK = new Object();
public void increment() {
   synchronized(LOCK) {
       i++;
    } 
}

or this:

或这个:

private static int i = 0;
public static synchronized void increment() {
   i++; 
}

Do note that, for a single object instance, calling a method that is being accessed by multiple threads and operate on shared mutable data one has to take into consideration the fact that a method's parameters, local variable and return value are local for each Thread.

请注意,对于单个对象实例,调用一个被多个线程访问并对共享可变数据进行操作的方法必须考虑这样一个事实,即方法的参数、局部变量和返回值对于每个线程都是局部的。

For more information check out this link:
http://www.javamex.com/tutorials/synchronization_volatile.shtml

有关更多信息,请查看此链接:http:
//www.javamex.com/tutorials/synchronization_volatile.shtml

Hope this helps.

希望这可以帮助。

UPDATE: There is also the case where you can synchronize on the class object itself. More info here: How to synchronize a static variable among threads running different instances of a class in java?

更新:还有一种情况,您可以在类对象本身上进行同步。更多信息:如何在java中运行不同类实例的线程之间同步静态变量?

回答by Michal B

I think it doesnt work as you expected:

我认为它不像你预期的那样工作:

private static int i = 0;
public void increment() {
   synchronized (i) { 
      i++; 
   }
}

integer is immutable so you are sychronizing on a different object all the time. int "i" is autoboxed to Integer object, then you set lock on it. If another thread goes into this method int i is autoboxed to another Integer object and you set lock on a different object then before.

整数是不可变的,因此您始终在不同的对象上进行同步。int "i" 自动装箱为 Integer 对象,然后您对其设置锁定。如果另一个线程进入这个方法 int i 被自动装箱到另一个 Integer 对象,然后你在不同的对象上设置锁定。

回答by electroCutie

Atomic reads and writes merely mean that you will never read, e.g. the first 16 bits of an int update and another from an old value.

原子读写仅意味着您永远不会读取,例如 int 更新的前 16 位和旧值的另一个。

This says nothing about WHEN other threads see these writes.

这与其他线程何时看到这些写入无关。

The long story short is that when two threads race with no memory barriers between them something gets lost.

长话短说,当两个线程在它们之间没有内存障碍的情况下竞争时,就会丢失一些东西。

Spin up two or more threads that increment a single shared integer and also count their own increments. When the integer gets to some value (INT_MAX, for example. Nice and large to let things warm up) stop everything and return the value of the int and the number of increments each thread performed.

启动两个或多个线程,这些线程增加单个共享整数并计算它们自己的增量。当整数达到某个值时(例如,INT_MAX。让事情升温的好和大)停止一切并返回 int 的值和每个线程执行的增量数。

    import java.util.Stack;

public class Test{

  static int ctr = Integer.MIN_VALUE;
  final static int THREADS = 4;

  private static void runone(){
    ctr = 0;
    Stack<Thread> threads = new Stack<>();
    for(int i = 0; i < THREADS; i++){
      Thread t = new Thread(new Runnable(){
        long cycles = 0;

        @Override
        public void run(){
          while(ctr != Integer.MAX_VALUE){
            ctr++;
            cycles++;
          }
          System.out.println("Cycles: " + cycles + ", ctr: " + ctr);
        }
      });
      t.start();
      threads.push(t);
    }
    while(!threads.isEmpty())
      try{
        threads.pop().join();
      }catch(InterruptedException e){
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
    System.out.println();
  }

  public static void main(String args[]){
    System.out.println("Int Range: " + ((long) Integer.MAX_VALUE - (long) Integer.MIN_VALUE));
    System.out.println("  Int Max: " + Integer.MAX_VALUE);
    System.out.println();
    for(;;)
      runone();
  }
}

Here is the result of this test on my quad core box (feel free to play with the thread count in the code, I just matched my core count, obviously):

这是在我的四核盒子上的测试结果(随意使用代码中的线程数,显然我只是匹配了我的核心数):

Int Range: 4294967295
Int Max: 2147483647

Cycles: 2145700893, ctr: 76261202
Cycles: 2147479716, ctr: 1825148133
Cycles: 2146138184, ctr: 1078605849
Cycles: 2147282173, ctr: 2147483647

Cycles: 2147421893, ctr: 127333260
Cycles: 2146759053, ctr: 220350845
Cycles: 2146742845, ctr: 450438551
Cycles: 2146537691, ctr: 2147483647

Cycles: 2110149932, ctr: 696604594
Cycles: 2146769437, ctr: 2147483647
Cycles: 2147095646, ctr: 2147483647
Cycles: 2147483647, ctr: 2147483647

Cycles: 2147483647, ctr: 330141890
Cycles: 2145029662, ctr: 2147483647
Cycles: 2143136845, ctr: 2147483647
Cycles: 2147007903, ctr: 2147483647

Cycles: 2147483647, ctr: 197621458
Cycles: 2076982910, ctr: 2147483647
Cycles: 2125642094, ctr: 2147483647
Cycles: 2125321197, ctr: 2147483647

Cycles: 2132759837, ctr: 330963474
Cycles: 2102475117, ctr: 2147483647
Cycles: 2147390638, ctr: 2147483647
Cycles: 2147483647, ctr: 2147483647

回答by adekock11

This is not atomic:

这不是原子的:

i++;

However, this is:

然而,这是:

i = 5;

I think this is where some confusion sets in.

我认为这是一些混乱的地方。

回答by Rodrigo Gomez

When data is being shared between threads, synchronization is needed. When dealing with an Integer, which can go from main Memory to a processor's cache in a multiple processor system, a thread may be updating a local copy of an integer tied to a specific processor.

当线程之间共享数据时,需要同步。在处理一个整数时,它可以从主内存到多处理器系统中的处理器缓存,线程可能正在更新与特定处理器相关的整数的本地副本。

The volatile (See Wiki in Java Section)keyword in Java will ensure any update to an Integer will happen in memory and not a local copy.

Java 中的 volatile (请参阅 Java 部分中的 Wiki)关键字将确保对 Integer 的任何更新都将发生在内存中而不是本地副本中。

Further, to synchronize updates to an Integer, consider using AtomicInteger. This implementation has a method (compareAndSet)to check if a value is what a thread expects AND set it if does. If it doesn't match, then another thread could have updated the value. The AtomicInteger will perform both the reading and updating of the Integer in an atomic operation, with the advantage of not having to block.

此外,要将更新同步到 Integer,请考虑使用 AtomicInteger。这个实现有一个方法(compareAndSet)来检查一个值是否是线程期望的,如果是,则设置它。如果不匹配,则另一个线程可能已更新该值。AtomicInteger 将在原子操作中执行 Integer 的读取和更新,其优点是不必阻塞。