Four Common Ways to Implement Thread Synchronization in Java
This article explains the concept of thread synchronization in Java and provides detailed examples of four implementation methods—using the synchronized keyword, ReentrantLock, atomic variables, and ThreadLocal—along with code snippets and a comparison of their advantages and usage scenarios.
Java thread synchronization is a core topic of multithreading and concurrency programming, essential for preventing data inconsistency when multiple threads access shared resources.
What Is Thread Synchronization
When several threads operate on the same data simultaneously, conflicts can arise, leading to inaccurate results; synchronization ensures that threads access shared resources one at a time, effectively "queueing" them.
Thread Synchronization Methods
1. Using the synchronized Keyword
This approach synchronizes a specific code block (a synchronized statement) rather than an entire method, reducing lock contention.
synchronized (object) {
// critical section
}Example implementation:
public class SynchronizedThread {
class Bank {
private int account = 200;
public int getAccount() { return account; }
public synchronized void save(int money) { account += money; }
}
class NewThread implements Runnable {
private Bank bank;
public NewThread(Bank bank) { this.bank = bank; }
@Override
public void run() {
for (int i = 0; i < 10; i++) {
bank.save(10);
System.out.println(i + " account balance: " + bank.getAccount());
}
}
}
public void useThread() {
Bank bank = new Bank();
NewThread new_thread = new NewThread(bank);
Thread thread1 = new Thread(new_thread);
Thread thread2 = new Thread(new_thread);
thread1.start();
thread2.start();
}
public static void main(String[] args) {
SynchronizedThread st = new SynchronizedThread();
st.useThread();
}
}2. Using ReentrantLock
ReentrantLock implements the Lock interface, offering explicit lock control with the same basic semantics as synchronized but with additional capabilities.
private int account = 100;
private Lock lock = new ReentrantLock();
public int getAccount() { return account; }
public void save(int money) {
lock.lock();
try {
account += money;
} finally {
lock.unlock();
}
}3. Using Atomic Variables
Atomic classes (e.g., AtomicInteger ) provide lock‑free, thread‑safe operations on single variables.
private AtomicInteger account = new AtomicInteger(100);
public AtomicInteger getAccount() { return account; }
public void save(int money) {
account.addAndGet(money);
}4. Using ThreadLocal
ThreadLocal gives each thread its own independent copy of a variable, eliminating interference between threads.
public class Bank {
private static ThreadLocal
account = new ThreadLocal
() {
@Override
protected Integer initialValue() { return 100; }
};
public void save(int money) {
account.set(account.get() + money);
}
public int getAccount() { return account.get(); }
}Comparison of synchronized and ReentrantLock
ReentrantLock is an explicit lock that must be manually released, while synchronized is implicit and automatically releases when the block exits. ReentrantLock only provides block‑level locking, whereas synchronized supports both block and method locking. In most cases, the recommended order of preference is: ReentrantLock > synchronized block > synchronized method.
For deeper insight into the underlying implementation of synchronized , refer to the linked article on its internal mechanism.
Mike Chen's Internet Architecture
Over ten years of BAT architecture experience, shared generously!
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.