See I ++ everyone is not a contemptuous smile, is not an I ++ and ++ I order problem?

Wrong!!! So if I were to ask: how does i++ work in the case of multi-threaded concurrency, how do I guarantee atomicity?

To understand what’s going on you need to know at least a few things:

  • What are the three main features of concurrency?
  • What exactly is the bytecode execution of the i++ operation?
  • In the case of multi-threaded concurrency, what operations can guarantee i++ atomicity?

Do you really understand??

Let’s pick up where we left off”Still Being Volatile? How to Deckhole?”The error using volatile for i++ causes a bug.

First of all, in the case of multi-threaded concurrency, we need to know what are the three characteristics of concurrency
  1. Atomicity: Atomicity means that an operation cannot be interrupted, either to complete or not to complete.

  2. Visibility: Visibility means that changes to a variable are visible to all threads. That is, when one thread changes the value of this variable, the new value is immediately known to other threads.

  3. Orderliness: To improve program performance, both the editor and the processor may reorder the instructions in the program.

Today’s BUG is mainly caused by atomicity, so this article will focus on atomicity.

Atomicity, for example, in real life, for example
You’re on a roller coaster. You can’t stop halfway, can you?
You transfer money to others certainly hope that after his money was deducted, the money on the account of others arrived in an account, namely all operations can be completed. And do not want to appear their own money buckle, did not turn to others.
So we want to perform a complete operation before the next operation is to follow the principle of atomicity, EMMM… Volatile, however, does not guarantee atomicity for i++ operations.

We know that single reads and writes of basic data types are atomic. Similarly, a single read or write on a volatile variable is atomic. But for things like ++,–, the logic is not! These kinds of compound operations, these operations as a whole are not atomic.

 volatile int i=0;// Define volatile variable I
 if(i==1)// Read I separately. This operation is atomic
 i=1;// Write (assign) to I alone. This operation is atomic
 i++;// Compound operation, this operation is not atomic
Copy the code

Because the ++ operation needs to be done in three operations! Instead of doing it all in one step. To see why you need to look at bytecode instructions for answers, know what the computer is really doing.

When we decompile javap -c volatiletest.class, we see that increase() race++ is composed of the following bytecode instructions.

  public void increase();
    Code:
       0: aload_0
       1: dup
       2: getfield      #2 // Field race:I
       5: iconst_1
       6: iadd
       7: putfield      #2 // Field race:I
      10: return

Copy the code

Are you thinking what the hell is this? Can’t read bytecode? It doesn’t matter we have someone to talk to.

Aload_0 pushes this reference to the top of the stack. Dup copies the top value of this application and pushes it to the top of the stack, i.e. there are consecutive identical this references on the operand stack. Getfield pops up the object reference at the top of the stack, gets the value of its field RACE and pushes it onto the top of the stack. The first operation iconst_1 pushes int (1) to the top of the stack. Iadd pops out the top of the stack and adds the two elements (race+1) and pushes the result to the top. The second operation putfield pops two variables (cumulative values, this reference) from the top of the stack and assigns the values to this instance field Race. The third operation, assignmentCopy the code

If you still don’t understand it, don’t worry, just look at the diagram below.

At the bytecode level, it’s easy to analyze the cause of concurrency failure. If two threads execute race++ at the same time,

(1) When thread A and thread B simultaneously execute getField instruction to push the value of RACE to the top of their respective operation stack. The volatile keyword ensures that the value of race is correct (the latest value) at this time.

(2) Thread A and thread B simultaneously execute iconst_1 to push int (1) to the top of the stack

(3) Thread A has completed the subsequent operations iadd and putfield successively, and the value of RACE in the main memory has been increased by 1. After thread A finishes executing, thread B’s race value at the top of the stack becomes stale data.

(4) Thread B will synchronize the smaller values to the main memory after executing iadd and putfield.

As a result, you can see that the increment I was expecting from thread B through the ++ operation is invalid, which is why the result value of our previous execution is always too small.

In this scenario, volatile is not recommended, and locking is required to ensure atomicity, that is, synchronized.

How to solve the i++ problem

Synchronized has atomicity, so we can guarantee atomicity of race++ operations through synchronized. Directly on the code:
public class SynchronizedTest {
    public int race = 0;
    // Use synchronized to ensure atomicity of the ++ operation
    public synchronized void increase(a) {
        race++;
    }
    public int getRace(a){
        return race;
    }

    public static void main(String[] args) {
        // Create five threads and add the same volatileTest instance
        SynchronizedTest synchronizedTest=new SynchronizedTest();
        int threadCount = 10;
        Thread[] threads = new Thread[threadCount];/ / 5 threads
        for (int i = 0; i < threadCount; i++) {
            // Each thread performs 1000 ++ operations
            threads[i]  = new Thread(()->{
                for (int j = 0; j < 10000; j++) {
                    synchronizedTest.increase();
                }
                System.out.println(synchronizedTest.getRace());
            });
            threads[i].start();
        }

        // Wait for all accumulated threads to finish
        for (int i = 0; i < threadCount; i++) {
            try {
                threads[i].join();
            } catch(InterruptedException e) { e.printStackTrace(); }}// Race is: 5*10000=50000 after all child threads have finished.
        System.out.println("Sum result:"+synchronizedTest.getRace()); }}Copy the code

The execution result is as follows:

Hoo ~ finally solved, comfortable.

Don’t relax. A new storm has emerged. With the introduction of synchronized, does everyone suddenly think of the soul searching of the interview devil? What’s the difference between synchronized and volatile?
So, what is volatile? How to use to figure out, and then more clearly compared.

This article mainly explains the atomicity of the three concurrent features, and why i++ may cause problems in concurrent scenarios, and the introduction of synchronized to solve the problem of i++. In the next chapter, we will explain:

  • What volatile is
  • What is the difference between synchronized and volatile
  • Correct use of volatile

Last dry goods, please stamp”Still Being Volatile? How to Deckhole?”