Make writing a habit together! This is the second day of my participation in the “Gold Digging Day New Plan · April More text challenge”. Click here for more details.

Thread safety refers to a method or a piece of code that can be correctly executed in multiple threads without data inconsistency or data pollution. We call such a program thread-safe, whereas it is non-thread-safe. There are three ways to address thread-safety issues in Java:

  1. Use thread-safe classes, such as AtomicInteger.
  2. Lock queue execution
    1. Use synchronized locking.
    2. Use ReentrantLock to lock.
  3. Use the thread-local variable ThreadLocal.

Let’s look at their implementation one by one.

Demonstration of thread safety issues

We create a variable number equal to 0, and then we create thread 1, and we do a million ++ operations, and we create thread 2, and we do a million — operations, and then we print the value of the variable number, and if we print 0, Thread-safe, otherwise, non-thread-safe. Example code is as follows:

public class ThreadSafeTest {
    // Global variables
    private static int number = 0;
    // Loop number (100W)
    private static final int COUNT = 1 _000_000;

    public static void main(String[] args) throws InterruptedException {
        Thread 1: perform 100W ++ operations
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < COUNT; i++) { number++; }}); t1.start();Thread 2: perform 100W -- operations
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < COUNT; i++) { number--; }}); t2.start();// Wait for thread 1 and thread 2 to execute, and print the final result of number
        t1.join();
        t2.join();
        System.out.println("Number final result:"+ number); }}Copy the code

The execution result of the above program is as follows:As can be seen from the above execution results, the final result of the number variable is not 0, which is inconsistent with the expected correct result, which is the thread safety problem in multithreading.

Address thread safety issues

1. AtomicInteger

AtomicInteger is a thread-safe class that can be used to turn the ++ and — operations into one atomic operation, thus solving the non-thread-safe problem, as shown in the following code:

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerExample {
    / / create AtomicInteger
    private static AtomicInteger number = new AtomicInteger(0);
    // The number of cycles
    private static final int COUNT = 1 _000_000;

    public static void main(String[] args) throws InterruptedException {
        Thread 1: perform 100W ++ operations
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < COUNT; i++) {
                / / + + operationsnumber.incrementAndGet(); }}); t1.start();Thread 2: perform 100W -- operations
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < COUNT; i++) {
                / / - operationnumber.decrementAndGet(); }}); t2.start();// Wait for thread 1 and thread 2 to execute, and print the final result of number
        t1.join();
        t2.join();
        System.out.println("Final result:"+ number.get()); }}Copy the code

The execution result of the above program is as follows:

2. Lock and queue

There are two types of locks in Java: synchronized and ReentrantLock.

2.1 Synchronized

Synchronized is the automatic lock and automatic lock release of the JVM level, its implementation code is as follows:

public class SynchronizedExample {
    // Global variables
    private static int number = 0;
    // Loop number (100W)
    private static final int COUNT = 1 _000_000;

    public static void main(String[] args) throws InterruptedException {
        Thread 1: perform 100W ++ operations
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < COUNT; i++) {
                // Lock queue execution
                synchronized(SynchronizedExample.class) { number++; }}}); t1.start();Thread 2: perform 100W -- operations
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < COUNT; i++) {
                // Lock queue execution
                synchronized(SynchronizedExample.class) { number--; }}}); t2.start();// Wait for thread 1 and thread 2 to execute, and print the final result of number
        t1.join();
        t2.join();
        System.out.println("Number final result:"+ number); }}Copy the code

The execution result of the above program is as follows:

2.2 ReentrantLock

ReentrantLock ReentrantLock requires the programmer to lock and release the lock itself.

import java.util.concurrent.locks.ReentrantLock;

/** * Use ReentrantLock to solve non-thread-safe problems */
public class ReentrantLockExample {
    // Global variables
    private static int number = 0;
    // Loop number (100W)
    private static final int COUNT = 1 _000_000;
    / / created already
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        Thread 1: perform 100W ++ operations
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < COUNT; i++) {
                lock.lock();    // Manually lock
                number++;       / / + + operations
                lock.unlock();  // Manually release the lock}}); t1.start();Thread 2: perform 100W -- operations
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < COUNT; i++) {
                lock.lock();    // Manually lock
                number--;       / / - operation
                lock.unlock();  // Manually release the lock}}); t2.start();// Wait for thread 1 and thread 2 to execute, and print the final result of number
        t1.join();
        t2.join();
        System.out.println("Number final result:"+ number); }}Copy the code

The execution result of the above program is as follows:

3. The thread-local variable ThreadLocal

ThreadLocal creates its own private variables for each thread. Different threads operate on different variables, so there are no non-thread-safe problems.

public class ThreadSafeExample {
    // Create a ThreadLocal (set the initial value in each thread to 0)
    private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);
    // Global variables
    private static int number = 0;
    // Loop number (100W)
    private static final int COUNT = 1 _000_000;

    public static void main(String[] args) throws InterruptedException {
        Thread 1: perform 100W ++ operations
        Thread t1 = new Thread(() -> {
            try {
                for (int i = 0; i < COUNT; i++) {
                    / / + + operations
                    threadLocal.set(threadLocal.get() + 1);
                }
                // Add the values in ThreadLocal
                number += threadLocal.get();
            } finally {
                threadLocal.remove(); // Clear resources to prevent memory overflow}}); t1.start();Thread 2: perform 100W -- operations
        Thread t2 = new Thread(() -> {
            try {
                for (int i = 0; i < COUNT; i++) {
                    / / - operation
                    threadLocal.set(threadLocal.get() - 1);
                }
                // Add the values in ThreadLocal
                number += threadLocal.get();
            } finally {
                threadLocal.remove(); // Clear resources to prevent memory overflow}}); t2.start();// Wait for thread 1 and thread 2 to execute, and print the final result of number
        t1.join();
        t2.join();
        System.out.println("Final result:"+ number); }}Copy the code

The execution result of the above program is as follows:

conclusion

In Java, there are three ways to solve thread-safety problems: 1. Use thread-safe classes, such as AtomicInteger class; 2. 2. Use synchronized or ReentrantLock to queue execution. 3. Use the thread-local variable ThreadLocal.

Judge right and wrong from yourself, praise to listen to others, gain and loss in the number.

Collection of articles: gitee.com/mydb/interv…