Java Concurrent Programming (03): multithreading, concurrent access, synchronization control

Source code of this article: GitHub · click here | gitee · click here

1、 Concurrency problem

When multi-threaded learning, the first complex problem to face is the access of variables in concurrent mode. If you don't understand the internal process and reasons, there is often such a problem: the variable value processed by the thread is not what you want, and you may say with a confused face: is this illogical?

1. Member variable access

Multiple threads accessing class member variables may cause various problems.

public class AccessVar01 {
    public static void main(String[] args) {
        Var01Test var01Test = new Var01test() ;
        VarThread01A varThread01A = new VarThread01A(var01Test) ;
        varThread01A.start();
        VarThread01B varThread01B = new VarThread01B(var01Test) ;
        varThread01B.start();
    }
}
class VarThread01A extends Thread {
    Var01Test var01Test = new Var01test() ;
    public VarThread01A (Var01Test var01Test){
        this.var01Test = var01Test ;
    }
    @Override
    public void run() {
        var01Test.addNum(50);
    }
}
class VarThread01B extends Thread {
    Var01Test var01Test = new Var01test() ;
    public VarThread01B (Var01Test var01Test){
        this.var01Test = var01Test ;
    }
    @Override
    public void run() {
        var01Test.addNum(10);
    }
}
class Var01Test {
    private Integer num = 0 ;
    public void addNum (Integer var){
        try {
            if (var == 50){
                num = num + 50 ;
                Thread.sleep(3000);
            } else {
                num = num + var ;
            }
            System.out.println("var="+var+";num="+num);
        } catch (Exception e){
            e.printStackTrace();
        }
    }
}

The flow of the case here is to calculate a member variable concurrently. The original intention of the program is: VAR = 50, get num = 50, and the actual output is:

var=10;num=60
var=50;num=60

Varthread01a thread processing enters sleep. During sleep, Num has been added by thread varthread01b once. This is the result of multi-threaded concurrent access.

2. Method private variable

Modify the above code logic and put the num variable in the method as a private method variable.

class Var01Test {
    // private Integer num = 0 ;
    public void addNum (Integer var){
        Integer num = 0 ;
        try {
            if (var == 50){
                num = num + 50 ;
                Thread.sleep(3000);
            } else {
                num = num + var ;
            }
            System.out.println("var="+var+";num="+num);
        } catch (Exception e){
            e.printStackTrace();
        }
    }
}

The variables inside the method are private and bound to the thread executing the method. There will be no interference between threads.

2、 Synchronous control

1. Synchronized keyword

Usage: modify the method, or control the synchronization block to ensure that only one thread enters the method at the same time when multiple threads are concurrent, or synchronize the code block, so that the thread can safely access and process variables. If the modification is a static method, it works on all objects of this class.

Exclusive locks belong to pessimistic locks. Synchronized is an exclusive lock. Assuming that in the worst case, only one thread executes and blocks other threads. If concurrency is high and processing time is long, multiple threads will hang and wait for the thread holding the lock to release the lock.

2. Modification method

The principle of this case is the same as that of the first case. However, although the synchronization control is added where the value is modified, another pit is dug, and there is no restriction on reading. This phenomenon is commonly known as dirty reading.

public class AccessVar02 {
    public static void main(String[] args) {
        Var02Test var02Test = new Var02Test ();
        VarThread02A varThread02A = new VarThread02A(var02Test) ;
        varThread02A.start();
        VarThread02B varThread02B = new VarThread02B(var02Test) ;
        varThread02B.start();
        var02Test.readValue();
    }
}
class VarThread02A extends Thread {
    Var02Test var02Test = new Var02Test ();
    public VarThread02A (Var02Test var02Test){
        this.var02Test = var02Test ;
    }
    @Override
    public void run() {
        var02Test.change("my","name");
    }
}
class VarThread02B extends Thread {
    Var02Test var02Test = new Var02Test ();
    public VarThread02B (Var02Test var02Test){
        this.var02Test = var02Test ;
    }
    @Override
    public void run() {
        var02Test.change("you","age");
    }
}
class Var02Test {
    public String key = "cicada" ;
    public String value = "smile" ;
    public synchronized void change (String key,String value){
        try {
            this.key = key ;
            Thread.sleep(2000);
            this.value = value ;
            System.out.println("key="+key+";value="+value);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public void readValue (){
        System.out.println("读取:key="+key+";value="+value);
    }
}

In the thread, it has been logically modified, but it has not been executed, but the value read in the main thread is meaningless. It is necessary to add synchronous thread control to the reading method.

3. Synchronous control logic

The implementation of synchronization control is an object-based monitor.

4. Decorated code block

To illustrate, the code block contains all the logic in the method. The granularity of locking is the same as that of the modification method. Just write it on the method. A core purpose of synchronizing code blocks is to reduce the granularity of locking resources, just like table locks and row level locks.

public class AccessVar03 {
    public static void main(String[] args) {
        Var03Test var03Test1 = new Var03test() ;
        Thread thread1 = new Thread(var03Test1) ;
        thread1.start();
        Thread thread2 = new Thread(var03Test1) ;
        thread2.start();
        Thread thread3 = new Thread(var03Test1) ;
        thread3.start();
    }
}
class Var03Test implements Runnable {
    private Integer count = 0 ;
    public void countAdd() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized(this) {
            count++ ;
            System.out.println("count="+count);
        }
    }
    @Override
    public void run() {
        countAdd() ;
    }
}

Here is the core code logic for locking count to handle this action. Concurrent processing is not allowed.

5. Modified static method

Static methods belong to class level methods, and objects cannot be called directly. However, the static method of synchronized decoration locks all objects of this class.

public class AccessVar04 {
    public static void main(String[] args) {
        Var04Test var04Test1 = new Var04test() ;
        Thread thread1 = new Thread(var04Test1) ;
        thread1.start();
        Var04Test var04Test2 = new Var04test() ;
        Thread thread2 = new Thread(var04Test2) ;
        thread2.start();
    }
}
class Var04Test implements Runnable {
    private static Integer count ;
    public Var04Test (){
        count = 0 ;
    }
    public synchronized static void countAdd() {
        System.out.println(Thread.currentThread().getName()+";count="+(count++));
    }
    @Override
    public void run() {
        countAdd() ;
    }
}

If synchronous control is not used, the output results should be as follows logically and visually:

Thread-0;count=0
Thread-1;count=0

After adding synchronization control, the actual test output results are as follows:

Thread-0;count=0
Thread-1;count=1

6. Precautions

3、 Volatile keyword

1. Basic description

In the JAVA memory model, in order to improve performance, threads copy copies of variables to be accessed in their own working memory. In this way, the value of the same variable in the environment of one thread may be inconsistent with the value in the environment of another thread at a certain time.

Using volatile to modify a member variable cannot modify a method, that is, to identify that the thread needs to obtain the variable from the shared memory when accessing the variable. The modification of the variable also needs to be refreshed to the shared memory synchronously, so as to ensure the visibility of the variable to all threads.

2. Use case

class Var05Test {
    private volatile boolean myFlag = true ;
    public void setFlag (boolean myFlag){
        this.myFlag = myFlag ;
    }
    public void method() {
        while (myFlag){
            try {
                System.out.println(Thread.currentThread().getName()+myFlag);
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

3. Precautions

4、 Source code address

GitHub·地址
https://github.com/cicadasmile/java-base-parent
GitEE·地址
https://gitee.com/cicadasmile/java-base-parent
The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>