Java 为什么 i++ 不是原子的?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/25168062/
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-10 23:46:53  来源:igfitidea点击:

Why is i++ not atomic?

javamultithreadingconcurrency

提问by Andie2302

Why is i++not atomic in Java?

为什么i++在 Java 中不是原子的?

To get a bit deeper in Java I tried to count how often the loop in threads are executed.

为了更深入地了解 Java,我尝试计算线程中循环的执行频率。

So I used a

所以我用了一个

private static int total = 0;

in the main class.

在主班。

I have two threads.

我有两个线程。

  • Thread 1: Prints System.out.println("Hello from Thread 1!");
  • Thread 2: Prints System.out.println("Hello from Thread 2!");
  • 主题 1:打印 System.out.println("Hello from Thread 1!");
  • 主题 2:打印 System.out.println("Hello from Thread 2!");

And I count the lines printed by thread 1 and thread 2. But the lines of thread 1 + lines of thread 2 don't match the total number of lines printed out.

我计算了线程 1 和线程 2 打印的行数。但是线程 1 的行数 + 线程 2 的行数与打印出的总行数不匹配。

Here is my code:

这是我的代码:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Test {

    private static int total = 0;
    private static int countT1 = 0;
    private static int countT2 = 0;
    private boolean run = true;

    public Test() {
        ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
        newCachedThreadPool.execute(t1);
        newCachedThreadPool.execute(t2);
        try {
            Thread.sleep(1000);
        }
        catch (InterruptedException ex) {
            Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
        }
        run = false;
        try {
            Thread.sleep(1000);
        }
        catch (InterruptedException ex) {
            Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
        }
        System.out.println((countT1 + countT2 + " == " + total));
    }

    private Runnable t1 = new Runnable() {
        @Override
        public void run() {
            while (run) {
                total++;
                countT1++;
                System.out.println("Hello #" + countT1 + " from Thread 2! Total hello: " + total);
            }
        }
    };

    private Runnable t2 = new Runnable() {
        @Override
        public void run() {
            while (run) {
                total++;
                countT2++;
                System.out.println("Hello #" + countT2 + " from Thread 2! Total hello: " + total);
            }
        }
    };

    public static void main(String[] args) {
        new Test();
    }
}

采纳答案by Kaz

i++is probably not atomic in Java because atomicity is a special requirement which is not present in the majority of the uses of i++. That requirement has a significant overhead: there is a large cost in making an increment operation atomic; it involves synchronization at both the software and hardware levels that need not be present in an ordinary increment.

i++在 Java 中可能不是原子的,因为原子性是一个特殊的要求,在i++. 这个要求有很大的开销:使增量操作原子化需要很大的成本;它涉及不需要以普通增量出现的软件和硬件级别的同步。

You could make the argument that i++should have been designed and documented as specifically performing an atomic increment, so that a non-atomic increment is performed using i = i + 1. However, this would break the "cultural compatibility" between Java, and C and C++. As well, it would take away a convenient notation which programmers familiar with C-like languages take for granted, giving it a special meaning that applies only in limited circumstances.

您可以将i++应该设计和记录的参数设置为专门执行原子增量,以便使用i = i + 1. 但是,这会破坏 Java 与 C 和 C++ 之间的“文化兼容性”。同样,它会带走熟悉 C 类语言的程序员认为理所当然的方便符号,赋予它一个仅适用于有限情况的特殊含义。

Basic C or C++ code like for (i = 0; i < LIMIT; i++)would translate into Java as for (i = 0; i < LIMIT; i = i + 1); because it would be inappropriate to use the atomic i++. What's worse, programmers coming from C or other C-like languages to Java would use i++anyway, resulting in unnecessary use of atomic instructions.

基本的 C 或 C++ 代码就像for (i = 0; i < LIMIT; i++)将转换为 Java 一样for (i = 0; i < LIMIT; i = i + 1);因为使用 atomic 是不合适的i++。更糟糕的是,从 C 或其他类似 C 语言到 Java 的程序员i++无论如何都会使用,从而导致不必要地使用原子指令。

Even at the machine instruction set level, an increment type operation is usually not atomic for performance reasons. In x86, a special instruction "lock prefix" must be used to make the incinstruction atomic: for the same reasons as above. If incwere always atomic, it would never be used when a non-atomic inc is required; programmers and compilers would generate code that loads, adds 1 and stores, because it would be way faster.

