在 Java 并发编程中,synchronized
关键字和 ReentrantLock
都是用于实现线程同步的重要机制,但它们在实现方式、功能和性能特征上有所不同。
主要区别
特性 | synchronized | ReentrantLock |
---|---|---|
实现方式 | Java 语言关键字,JVM 级别实现 | JDK 提供的类,API 级别实现 |
锁获取方式 | 隐式获取和释放锁 | 显式获取和释放锁 |
灵活性 | 相对简单,功能有限 | 更灵活,提供更多功能 |
可中断性 | 不可中断等待 | 支持可中断的锁获取 |
公平性 | 非公平锁 | 可选择公平或非公平锁 |
条件变量 | 单一等待条件 | 支持多个条件变量 |
性能 | Java 6+ 优化后性能相当 | 在高竞争环境下可能略有优势 |
应用场景
synchronized 适用场景:
-
简单的同步需求
-
代码块或方法级别的同步
-
不需要高级功能的场景
-
希望代码更简洁易读的情况
ReentrantLock 适用场景:
-
需要可定时、可中断的锁获取
-
需要公平锁机制
-
需要多个条件变量
-
需要尝试获取锁(tryLock)
-
需要更细粒度的锁控制
注意事项
synchronized 注意事项:
-
自动释放锁,不会忘记释放
-
同步范围应尽量小,减少性能影响
-
避免嵌套同步导致的死锁
-
同步方法不能被继承重写
ReentrantLock 注意事项:
-
必须显式地在 finally 块中释放锁
-
不要将锁对象暴露给外部代码
-
注意锁的公平性选择对性能的影响
-
合理使用条件变量
下面通过更详细的代码示例来说明两者的区别、应用场景和注意事项。
基本用法对比
synchronized 基本用法
public class SynchronizedExample {
private int count = 0;
private final Object lock = new Object();
// 同步方法
public synchronized void increment() {
count++;
}
// 同步代码块
public void incrementWithBlock() {
synchronized (lock) {
count++;
}
}
// 静态同步方法
public static synchronized void staticIncrement() {
// 类级别的同步
}
}
ReentrantLock 基本用法
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private int count = 0;
private final ReentrantLock lock = new ReentrantLock();
public void increment() {
lock.lock(); // 获取锁
try {
count++;
} finally {
lock.unlock(); // 必须在finally块中释放锁
}
}
}
高级特性对比示例
可中断锁获取
import java.util.concurrent.locks.ReentrantLock;
public class InterruptibleLockExample {
private final ReentrantLock lock = new ReentrantLock();
public void performTask() {
try {
// 可中断的锁获取
lock.lockInterruptibly();
try {
// 执行需要同步的操作
System.out.println("Task started by " + Thread.currentThread().getName());
Thread.sleep(5000); // 模拟长时间操作
System.out.println("Task completed by " + Thread.currentThread().getName());
} finally {
lock.unlock();
}
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " was interrupted while waiting for lock");
}
}
public static void main(String[] args) throws InterruptedException {
InterruptibleLockExample example = new InterruptibleLockExample();
Thread t1 = new Thread(example::performTask, "Thread-1");
Thread t2 = new Thread(example::performTask, "Thread-2");
t1.start();
Thread.sleep(100); // 确保t1先获取锁
t2.start();
Thread.sleep(1000); // 让t2等待一会儿
t2.interrupt(); // 中断t2的锁等待
}
}
尝试获取锁与超时
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class TryLockExample {
private final ReentrantLock lock = new ReentrantLock();
public void tryLockMethod() {
// 尝试获取锁,立即返回结果
if (lock.tryLock()) {
try {
System.out.println(Thread.currentThread().getName() + " acquired the lock");
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
} else {
System.out.println(Thread.currentThread().getName() + " could not acquire the lock");
}
}
public void tryLockWithTimeout() {
try {
// 尝试获取锁,最多等待2秒
if (lock.tryLock(2, TimeUnit.SECONDS)) {
try {
System.out.println(Thread.currentThread().getName() + " acquired the lock with timeout");
Thread.sleep(3000); // 持有锁3秒
} finally {
lock.unlock();
}
} else {
System.out.println(Thread.currentThread().getName() + " timed out waiting for lock");
}
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " was interrupted while waiting for lock");
}
}
public static void main(String[] args) {
TryLockExample example = new TryLockExample();
// 测试tryLock()
new Thread(example::tryLockMethod, "Thread-1").start();
new Thread(example::tryLockMethod, "Thread-2").start();
try { Thread.sleep(3000); } catch (InterruptedException e) {}
// 测试带超时的tryLock()
new Thread(example::tryLockWithTimeout, "Thread-3").start();
new Thread(example::tryLockWithTimeout, "Thread-4").start();
}
}
公平锁 vs 非公平锁
import java.util.concurrent.locks.ReentrantLock;
public class FairLockExample {
private final ReentrantLock fairLock = new ReentrantLock(true); // 公平锁
private final ReentrantLock unfairLock = new ReentrantLock(false); // 非公平锁
public void testFairLock() {
for (int i = 0; i < 5; i++) {
fairLock.lock();
try {
System.out.println(Thread.currentThread().getName() + " acquired fair lock");
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
fairLock.unlock();
}
}
}
public void testUnfairLock() {
for (int i = 0; i < 5; i++) {
unfairLock.lock();
try {
System.out.println(Thread.currentThread().getName() + " acquired unfair lock");
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
unfairLock.unlock();
}
}
}
public static void main(String[] args) {
FairLockExample example = new FairLockExample();
System.out.println("Testing fair lock:");
for (int i = 0; i < 3; i++) {
new Thread(example::testFairLock, "Fair-Thread-" + i).start();
}
try { Thread.sleep(3000); } catch (InterruptedException e) {}
System.out.println("\nTesting unfair lock:");
for (int i = 0; i < 3; i++) {
new Thread(example::testUnfairLock, "Unfair-Thread-" + i).start();
}
}
}
条件变量 (Condition)
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionExample {
private final ReentrantLock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
private final Object[] items = new Object[5];
private int putPtr, takePtr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length) {
System.out.println("Buffer is full, waiting...");
notFull.await(); // 等待"不满"条件
}
items[putPtr] = x;
if (++putPtr == items.length) putPtr = 0;
++count;
System.out.println("Produced: " + x);
notEmpty.signal(); // 通知"不空"条件
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0) {
System.out.println("Buffer is empty, waiting...");
notEmpty.await(); // 等待"不空"条件
}
Object x = items[takePtr];
if (++takePtr == items.length) takePtr = 0;
--count;
System.out.println("Consumed: " + x);
notFull.signal(); // 通知"不满"条件
return x;
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
ConditionExample example = new ConditionExample();
// 生产者线程
Thread producer = new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
example.put(i);
Thread.sleep(200);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
// 消费者线程
Thread consumer = new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
example.take();
Thread.sleep(500);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
producer.start();
consumer.start();
}
}
性能比较示例
import java.util.concurrent.locks.ReentrantLock;
public class PerformanceComparison {
private int counter = 0;
private final Object syncLock = new Object();
private final ReentrantLock reentrantLock = new ReentrantLock();
public void incrementWithSynchronized() {
synchronized (syncLock) {
counter++;
}
}
public void incrementWithReentrantLock() {
reentrantLock.lock();
try {
counter++;
} finally {
reentrantLock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
PerformanceComparison example = new PerformanceComparison();
int numThreads = 10;
int iterations = 1000000;
// 测试synchronized性能
long startTime = System.currentTimeMillis();
Thread[] syncThreads = new Thread[numThreads];
for (int i = 0; i < numThreads; i++) {
syncThreads[i] = new Thread(() -> {
for (int j = 0; j < iterations; j++) {
example.incrementWithSynchronized();
}
});
syncThreads[i].start();
}
for (Thread t : syncThreads) {
t.join();
}
long syncTime = System.currentTimeMillis() - startTime;
// 重置计数器
example.counter = 0;
// 测试ReentrantLock性能
startTime = System.currentTimeMillis();
Thread[] lockThreads = new Thread[numThreads];
for (int i = 0; i < numThreads; i++) {
lockThreads[i] = new Thread(() -> {
for (int j = 0; j < iterations; j++) {
example.incrementWithReentrantLock();
}
});
lockThreads[i].start();
}
for (Thread t : lockThreads) {
t.join();
}
long lockTime = System.currentTimeMillis() - startTime;
System.out.println("Synchronized time: " + syncTime + "ms");
System.out.println("ReentrantLock time: " + lockTime + "ms");
System.out.println("Final counter value: " + example.counter);
}
}
注意事项与最佳实践
synchronized 注意事项
public class SynchronizedPitfalls {
private final Object lock1 = new Object();
private final Object lock2 = new Object();
// 潜在的死锁问题
public void method1() {
synchronized (lock1) {
// 一些操作...
method2(); // 可能造成死锁
}
}
public void method2() {
synchronized (lock2) {
// 一些操作...
method1(); // 可能造成死锁
}
}
// 更好的设计 - 避免嵌套锁
public void betterMethod1() {
synchronized (lock1) {
// 只操作与lock1相关的资源
}
// 在锁外调用其他方法
betterMethod2();
}
public void betterMethod2() {
synchronized (lock2) {
// 只操作与lock2相关的资源
}
}
}
ReentrantLock 注意事项
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockPitfalls {
private final ReentrantLock lock = new ReentrantLock();
// 错误示例 - 可能忘记释放锁
public void riskyMethod() {
lock.lock();
// 如果这里抛出异常,锁永远不会被释放
// 应该使用try-finally结构
// lock.unlock(); // 可能永远不会执行
}
// 正确示例 - 使用try-finally确保锁释放
public void safeMethod() {
lock.lock();
try {
// 受保护的代码
} finally {
lock.unlock();
}
}
// 避免在持有锁时调用外部方法
public void problematicMethod() {
lock.lock();
try {
externalMethod(); // 危险!外部方法可能执行很长时间或尝试获取其他锁
} finally {
lock.unlock();
}
}
private void externalMethod() {
// 可能执行很长时间或尝试获取其他锁
}
// 更好的设计 - 最小化锁内代码
public void betterMethod() {
// 在锁外执行尽可能多的操作
Object data = prepareData();
lock.lock();
try {
// 只执行必须同步的最小操作
updateSharedState(data);
} finally {
lock.unlock();
}
}
private Object prepareData() {
return new Object();
}
private void updateSharedState(Object data) {
// 更新共享状态
}
}
在实际开发中,大多数情况下 synchronized
已经足够且更安全,因为它自动管理锁的释放。但当需要更高级的同步功能时,ReentrantLock
提供了更大的灵活性和控制能力。