Three problems in concurrent programming

Posted by jhonrputra on Tue, 04 Jan 2022 13:13:48 +0100

1, Visibility

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

Case demonstration: one thread loops through flag and while according to the boolean flag, and another thread changes the value of the flag variable
A thread does not stop the loop.

/**
 * Objective: to demonstrate visibility issues
 * 1.Create a shared variable
 * 2.Create a thread that constantly reads shared variables
 * 3.Create a thread to modify shared variables
 */
public class Test01Visibility {

    // 1. Create a shared variable
    private static boolean flag = true;

    public static void main(String[] args) throws InterruptedException {
        // 2. Create a thread to continuously read shared variables
        new Thread(() -> {
            while (flag) {

            }
        }).start();

        Thread.sleep(2000);

        // 3. Create a thread to modify shared variables
        new Thread(() -> {
            flag = false;
            System.out.println("The thread modified the value of the variable to false");
        }).start();
    }

}

Summary: during concurrent programming, there will be visibility problems. When one thread modifies a shared variable, another thread does not immediately see the latest modified value.

2, 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.

Case demonstration: five threads execute i + + 1000 times each;

/**
 * Objective: to demonstrate atomicity
 * 1.Define a shared variable number
 * 2.1000 + + operation on number
 * 3.Using 5 threads
 */
public class Test02Atomictity {

    // 1. Define a shared variable number
    private static int number = 0;

    public static void main(String[] args) throws InterruptedException {
        // 2. 1000 + + operation on number
        Runnable increment = ()  -> {
            for (int i = 0; i < 1000; i++) {
                number++;
            }
        };

        List<Thread> list = new ArrayList<>();

        // 3. Use 5 threads to
        for (int i = 0; i < 5; i++) {
            Thread t = new Thread(increment);
            t.start();
            list.add(t);
        }

        for (Thread t : list) {
            // Wait until the thread ends, and then run the others. In order to end the operation of all five threads, let the main thread print the results
            t.join();
        }


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

Use the plug-in to disassemble the class file to obtain the following bytecode instructions:

 

For number + + (number is a static variable), the following JVM bytecode instructions will actually be generated:

 9 getstatic #18 <com/best/eureka/synchronzied/Test02Atomictity.number>
12 iconst_1
13 iadd
14 putstatic #18 <com/best/eureka/synchronzied/Test02Atomictity.number>

It can be seen that number + + is composed of multiple statements. The above multiple instructions will not cause problems in the case of one thread, but problems may occur in the case of multiple threads.

For example, when one thread executes 13: iadd, another thread executes 9: getstatic. It will result in number + + twice, and actually only 1 is added

Summary: during concurrent programming, atomicity problems will occur. When one thread operates half of the shared variables, another thread may also operate the shared variables, interfering with the operation of the previous thread.

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.

Case demonstration: jcstress is a java Concurrent pressure measurement tool https://wiki.openjdk.java.net/display/CodeTools/jcstress

Modify pom file and add dependency

      <dependency>
            <groupId>org.openjdk.jcstress</groupId>
            <artifactId>jcstress-core</artifactId>
            <version>0.3</version>
        </dependency>
@JCStressTest
@Outcome(id = {"1", "4"}, expect = Expect.ACCEPTABLE, desc = "ok")
@Outcome(id = "0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "danger")
@State
class Test03Orderliness {
    int num = 0;
    boolean ready = false;
    // Code executed by thread 1
    @Actor
    public void actor1(I_Result r) {
        if(ready) {
            r.r1 = num + num;
        } else {
            r.r1 = 1;
        }
    }
    // Code executed by thread 2
    @Actor
    public void actor2(I_Result r) {
        num = 2;
        ready = true;
    }
}

 I_Result is an object with an attribute r1 to save the result. How many results may occur in the case of multithreading?

Case 1: thread 1 executes actor 1 first. At this time, ready = false, so the result of entering the else branch is 1.

Case 2: thread 2 executes to actor 2 and executes num = 2; And ready = true, thread 1 executes, this time enters the if branch, and the result is 4.

Case 3: thread 2 executes actor 2 first and only executes num = 2; However, before the execution is ready = true, thread 1 executes and still enters the else branch. The result is 1.

There is another result 0.

Run test:

mvn clean install

java -jar target/jcstress.jar

Summary: the sequence of program code in the execution process. Due to the optimization of Java during compilation and runtime, the execution sequence of code may not be the sequence when developers write code.

Video tutorial

Topics: JUC