java concurrency -- three problems of concurrent programming

1. Three problems of concurrent programming

1. Visibility issues

Visibility concept

Visibility: when one thread modifies a shared variable, the other gets the latest modified value immediately.

In practical applications, we need to ensure visibility, but there is no visibility between threads without special methods. It often happens that a thread explicitly changes the constant, and the constant value in the running thread is still used before the change.

Use a specific case to reveal this problem:

package com.xxx.concurrent_problem;

/**
  Case presentation:
          When one thread modifies a shared variable, another thread cannot get the latest value immediately
*/
public class Test01Visibility{
    //Data accessed by multiple threads, we become the shared data of threads
    private static boolean run = false;

    public static void main(String[] args) throws InterruptedException{
      //t1 thread constantly reads the value of run shared variable
      Thread t1 = new Thread(() -> {
        while(run){

        }
      });
      t1.start();

      Thread.sleep(1000);

      //t2 thread modifies the value of the shared variable
      Thread t2 = new Thread(() -> {
        run =  false;
        System.out.println("When the time is up, line layer 2 is set to false");
      });
      t2.start();

      //The modification of the run shared variable by t2 thread can be observed, and t1 thread cannot read the changed value;
      //This creates a visibility problem
    }
}

The fundamental problem is that when the thread runs, it will copy a shared memory variable to the working memory. In other words, when a thread works, no matter how the variable in the shared memory changes, the variable in the thread's working memory will not change, so the visibility cannot be guaranteed.

2. Atomicity

What is atomicity?

Atomicity: in one or more operations, either all operations are executed and will not be interrupted by other factors, or all operations are not executed.

Specifically, when a thread operates on variables in shared memory, it should ensure that the operation will not be disturbed by other threads until the current thread finishes working.

Atomic problem demonstration:

package com.xxx.demo01_concurrent_problem;

import java.util.ArrayList;

/**
    Case demonstration: five threads execute i + + 1000 times each;
*/
public class Test02Atomicity{
  private static int number = 0;
  public static void main(String[] args) throws InterruptedException{

    //All 5 threads execute 1000 times i++
    Runnable increment = () -> {
      for( int i = 0 ; i < 1000; i++){
        number++;
      }
    };

    //5 threads
    ArrayList<Thread> ts = new ArrayList<>();
    for(int i = 0; i < 5 ; i++){
      Thread t = new Thread(increment);
      t.start();
      ts.add(t);
    }

    for(Thread t : ts){
      t.join();
    }

  
    System.out.println("number = "+ number);
  }
}

Final result: < 5000 may occur

Because the increment operation in the thread is not locked, the variable is operated by other threads in the middle of the operation, causing the operation to fail.

Detailed explanation of failure:

Extension: java disassembly command: javap - P - V \Test02Atomicity. class

This is what we call atomicity!

3. Order

Ordering: it refers to the execution order of the code in the program. Java will optimize the code at compile time and run time, which will lead to the final execution order of the program, which is not necessarily the order when we write the code.

In other words, the actual operation may be inconsistent with the execution order we wrote.

There are bound to be many problems.

For example:

package com.xxx.concurrent_problem;

import org.openjdk.jcstress.annotations.*;
import org.openjdk.jcstress.infra.results.I_Result;

@JCStressTest
@OutCome(id = {"1" , "4"}, expect =  Expect.ACCEPTABLE, desc = "ok")
@OutCome(id = 0, expect = EXPECT.ACCEPTABLE_INTERESTING, desc = "danger")
@State
public class Test03Orderliness{
    int num = 0;
    boolean ready = false;


    @Actor
    public void actor1(I_Result r){
      if(ready){
        r.r1 = num + num;
      }else{
        r.r1 = 1;
      }
    }

    //Code executed by thread 2; Modify the two variables accordingly;
    @Actor
    public void actor2(I_Result r){
      num = 2;
      ready = true;
    }

    
}

The two codes of thread 2 in the above code do not have a clear logical relationship, so they are interchangeable. However, adding replacement will inevitably lead to the wrong value of num. This is the common problem of order, which we don't want to see.

Extension: stress testing jcstress tool

Official website: https://wiki.openjdk.java.net/display/CodeTools/jcstress

Add dependency:

<dependency>
  <groupId>org.openjdk.jcstress</groupId>
  <artifactId>jcstress-core</artifactId>
  <version>${jcstress.version</version>
</dependency>

usage method:

The detection results are obtained by jcstress;
Open the idea terminal; Terminal;
Type the command line: mvn clean install
After completion, two jar packages will be formed in the target directory (provided that jcstress dependency is installed;)- jcstress.jar,Synchronized-1.0-SNAPSHOT.jar
Run jcstress Jar the jar package;
C:\Users\13666\IdeaProjects\XXX\Synchronized> java -jar target/jcstress.jar
After operation, conduct multi wheel pressure test;

Keywords: Java Back-end

Added by tegwin15 on Sun, 09 Jan 2022 06:20:40 +0200