Abstract: In the multi-threaded (concurrent) scenario, how to write a thread-safety program is of great significance for the correct and stable operation of the program. The following will combine examples to talk about how to implement thread-safe programs in the Java language.

This article is shared from HUAWEI CLOUD COMMUNITY " Java Realizes Thread Safety in Multithreaded Scenarios", author: jackwangcumt.

1 Introduction

With the rapid development of computer hardware at present, the CPU on personal computers is also multi-core, and now the common number of CPU cores is 4 cores or 8 cores. Therefore, when writing a program, it is necessary to write parallel programs in order to improve efficiency and give full play to the capabilities of the hardware. As the main language of Internet applications, Java language is widely used in the development of enterprise applications. It also supports multithreading, but although multithreading is good, it has higher requirements for programming.

A program that can run correctly in a single thread does not mean that it can run correctly in a multi-threaded scenario. The correctness here is often not easy to be found, and it may only appear when the number of concurrency reaches a certain amount. This is also the reason why it is not easy to reproduce in the test session. Therefore, how to write a thread-safe program in a multithreaded (concurrent) scenario is of great significance to the correct and stable operation of the program. The following will combine examples to talk about how to implement thread-safe programs in the Java language.

In order to give a perceptual understanding, here is an example of thread insecurity, as follows:

package com.example.learn;
public class Counter {
    private static int counter = 0;
    public static int getCount(){
        return counter;
    }
    public static  void add(){
        counter = counter + 1;
    }
}

This class has a static attribute counter for counting. The static method add() can be used to add 1 to the counter, and the current counter value can also be obtained through the getCount() method. If it is single-threaded, this program is no problem, for example, if it loops 10 times, then the counter value obtained at the end is 10. However, in the case of multi-threading, this result may not be obtained correctly, and may be equal to 10, or it may be less than 10, such as 9. An example of a multi-threaded test is given below:

package com.example.learn;
public class MyThread extends Thread{
    private String name ;
    public MyThread(String name){
        this.name = name ;
    }
    public void run(){
        Counter.add();
        System.out.println("Thead["+this.name+"] Count is "+  Counter.getCount());
    }
}
///////////////////////////////////////////////////////////
package com.example.learn;
public class Test01 {
    public static void main(String[] args) {
        for(int i=0;i<5000;i++){
            MyThread mt1 = new MyThread("TCount"+i);
            mt1.start();
        }
    }
}

In order to reproduce the counting problem, the number of threads is adjusted to a larger value, here is 5000. Running this example, the possible output results are as follows:

Thead[TCount5] Count is 4
Thead[TCount2] Count is 9
Thead[TCount4] Count is 4
Thead[TCount14] Count is 10
..................................
Thead[TCount4911] Count is 4997
Thead[TCount4835] Count is 4998
Thead[TCount4962] Count is 4999

Note: In a multithreaded scenario, the output result of a program that is thread-unsafe is uncertain.

2 synchronized method

Based on the above example, the most direct way to make it a thread-safe program is to add the synchronized keyword to the corresponding method to make it a synchronized method. It can modify a class, a method and a code block. Modify the above counting program, the code is as follows:

package com.example.learn;
public class Counter {
    private static int counter = 0;
    public static int getCount(){
        return counter;
    }
    public static synchronized void add(){
        counter = counter + 1;
    }
}

Run the program again, the output is as follows:

......
Thead[TCount1953] Count is 4998
Thead[TCount3087] Count is 4999
Thead[TCount2425] Count is 5000

3 Locking mechanism

Another common synchronization method is locking. For example, there is a reentrant lock in Java, ReentrantLock, which is a recursive and non-blocking synchronization mechanism. Compared with synchronized, it can provide a more powerful and flexible locking mechanism. Can reduce the probability of deadlock. The sample code is as follows:

package com.example.learn;
import java.util.concurrent.locks.ReentrantLock;
public class Counter {
    private  static int counter = 0;
    private static final ReentrantLock lock = new ReentrantLock(true);
    public static int getCount(){
        return counter;
    }
    public static  void add(){
        lock.lock();
        try {
            counter = counter + 1;
        } finally {
            lock.unlock();
        }
    }
}

Run the program again, the output is as follows:

......
Thead[TCount1953] Count is 4998
Thead[TCount3087] Count is 4999
Thead[TCount2425] Count is 5000

Note: Java also provides a read-write lock ReentrantReadWriteLock, so that read-write separation can be performed, which is more efficient.

4 Use Atomic objects

Because the lock mechanism will affect certain performance, and in some scenarios, it can be implemented in a lock-free manner. Java has built-in Atomic related atomic operation classes, such as AtomicInteger, AtomicLong, AtomicBoolean and AtomicReference, which can be selected according to different scenarios. The sample code is given below:

package com.example.learn;
import java.util.concurrent.atomic.AtomicInteger;
public class Counter {
    private static final AtomicInteger counter = new AtomicInteger();
    public static int getCount(){
        return counter.get();
    }
    public static void add(){
        counter.incrementAndGet();
    }
}

Run the program again, the output is as follows:

......
Thead[TCount1953] Count is 4998
Thead[TCount3087] Count is 4999
Thead[TCount2425] Count is 5000

5 Stateless objects

As mentioned earlier, one reason for thread insecurity is that multiple threads access data in an object at the same time, and the data is shared. Therefore, if the data becomes exclusive, that is, stateless, then it is natural It is thread-safe. The so-called stateless method is to give the same input, it can return a consistent result. The sample code is given below:

package com.example.learn;
public class Counter {
    public static int sum (int n) {
        int ret = 0;
        for (int i = 1; i <= n; i++) {
            ret += i;
        }
        return ret;
    }
}

6 Immutable objects

As mentioned earlier, if you need to share a piece of data in multiple threads, and the given value of this data cannot be changed, it is also thread-safe, which is equivalent to a read-only attribute. In Java, attributes can be modified through the final keyword. The sample code is given below:

package com.example.learn;
public class Counter {
    public final int count ;
    public Counter (int n) {
        count = n;
    }
}

7 Summary

There are several thread-safe methods mentioned earlier. The overall idea is either to achieve synchronization through a lock mechanism, or to prevent data sharing and prevent data from being read and written in multiple threads. In addition, some articles mentioned that volatile modification can be used in front of variables to realize the synchronization mechanism, but this is not necessarily tested after testing. In some scenarios, volatile still cannot guarantee thread safety. Although the above is a summary of the experience of thread safety, it still needs to be verified through rigorous testing. Practice is the only criterion for testing truth.

Click to follow and learn about Huawei Cloud's fresh technology for the first time~


华为云开发者联盟
1.4k 声望1.8k 粉丝

生于云,长于云,让开发者成为决定性力量