java线程同步工具:`synchronized`、`ReentrantLock`与其他并发工具的对比与应用
在Java多线程编程中,线程同步和并发控制是确保程序正确性和性能的关键。Java提供了多种同步工具,包括内置的synchronized
关键字、ReentrantLock
以及java.util.concurrent
包中的高级并发工具。这些工具各有特点,适用于不同的并发场景。本文将详细对比synchronized
与ReentrantLock
,并介绍其他常用的线程工具,帮助开发者根据需求选择合适的工具。
一、synchronized
与ReentrantLock
的对比
1. 基本概念
synchronized
:- Java的内置关键字,基于JVM的监视器锁(monitor lock)实现。
- 用于方法或代码块的同步,自动获取和释放锁。
- 是隐式锁,依赖JVM管理,代码简洁但灵活性较低。
ReentrantLock
:java.util.concurrent.locks
包中的显式锁,基于AQS(AbstractQueuedSynchronizer)实现。- 需要手动调用
lock()
和unlock()
来获取和释放锁。 - 提供更高的灵活性和功能,但需要开发者显式管理锁的释放。
2. 主要区别
特性 | synchronized | ReentrantLock |
---|---|---|
锁的类型 | 内置锁,JVM级别实现 | 显式锁,基于AQS实现 |
可重入性 | 支持(同一线程可多次获取锁) | 支持(同一线程可多次获取锁) |
灵活性 | 较低,自动获取/释放锁 | 较高,支持更多高级功能 |
锁的获取方式 | 自动获取,阻塞直到获取 | 可选择阻塞(lock() )或非阻塞(tryLock() ) |
公平性 | 非公平锁(不保证线程获取锁的顺序) | 可配置公平锁(new ReentrantLock(true) )或非公平锁 |
条件变量 | 单一条件(wait() /notify() ) | 支持多个Condition 对象(更灵活的等待/通知) |
中断响应 | 不支持锁获取中断 | 支持(lockInterruptibly() ) |
超时机制 | 不支持超时获取锁 | 支持(tryLock(long, TimeUnit) ) |
性能 | 现代JVM优化后性能较好 | 在高并发场景下通常更高效 |
释放锁的方式 | 自动释放(离开同步块或方法) | 需手动调用unlock() ,否则可能导致死锁 |
异常处理 | 异常后自动释放锁 | 需在finally 块中显式释放锁 |
3. 功能对比
synchronized
:- 简单易用,适合简单的同步场景。
- 使用
wait()
和notify()/notifyAll()
实现线程间的条件等待,功能有限。 - 无法中断等待锁的线程或设置锁超时。
- 示例:
class Counter {private int count = 0;public synchronized void increment() {count++;}public synchronized int getCount() {return count;} }
ReentrantLock
:- 提供非阻塞锁获取(
tryLock()
)、中断锁获取(lockInterruptibly()
)和超时机制。 - 支持多个
Condition
对象,适合复杂条件等待场景。 - 可配置公平锁,减少线程饥饿。
- 示例:
import java.util.concurrent.locks.ReentrantLock; class Counter {private int count = 0;private final ReentrantLock lock = new ReentrantLock();public void increment() {lock.lock();try {count++;} finally {lock.unlock();}}public int getCount() {lock.lock();try {return count;} finally {lock.unlock();}} }
- 提供非阻塞锁获取(
4. 使用场景
synchronized
: 适合简单同步场景,代码简洁,适用于共享变量保护或方法同步。ReentrantLock
: 适合需要高级功能的场景,如超时控制、中断响应、公平锁或多条件等待(如生产者-消费者模型)。- 示例:
ReentrantLock lock = new ReentrantLock(); Condition condition = lock.newCondition(); void produce() {lock.lock();try {while (buffer.isFull()) {condition.await();}buffer.add(item);condition.signal();} finally {lock.unlock();} }
- 示例:
5. 性能
- 早期Java中,
synchronized
是重量级锁,性能较低。现代JVM(Java 6及以上)通过锁升级(偏向锁→轻量级锁→重量级锁)优化,性能接近ReentrantLock
。 ReentrantLock
在高并发场景下通常更高效,尤其在需要公平锁或复杂条件等待时。
6. 选择建议
- 优先使用
synchronized
,因为它简单且现代JVM优化良好。 - 当需要中断、超时、公平锁或多条件等待时,选择
ReentrantLock
。
二、其他常用线程工具
Java的java.util.concurrent
包提供了丰富的并发工具,适用于更复杂的多线程场景。以下是常用的工具及其应用:
1. 线程池相关工具
ExecutorService
/Executors
:- 管理线程池,支持任务提交、批量执行和线程复用。
- 常用实现:
Executors.newFixedThreadPool(int n)
: 固定大小线程池。Executors.newCachedThreadPool()
: 动态调整线程数。Executors.newSingleThreadExecutor()
: 单线程顺序执行。Executors.newScheduledThreadPool(int n)
: 支持定时任务。
- 使用场景:批量任务处理、后台任务、定时任务。
- 示例:
ExecutorService executor = Executors.newFixedThreadPool(4); executor.submit(() -> System.out.println("Task executed")); executor.shutdown();
ForkJoinPool
:- 专为分治算法设计,基于工作窃取算法。
- 使用场景:并行排序、矩阵计算。
- 示例:
ForkJoinPool pool = ForkJoinPool.commonPool(); RecursiveTask<Integer> task = new MyTask(); Integer result = pool.invoke(task);
2. 并发集合
ConcurrentHashMap
:- 线程安全的哈希表,锁粒度细,适合高并发读写。
- 使用场景:共享键值存储。
- 示例:
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>(); map.put("key", 1); map.computeIfPresent("key", (k, v) -> v + 1);
CopyOnWriteArrayList
/CopyOnWriteArraySet
:- 写时复制,适合读多写少场景。
- 使用场景:事件监听器列表、只读配置。
- 示例:
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>(); list.add("item");
BlockingQueue
:- 线程安全队列,支持阻塞操作。
- 实现类:
ArrayBlockingQueue
、LinkedBlockingQueue
、PriorityBlockingQueue
、SynchronousQueue
。 - 使用场景:生产者-消费者模型。
- 示例:
BlockingQueue<String> queue = new ArrayBlockingQueue<>(10); queue.put("task"); String task = queue.take();
3. 锁和同步工具
ReadWriteLock
/ReentrantReadWriteLock
:- 读写分离锁,允许多线程读、单线程写。
- 使用场景:缓存系统、数据库访问。
- 示例:
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); rwLock.readLock().lock(); try {// 读操作 } finally {rwLock.readLock().unlock(); }
Semaphore
:- 控制同时访问资源的线程数。
- 使用场景:数据库连接池、限流。
- 示例:
Semaphore semaphore = new Semaphore(3); semaphore.acquire(); try {// 访问资源 } finally {semaphore.release(); }
CountDownLatch
:- 让线程等待其他线程完成操作。
- 使用场景:多线程任务协调。
- 示例:
CountDownLatch latch = new CountDownLatch(3); new Thread(() -> {// 任务latch.countDown(); }).start(); latch.await();
CyclicBarrier
:- 让线程组在屏障点同步。
- 使用场景:多阶段计算。
- 示例:
CyclicBarrier barrier = new CyclicBarrier(3, () -> System.out.println("All reached")); new Thread(() -> {barrier.await(); }).start();
Phaser
:- 动态管理多阶段同步。
- 使用场景:复杂多阶段任务。
- 示例:
Phaser phaser = new Phaser(3); new Thread(() -> {phaser.arriveAndAwaitAdvance(); }).start();
4. 原子操作类
AtomicInteger
/AtomicLong
/AtomicBoolean
:- 基于CAS的无锁原子操作。
- 使用场景:计数器、状态标志。
- 示例:
AtomicInteger counter = new AtomicInteger(0); counter.incrementAndGet();
AtomicReference
:- 原子更新复杂对象。
- 示例:
AtomicReference<String> ref = new AtomicReference<>("old"); ref.compareAndSet("old", "new");
5. 其他工具
LockSupport
:- 提供低级线程控制(
park()
/unpark()
)。 - 使用场景:自定义锁实现。
- 示例:
LockSupport.park(); LockSupport.unpark(thread);
- 提供低级线程控制(
ThreadLocal
:- 为每个线程提供独立变量副本。
- 使用场景:线程本地存储。
- 示例:
ThreadLocal<Integer> local = ThreadLocal.withInitial(() -> 0); local.set(1);
三、总结与选择建议
synchronized
和ReentrantLock
:synchronized
适合简单同步场景,代码简洁,依赖JVM优化。ReentrantLock
提供高级功能(如中断、超时、公平锁),适合复杂场景。
- 其他并发工具:
- 线程池(
ExecutorService
、ForkJoinPool
)适合任务管理和并行计算。 - 并发集合(
ConcurrentHashMap
、BlockingQueue
等)优化高并发数据结构操作。 - 高级锁和同步器(
ReadWriteLock
、Semaphore
、CountDownLatch
等)提供灵活的同步控制。 - 原子类(
AtomicInteger
等)适合高效无锁操作。 ThreadLocal
和LockSupport
解决特定场景需求。
- 线程池(
开发者应根据场景需求选择工具:简单同步用synchronized
,复杂场景用ReentrantLock
或并发包工具,高并发数据操作用并发集合,任务管理用线程池。