ReentrantLock和synchronized的区别
文章目录
- 基本使用方式的区别
- 锁的获取和释放机制
- synchronized的自动管理
- ReentrantLock的手动管理
- 等待可中断的区别
- 尝试获取锁的能力
- 公平锁和非公平锁
- 条件变量的支持
基本使用方式的区别
synchronized是Java内置的关键字,使用起来比较简单:
public class SynchronizedExample {private final Object lock = new Object();public void method1() {synchronized (lock) {// 同步代码块}}public synchronized void method2() {// 同步方法}
}
而ReentrantLock是一个类,需要手动获取和释放锁:
public class ReentrantLockExample {private final ReentrantLock lock = new ReentrantLock();public void method() {lock.lock();try {// 同步代码块} finally {lock.unlock();}}
}
从使用方式上看,synchronized更简洁,而ReentrantLock需要显式地获取和释放锁,但这也给了我们更多的控制权。
锁的获取和释放机制
synchronized的自动管理
synchronized的一个重要优势是锁的获取和释放是自动的。当线程进入synchronized代码块时自动获取锁,当离开时(无论是正常结束还是抛出异常)都会自动释放锁。这样就不会出现忘记释放锁的问题。
ReentrantLock的手动管理
ReentrantLock需要手动调用unlock()方法来释放锁:
public void flexibleLockUsage() {lock.lock();try {// 可以在这里调用其他方法someMethod();// 也可以在某些条件下提前返回if (someCondition) {return; // finally块会确保锁被释放}// 更多业务逻辑} finally {lock.unlock(); // 确保锁一定会被释放}
}
等待可中断的区别
这是两者之间一个很重要的区别。synchronized不支持中断,如果一个线程在等待synchronized锁时被阻塞了,就只能一直等下去,无法响应中断。
// synchronized无法中断等待
public synchronized void uninterruptibleMethod() {// 如果线程在等待进入这个方法时被阻塞// 调用Thread.interrupt()无法中断等待
}
而ReentrantLock提供了可中断的锁获取方式:
public void interruptibleMethod() {try {lock.lockInterruptibly(); // 可以被中断的锁获取try {// 业务逻辑} finally {lock.unlock();}} catch (InterruptedException e) {// 处理中断异常Thread.currentThread().interrupt();}
}
尝试获取锁的能力
synchronized要么获取到锁,要么一直等待,没有其他选择。但ReentrantLock提供了tryLock方法,可以尝试获取锁而不阻塞:
public boolean tryDoSomething() {if (lock.tryLock()) {try {// 获取到锁,执行业务逻辑return true;} finally {lock.unlock();}} else {// 没有获取到锁,可以做其他处理return false;}
}public boolean tryDoSomethingWithTimeout() {try {if (lock.tryLock(5, TimeUnit.SECONDS)) {try {// 在5秒内获取到锁return true;} finally {lock.unlock();}} else {// 5秒内没有获取到锁return false;}} catch (InterruptedException e) {Thread.currentThread().interrupt();return false;}
}
这种能力让我们可以实现更灵活的同步策略,比如避免死锁。
公平锁和非公平锁
synchronized只支持非公平锁,也就是说不能保证等待时间最长的线程优先获得锁。而ReentrantLock可以选择是公平锁还是非公平锁:
// 非公平锁(默认)
ReentrantLock unfairLock = new ReentrantLock();// 公平锁
ReentrantLock fairLock = new ReentrantLock(true);
公平锁保证了等待时间最长的线程优先获得锁,但性能会比非公平锁差一些,因为需要维护一个有序的等待队列。
条件变量的支持
这可能是ReentrantLock相对于synchronized最大的优势之一。synchronized只能配合wait()和notify()方法使用,而且只能有一个等待条件。
// synchronized + wait/notify
public synchronized void oldWay() throws InterruptedException {while (!condition) {wait(); // 只能等待一个条件}// 执行业务逻辑
}public synchronized void notifyOthers() {condition = true;notifyAll(); // 唤醒所有等待的线程,无法精确控制
}
而ReentrantLock可以创建多个Condition,实现更精确的线程通信:
public class MultiConditionExample {private final ReentrantLock lock = new ReentrantLock();private final Condition notFull = lock.newCondition();private final Condition notEmpty = lock.newCondition();private final Object[] items = new Object[10];private int putIndex, takeIndex, count;public void put(Object item) throws InterruptedException {lock.lock();try {while (count == items.length) {notFull.await(); // 等待不满的条件}items[putIndex] = item;putIndex = (putIndex + 1) % items.length;count++;notEmpty.signal(); // 通知不空的条件} finally {lock.unlock();}}public Object take() throws InterruptedException {lock.lock();try {while (count == 0) {notEmpty.await(); // 等待不空的条件}Object item = items[takeIndex];items[takeIndex] = null;takeIndex = (takeIndex + 1) % items.length;count--;notFull.signal(); // 通知不满的条件return item;} finally {lock.unlock();}}
}
这种方式可以让生产者只唤醒消费者,消费者只唤醒生产者,避免了不必要的线程唤醒,提高了效率。