即使在机器指令集级别,出于性能原因,增量类型的操作通常也不是原子的。在 x86 中,必须使用特殊指令“锁定前缀”来使inc指令原子化:出于与上述相同的原因。如果inc总是原子的,则在需要非原子公司时永远不会使用它;程序员和编译器会生成加载、加 1 和存储的代码,因为它会更快。

In some instruction set architectures, there is no atomic incor perhaps no incat all; to do an atomic inc on MIPS, you have to write a software loop which uses the lland sc: load-linked, and store-conditional. Load-linked reads the word, and store-conditional stores the new value if the word has not changed, or else it fails (which is detected and causes a re-try).

在某些指令集架构中,没有原子性inc或根本没有inc;要在 MIPS 上执行 atomic inc,您必须编写一个软件循环,该循环使用lland sc:load-linked 和 store-conditional。Load-linked 读取单词,如果单词没有改变,store-conditional 存储新值,否则它会失败(被检测到并导致重试)。

回答by celeritas

In the JVM, an increment involves a read and a write, so it's not atomic.

在 JVM 中,增量涉及读取和写入,因此它不是原子的。

回答by Eran

i++involves two operations :

i++涉及两个操作:

  1. read the current value of i
  2. increment the value and assign it to i
  1. 读取当前值 i
  2. 增加值并将其分配给 i

When two threads perform i++on the same variable at the same time, they may both get the same current value of i, and then increment and set it to i+1, so you'll get a single incrementation instead of two.

当两个线程i++同时在同一个变量上执行时,它们可能都获得相同的当前值i,然后递增并将其设置为i+1,因此您将获得单个增量而不是两个。

Example :

例子 :

int i = 5;
Thread 1 : i++;
           // reads value 5
Thread 2 : i++;
           // reads value 5
Thread 1 : // increments i to 6
Thread 2 : // increments i to 6
           // i == 6 instead of 7

回答by Aniket Thakur

Why is i++ not atomic in Java?

为什么 i++ 在 Java 中不是原子的?

Let's break the increment operation into multiple statements:

让我们将增量操作分解为多个语句:

Thread 1 & 2 :

主题 1 和 2 :

  1. Fetch value of total from memory
  2. Add 1 to the value
  3. Write back to the memory
  1. 从内存中获取总计值
  2. 将值加 1
  3. 写回记忆

If there is no synchronization then let's say Thread one has read the value 3 and incremented it to 4, but has not written it back. At this point, the context switch happens. Thread two reads the value 3, increments it and the context switch happens. Though both threads have incremented the total value, it will still be 4 - race condition.

如果没有同步,那么假设线程一已读取值 3 并将其增加到 4,但尚未将其写回。此时,上下文切换发生。线程 2 读取值 3,将其递增,然后发生上下文切换。尽管两个线程都增加了总值,但它仍然是 4 - 竞争条件。

回答by TheBat

Concurrency (the Threadclass and such) is an added feature in v1.0 of Java. i++was added in the beta before that, and as such is it still more than likely in its (more or less) original implementation.

并发(Thread类等)是Javav1.0 中的一个附加特性。i++在此之前已添加到测试版中,因此在其(或多或少)原始实现中仍然很有可能。

It is up to the programmer to synchronize variables. Check out Oracle's tutorial on this.

由程序员来同步变量。查看Oracle 关于此的教程

Edit: To clarify, i++ is a well defined procedure that predates Java, and as such the designers of Java decided to keep the original functionality of that procedure.

编辑:澄清一下,i++ 是一个定义良好的过程,它早于 Java,因此 Java 的设计者决定保留该过程的原始功能。

The ++ operator was defined in B (1969) which predates java and threading by just a tad.

++ 运算符是在 B (1969) 中定义的,它比 Java 和线程早一点点。

回答by Jonathan Rosenne

The important thing is the JLS (Java Language Specification) rather than how various implementations of the JVM may or may not have implemented a certain feature of the language. The JLS defines the ++ postfix operator in clause 15.14.2 which says i.a. "the value 1 is added to the value of the variable and the sum is stored back into the variable". Nowhere does it mention or hint at multithreading or atomicity. For these the JLS provides volatileand synchronized. Additionally, there is the package java.util.concurrent.atomic(see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/package-summary.html)

