Synchronized和ReentrantLock在Java中都是用来控制并发访问共享资源的机制,但它们在获取释放锁的方式、锁的特性以及锁的实现级别等方面存在一些差异。
锁的特性
synchronized是Java的关键字,可以隐式地获取和释放锁。synchronized关键字通过内置的锁机制来控制对共享资源的并发访问,确保每次只有一个线程能够执行被synchronized保护的代码区域。在Java对象头中有关于锁的信息,当一个线程试图获取一个对象的锁时,JVM会进行相应的内存布局调整和优化,以确保线程安全。
ReentrantLock支持公平锁,即等待时间最长的线程会优先获得锁,而synchronized则默认情况下是不公平的。当线程进入synchronized修饰的代码块时,JVM会自动给对象加锁,离开时自动解锁。而ReentrantLock需要开发者手动调用lock()和unlock()方法来获取和释放锁。
synchronized典型代码:
synchronized (ClassName.class) {}
而可重入锁为保证锁释放,每一个 lock() 动作,我建议都立即对应一个 try-catch-finally,典型的代码结构如下:
ReentrantLock fairLock = new ReentrantLock(true);
fairLock.lock();
try {// do something
} finally {fairLock.unlock();
}
ReentrantLock 是一个功能强大且灵活的同步工具,适用于那些需要高度细粒度控制的同步控制的场景。ReentrantLock提供一些api来更好的进行控制:
尝试锁定
你可以使用 tryLock() 方法尝试立即获取锁,如果锁不可用,它将返回 false:
if (lock.tryLock()) {try {// ... 执行线程安全的操作 ...} finally {lock.unlock();}
} else {// 未能获取锁,采取其他措施...
}
还可以指定最长等待时间:
try {if (lock.tryLock(5, TimeUnit.SECONDS)) {try {// ... 执行线程安全的操作 ...} finally {lock.unlock();}} else {// 在指定时间内未获得锁}
} catch (InterruptedException e) {// 当前线程在等待过程中被中断
}
条件变量
使用 newCondition() 方法可以创建与 ReentrantLock 关联的 Condition 实例,条件变量允许线程在不满足特定条件时等待,并在其他线程改变状态后被唤醒:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class ConditionDemo {private Lock lock = new ReentrantLock();private Condition condition = lock.newCondition();public void method1() {try {lock.lock();while (true) {// 等待条件满足condition.await();// 处理业务逻辑System.out.println("method1 is running");}} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}public void method2() {try {lock.lock();// 改变条件状态condition.signalAll();} finally {lock.unlock();}}
}
在这个示例中,我们创建了一个 Lock 和一个 Condition 对象。method1() 方法中使用了 while 循环来不断检查条件是否满足,如果条件不满足,就调用 condition.await() 方法让当前线程等待。当条件满足时,线程会被唤醒并继续执行。method2() 方法中改变了条件状态,并调用 condition.signalAll() 方法通知所有正在等待的线程。
抉择
选择synchronized还是ReentrantLock通常取决于以下因素:
-
简单性 vs. 灵活性:如果你需要简单的线程安全,并且不介意使用JVM内置的锁机制,那么synchronized可能是一个好选择。如果你需要超时、可中断的锁或者公平性保证,那么ReentrantLock是更好的选择。
-
性能考虑:在高竞争的环境下,ReentrantLock通常比synchronized表现更好,尤其是它的公平锁模式可以减少线程唤醒的开销。
-
功能需求:如果需要条件变量、尝试非阻塞地获取锁或绑定多个条件,ReentrantLock提供了这些功能,而synchronized则没有。
-
错误检查:使用ReentrantLock时,如果忘记释放锁,可能会导致死锁。synchronized则由JVM管理,不容易出现这种问题。