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
Synchronized
Is 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++; }
- Here
synchronized
The 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:
-
synchronized
The 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 providesReentrantLock
Class, can explicitly manage locks between threads. It's bettersynchronized
More 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 asConcurrentHashMap
、CopyOnWriteArrayList
wait. 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
Volatile
The modifier ensures that the modification of the variable is visible to all threads, but it does not guarantee atomicity. Usually withsynchronized
Or 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:
-
Volatile
Only 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 providesAtomicInteger
、AtomicLong
etc. 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
ThreadLocal
Provide 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 useCountDownLatch
orCyclicBarrier
etc. 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:
- use
synchronized
Method 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.