在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(); } }
- 示例:
追问:锁升级流程
锁升级是单向的,从低开销到高开销:无锁 → 偏向锁 → 轻量级锁 → 重量级锁。具体流程如下:
- 无锁状态:对象刚创建时,无任何线程访问。
- 偏向锁:
- 第一个线程访问时,通过CAS将线程ID写入Mark Word,进入偏向锁状态。
- 同一线程再次访问时,直接检查线程ID,效率极高。
- 轻量级锁:
- 如果有其他线程竞争,偏向锁撤销,升级为轻量级锁。
- 线程通过CAS尝试获取锁,失败后自旋。
- 重量级锁:
- 自旋失败或竞争激烈时,锁膨胀为重量级锁,依赖操作系统互斥锁,需要从用户态切换到内核态
- 未获取锁的线程被阻塞,进入Monitor的等待队列。
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或并发包工具,高并发数据操作用并发集合,任务管理用线程池。

被折叠的 条评论
为什么被折叠?