重要的是 JLS(Java 语言规范)而不是 JVM 的各种实现如何实现或不实现该语言的某个特性。JLS 在第 15.14.2 条中定义了 ++ 后缀运算符,它表示 ia“将值 1 添加到变量的值中,并将总和存储回变量中”。它没有提到或暗示多线程或原子性。对于这些,JLS 提供了volatilesynchronized。此外,还有包java.util.concurrent.atomic(参见http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/package-summary.html

回答by Roy van Rijn

If the operation i++would be atomic you wouldn't have the chance to read the value from it. This is exactly what you want to do using i++(instead of using ++i).

如果操作i++是原子的,您将没有机会从中读取值。这正是您想要使用i++(而不是使用++i)执行的操作。

For example look at the following code:

例如看下面的代码:

public static void main(final String[] args) {
    int i = 0;
    System.out.println(i++);
}

In this case we expect the output to be: 0(because we post increment, e.g. first read, then update)

在这种情况下,我们期望输出为:(0因为我们发布增量,例如先读取,然后更新)

This is one of the reasons the operation can't be atomic, because you need to read the value (and do something with it) and thenupdate the value.

这是操作不能是原子的原因之一,因为您需要读取该值(并对其进行处理),然后更新该值。

The other important reason is that doing something atomically usuallytakes more time because of locking. It would be silly to have all the operations on primitives take a little bit longer for the rare cases when people want to have atomic operations. That is why they've added AtomicIntegerand otheratomic classes to the language.

另一个重要的原因是,由于锁定,以原子方式执行某些操作通常需要更多时间。在人们想要进行原子操作的极少数情况下,让所有对原语的操作都花费更长的时间是很愚蠢的。这就是为什么他们已经增加AtomicInteger其他原子类的语言。

回答by kstratis

i++is a statement which simply involves 3 operations:

i++是一个只涉及 3 个操作的语句:

  1. Read current value
  2. Write new value
  3. Store new value
  1. 读取当前值
  2. 写入新值
  3. 存储新值

These three operations are not meant to be executed in a single step or in other words i++is not a compoundoperation. As a result all sorts of things can go wrong when more than one threads are involved in a single but non-compound operation.

这三个操作并不意味着在单个步骤中执行,或者换句话说i++不是 复合操作。因此,当多个线程参与一个单一但非复合的操作时,各种事情都可能出错。

Consider the following scenario:

考虑以下场景:

Time 1:

时间 1:

Thread A fetches i
Thread B fetches i

Time 2:

时间 2:

Thread A overwrites i with a new value say -foo-
Thread B overwrites i with a new value say -bar-
Thread B stores -bar- in i

// At this time thread B seems to be more 'active'. Not only does it overwrite 
// its local copy of i but also makes it in time to store -bar- back to 
// 'main' memory (i)

Time 3:

时间 3:

Thread A attempts to store -foo- in memory effectively overwriting the -bar- 
value (in i) which was just stored by thread B in Time 2.

Thread B has nothing to do here. Its work was done by Time 2. However it was 
all for nothing as -bar- was eventually overwritten by another thread.

And there you have it. A race condition.

你有它。竞争条件。



That's why i++is not atomic. If it was, none of this would have happened and each fetch-update-storewould happen atomically. That's exactly what AtomicIntegeris for and in your case it would probably fit right in.

这就是为什么i++不是原子的。如果是这样,这一切都fetch-update-store不会发生,并且每个都会原子地发生。这正是AtomicInteger它的用途,在您的情况下,它可能适合。

P.S.

聚苯乙烯

An excellent book covering all of those issues and then some is this: Java Concurrency in Practice

一本涵盖所有这些问题的好书,然后是: Java Concurrency in Practice

回答by yanghaogn

There are two steps:

有两个步骤:

  1. fetch i from memory
  2. set i+1 to i
  1. 从内存中取出 i
  2. 将 i+1 设置为 i

so it's not atomic operation. When thread1 executes i++, and thread2 executes i++, the final value of i may be i+1.

所以它不是原子操作。当thread1执行i++,thread2执行i++时,i的最终值可能是i+1。