programing

i ++가 원 자성이 아닌 이유는 무엇입니까?

nasanasas 2020. 8. 30. 08:43
반응형

i ++가 원 자성이 아닌 이유는 무엇입니까?


i++Java에서 원 자성 아닌 이유는 무엇 입니까?

Java를 좀 더 깊이 이해하기 위해 스레드의 루프가 실행되는 빈도를 계산하려고했습니다.

그래서 나는

private static int total = 0;

메인 클래스에서.

두 개의 스레드가 있습니다.

  • 글타래 (쓰레드) 1 : Prints System.out.println("Hello from Thread 1!");
  • 스레드 2 : Prints System.out.println("Hello from Thread 2!");

그리고 스레드 1과 스레드 2에 의해 인쇄 된 줄을 계산합니다. 그러나 스레드 1의 줄 + 스레드 2의 줄은 인쇄 된 총 줄 수와 일치하지 않습니다.

내 코드는 다음과 같습니다.

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

i++원자 성은 대부분의 .NET Framework 사용에없는 특수 요구 사항이기 때문에 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.

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.

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 inc instruction atomic: for the same reasons as above. If inc were 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.

In some instruction set architectures, there is no atomic inc or perhaps no inc at all; to do an atomic inc on MIPS, you have to write a software loop which uses the ll and 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).


i++ involves two operations :

  1. read the current value of i
  2. increment the value and assign it to 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.

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

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 volatile and 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)


Why is i++ not atomic in Java?

Let's break the increment operation into multiple statements:

Thread 1 & 2 :

  1. Fetch value of total from memory
  2. Add 1 to the value
  3. Write back to the memory

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.


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

  1. Read current value
  2. Write new value
  3. Store new value

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

As an example imagine this scenario:

Time 1:

Thread A fetches i
Thread B fetches i

Time 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:

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-store would happen atomically. That's exactly what AtomicInteger is for and in your case it would probably fit right in.

P.S.

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


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).

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)

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 then update the value.

The other important reason is that doing something atomically usually takes 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 AtomicInteger and other atomic classes to the language.


There are two steps:

  1. fetch i from memory
  2. set i+1 to i

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


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


Concurrency (the Thread class 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.

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

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.

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

참고URL : https://stackoverflow.com/questions/25168062/why-is-i-not-atomic

반응형