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 sections, combined with examples, show how to implement thread-safe programs in the Java language.

This article was shared by jackwangcumt from the huawei cloud community, “how Java implements thread safety in multi-threaded scenarios”.

1, the introduction

At present, with the rapid development of computer hardware, the CPU on personal computer is also multi-core, now the common number of CPU core is 4 or 8 core. Therefore, when you write programs, you need to write parallel programs in order to improve the efficiency and make full use of the hardware. As the main language of Internet application, Java language is widely used in the development of enterprise application programs. It also supports Multithreading (Multithreading), but although Multithreading is good, it has high requirements for the programming.

A program that works correctly in a single-threaded scenario does not work correctly in a multi-threaded scenario. The correctness here is often not easy to detect, and it is only possible when the number of concurrent applications reaches a certain level. This is why it is not easy to reproduce during testing. Therefore, it is of great significance for the correct and stable operation of the program how to write a thread-safety program in the multi-threaded (concurrent) scenario. The following sections, combined with examples, show how to implement thread-safe programs in the Java language.

To give you some sense, here’s an example of thread insecurity:

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; }}Copy the code

This class has a static property, counter, for counting. The static add() method can be used to increment the counter by 1, and the getCount() method can be used to get the current counter value. This program is fine if it is single threaded, say, 10 times, and the count counter is 10. But if you have multiple threads, then you don’t necessarily get this right, it could be equal to 10, it could be less than 10, like 9. Here’s an example of a multithreaded test:

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();
        }
    }
}
Copy the code

So in order to reproduce the count, I’m going to make the number of threads a little bit larger, which is 5000. Running this example produces the following possible output:

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 4999Copy the code

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

2, synchronized method

Based on the example above, the most straightforward way to make this a thread-safe program is to add the synchronized keyword to the corresponding method and make it a synchronized method. It can decorate a class, a method, and a block of code. To 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; }}Copy the code

Run the program again, and the output is as follows:

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

3. Locking mechanism

Another common method of synchronization is locking. For example, Java has ReentrantLock, which is a recursive non-blocking mechanism. It provides a more powerful and flexible locking mechanism than synchronized, while reducing the probability of deadlocks. The example 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(); }}}Copy the code

Run the program again, and the output is as follows:

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

Note: Java also provides a read/write lock ReentrantReadWriteLock, which can be separated from the read/write, more efficient.

4. Use Atomic objects

The locking mechanism may affect performance, but in some scenarios, it can be implemented without locking. Java has built-in Atomic related Atomic operations, such as AtomicInteger AtomicLong, AtomicBoolean and AtomicReference, can undertake choosing according to different scenarios. Here’s an example code:

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(); }}Copy the code

Run the program again, and the output is as follows:

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

Stateless objects

As mentioned earlier, one reason for thread insecurity is that multiple threads are accessing data in an object at the same time. Data is shared, so making the data exclusive (stateless) is naturally thread-safe. A stateless method is one that gives the same input and returns the same result. Here’s an example code:

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; }}Copy the code

6. Immutable objects

As mentioned earlier, if you need to share data across multiple threads that cannot be changed for a given value, it is also a thread-safe, read-only property. Properties can be modified in Java with the final keyword. Here’s an example code:

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

7,

Several thread-safe approaches have been mentioned, but the general idea is either to synchronize through locking or to prevent data sharing and reading and writing across multiple threads. In addition, some articles have stated that it is possible to use volatile in front of variables to achieve synchronization, but this has not been tested. In some scenarios, volatile still does not guarantee thread safety. Although the above is a summary of thread-safety experience, but still need to be verified through rigorous testing, practice is the only standard to test the truth.

Click follow to learn about the fresh technologies of Huawei Cloud