SoFunction
Updated on 2025-04-20

How to ensure thread safety by Java

How to ensure thread safety in Java?

1. The concept of thread safety

In a multi-threaded environment, multiple threads may access and modify shared resources (such as variables, data structures, etc.) at the same time. If these operations cannot be managed correctly, the following problems may be caused:

  • Race Condition: Multiple threads perform inconsistent read and write operations on shared resources.
  • Memory visibility issues: One thread's modification of shared variables cannot be seen by other threads in time.

In order to ensure the correctness and consistency of the program in a multi-threaded environment, measures need to be taken to ensure thread safety.

2. Methods to ensure thread safety

The following are some commonly used methods in Java to ensure thread safety:

(1) Use synchronized keywords

SynchronizedIs a built-in keyword provided by Java to implement object-level locks. It can modify methods or code blocks.

Synchronization method

public synchronized void increment() {
    count++;
}
  • HeresynchronizedThe current instance is locked (this), only the thread holding the lock can execute the method.

Synchronize code blocks

public void increment() {
    synchronized (this) { // Can be replaced with other objects        count++;
    }
}

Notes:

  • synchronizedThe granularity should be as small as possible to avoid blocking too much code.
  • The locked object must be the same instance, otherwise synchronization cannot be achieved.

(2) Use ReentrantLock (explicit lock)

Java providesReentrantLockClass, can explicitly manage locks between threads. It's bettersynchronizedMore flexible.

import ;

public class Counter {
    private int count = 0;
    private ReentrantLock lock = new ReentrantLock();

    public void increment() {
        (); // Add lock        try {
            count++;
        } finally {
            (); // Unlock, it must be placed in finally to ensure that the lock is released        }
    }
}

Features:

  • An explicit lock requires manual management of the acquisition and release of the lock.
  • Supports more complex synchronization logic (such as fair locks, interruptible locks, etc.).

(3) Avoid sharing state

If possible, try to let each thread have its own copy of its data to avoid sharing state. This will completely avoid thread safety issues.

For example:

public class ThreadSafeCounter implements Runnable {
    private int count;

    public void run() {
        // Each thread has an independent counter        for (int i = 0; i < 1000; i++) {
            count++;
        }
    }

    public int getCount() {
        return count;
    }
}

advantage:

  • Simple and efficient.
  • No synchronization mechanism is required.

(4) Use thread-safe collections

Java provides some thread-safe collection classes, such asConcurrentHashMapCopyOnWriteArrayListwait. They implement synchronization mechanisms internally, which can avoid the complexity of manually managing locks.

For example:

import ;

public class ThreadSafeMap {
    private ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();

    public void put(String key, Integer value) {
        (key, value);
    }

    public Integer get(String key) {
        return (key);
    }
}

Features:

  • Efficient concurrent control is implemented internally.
  • No additional synchronization logic is required when used.

(5) Use the volatile keyword

VolatileThe modifier ensures that the modification of the variable is visible to all threads, but it does not guarantee atomicity. Usually withsynchronizedOr use it in combination with other locking mechanisms.

For example:

public class VolatileExample {
    private volatile boolean flag = false;

    public void setFlag() {
        flag = true;
    }

    public void checkFlag() {
        while (!flag) {
            // Wait for flag to be set to true        }
    }
}

Notes:

  • VolatileOnly visibility can be guaranteed, but it cannot replace the locking mechanism.
  • Not suitable for complex operations (such as self-increment).

(6) Use atomic class (AtomicXXX)

Java providesAtomicIntegerAtomicLongetc. atomic classes that achieve lock-free thread safety through CAS (Compare-and-Swap) operations.

For example:

import ;

public class AtomicCounter {
    private AtomicInteger count = new AtomicInteger(0);

    public void increment() {
        (); // Atomic operation    }

    public int getCount() {
        return ();
    }
}

Features:

  • Efficient and lock-free.
  • Suitable for simple numerical operations.

(7) Avoid using static variables

Static variables are at the class level, and all threads share the same instance. If handled improperly, it may cause thread safety issues.

For example:

public class StaticCounter {
    private static int count = 0;

    public synchronized static void increment() { // Requires synchronization        count++;
    }
}

Notes:

  • Static methods or variables need to be explicitly synchronized.
  • Minimize the use of static variables to avoid thread safety issues.

(8) Use ThreadLocal

ThreadLocalProvide each thread with an independent copy of variables can effectively avoid thread safety issues.

For example:

import ;
import ;

public class ThreadLocalList {
    private static final ThreadLocal<List<String>> threadLocal = new ThreadLocal<>();

    public void add(String value) {
        List<String> list = ();
        if (list == null) {
            list = new ArrayList<>();
            (list);
        }
        (value);
    }

    public List<String> getList() {
        return ();
    }
}

Features:

  • Each thread has its own copy of variables.
  • Suitable for scenarios where thread independent state is required.

(9) Use CountDownLatch or CyclicBarrier

In complex multithreaded scenarios, you can useCountDownLatchorCyclicBarrieretc. to coordinate synchronization between threads.

For example:

import ;

public class Counter {
    private int count = 0;
    private CountDownLatch latch = new CountDownLatch(1);

    public void increment() {
        try {
            // Block until Latch is released            ();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        count++;
        (); // Notify other threads to continue execution    }
}

Features:

  • Provides a more advanced synchronization mechanism.
  • Can handle complex thread coordination problems.

(10) Avoid common mistakes of synchronized

Error example:

public class Counter {
    private int count = 0;

    public void increment() { // No synchronization may result in inconsistent data        count++;
    }

    public int getCount() {
        return count;
    }
}

Correct way to do it:

  • usesynchronizedMethod or block.
  • Use thread-safe classes (e.g.AtomicInteger)。

Summarize

Here are the main ways to ensure thread safety in Java:

method describe
synchronized Built-in keywords for implementing object-level locks.
ReentrantLock Explicit locks, providing more flexible synchronization control.
Avoid sharing status Each thread has its own copy of the data, completely avoiding thread safety issues.
Thread-safe collection class For example, ConcurrentHashMap implements efficient concurrency control internally.
volatile Ensure the visibility of variables, but it cannot replace the locking mechanism.
Atomic class (AtomicXXX) Lock-free thread safety is achieved through CAS operations.
Avoid using static variables Static variables are class-level and need to be explicitly synchronized.
ThreadLocal Provides an independent copy of variables for each thread.

Choosing the right method depends on the specific scenario and performance requirements.

The above is personal experience. I hope you can give you a reference and I hope you can support me more.