10 Proven Techniques to Ensure Thread Safety in Java Backend Development
This article explains why thread safety is critical for backend developers, describes common data race problems, and provides ten practical strategies—including stateless design, immutability, synchronized blocks, locks, distributed locks, volatile variables, ThreadLocal, concurrent collections, CAS, and data isolation—to reliably protect shared resources in multithreaded Java applications.
Introduction
For backend developers, thread safety is a daily concern. It occurs when multiple threads simultaneously read and write shared (critical) resources, leading to data anomalies that can break business functionality. This article presents ten practical techniques to guarantee thread safety.
1. Stateless
If a service does not hold any shared mutable state, it is inherently thread‑safe. The following example shows a class without any public fields; therefore, concurrent calls cannot corrupt data.
public class NoStatusService {
public void add(String status) {
System.out.println("add status:" + status);
}
public void update(String status) {
System.out.println("update status:" + status);
}
}2. Immutable
When shared resources are immutable (e.g., static final constants), they cannot be modified by any thread, eliminating race conditions.
public class NoChangeService {
public static final String DEFAULT_NAME = "abc";
public void add(String status) {
System.out.println(DEFAULT_NAME);
}
}3. No Modification Permission
If a public resource only exposes read access and never provides a mutator, it remains thread‑safe.
public class SafePublishService {
private String name;
public String getName() {
return name;
}
public void add(String status) {
System.out.println("add status:" + status);
}
}4. synchronized
The JDK offers built‑in synchronization via synchronized methods and synchronized blocks. Synchronized blocks are preferred because they limit the locked scope, reducing performance impact.
public class SyncService {
private int age = 1;
private Object object = new Object();
// synchronized method
public synchronized void add(int i) {
age = age + i;
System.out.println("age:" + age);
}
// synchronized block (object lock)
public void update(int i) {
synchronized (object) {
age = age + i;
System.out.println("age:" + age);
}
}
// synchronized block (class lock)
public void updateClass(int i) {
synchronized (SyncService.class) {
age = age + i;
System.out.println("age:" + age);
}
}
}5. Lock
Beyond synchronized, the JDK provides the Lock interface (e.g., ReentrantLock) with features such as fairness, re‑entrancy, and read/write modes.
public class LockService {
private ReentrantLock reentrantLock = new ReentrantLock();
public int age = 1;
public void add(int i) {
try {
reentrantLock.lock();
age = age + i;
System.out.println("age:" + age);
} finally {
reentrantLock.unlock();
}
}
}6. Distributed Lock
In a distributed environment, JVM‑level synchronization cannot coordinate across nodes. Distributed locks (e.g., based on Redis, Zookeeper, or databases) are required. The following pseudo‑code demonstrates a Redis lock.
try {
String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime);
if ("OK".equals(result)) {
return true;
}
return false;
} finally {
unlock(lockKey);
}7. volatile
When only visibility (not atomicity) is needed—such as a boolean flag that stops a service—declaring the variable volatile ensures all threads see the latest value.
@Service
public class CanalService {
private volatile boolean running = false;
private Thread thread;
// ... handle(), start(), stop() methods omitted for brevity ...
}Note: volatile must not be used for counters or other operations that require atomicity.
8. ThreadLocal
ThreadLocal provides a “space‑for‑time” trade‑off by giving each thread its own copy of a variable, eliminating contention for that data.
public class ThreadLocalService {
private ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
public void add(int i) {
Integer integer = threadLocal.get();
threadLocal.set(integer == null ? 0 : integer + i);
}
}Remember to call remove() in a finally block to avoid memory leaks.
9. Thread‑Safe Collections
When shared collections are needed, use the concurrent implementations provided by the JDK, such as CopyOnWriteArrayList, ConcurrentHashMap, etc.
public class HashMapTest {
private static ConcurrentHashMap<String, Object> hashMap = new ConcurrentHashMap<>();
public static void main(String[] args) {
new Thread(() -> hashMap.put("key1", "value1")).start();
new Thread(() -> hashMap.put("key2", "value2")).start();
try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(hashMap);
}
}10. CAS
The Compare‑And‑Swap (CAS) mechanism offers lock‑free atomic updates via the Unsafe class or higher‑level java.util.concurrent.atomic utilities.
public class AtomicService {
private AtomicInteger atomicInteger = new AtomicInteger();
public int add(int i) {
return atomicInteger.getAndAdd(i);
}
}CAS can suffer from the ABA problem; using AtomicStampedReference adds a version stamp to avoid it.
11. Data Isolation
Isolating data per thread or per partition (e.g., Kafka partitions) ensures that only one thread processes a given piece of data, thereby avoiding contention.
public class ThreadPoolTest {
public static void main(String[] args) {
ExecutorService threadPool = new ThreadPoolExecutor(
8, 10, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(500),
new ThreadPoolExecutor.CallerRunsPolicy()
);
List<User> userList = Lists.newArrayList(
new User(1L, "Su San", 18, "Chengdu"),
new User(2L, "Su San Tech", 20, "Sichuan"),
new User(3L, "Tech", 25, "Yunnan")
);
for (User user : userList) {
threadPool.submit(new Work(user));
}
try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(userList);
}
static class Work implements Runnable {
private User user;
public Work(User user) { this.user = user; }
@Override public void run() { user.setName(user.getName() + "Test"); }
}
}In Kafka, sending all messages of the same order to the same partition and processing that partition with a single consumer thread achieves the same isolation.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Su San Talks Tech
Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.cn.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
