synchronized和ReentrantLock有什么区别呢? - 《java核心技术》笔记
ReentrantLock能够实现很多synchronized无法做到的细节控制,比如公平性fairness,或者利用定义条件等。
知识点扩展:
线程安全
线程安全的定义
保证多线程环境下共享的、可修改的状态的正确性。
进而推导出保证线程安全的两个方法:
- 封装:将对象内部状态隐藏、保护起来。
- 不可变。
线程安全需要的几个基本特性
- 原子性:相关操作不会中途被其他线程干扰,一般通过同步机制实现;
- 可见性:一个线程修改了某个共享变量,其状态能理解被其他线程知晓。
- 有序性:保证线程内串行语义,避免指令重排。
synchronized和ReentrantLock的使用
synchronized如果用来修饰静态方法,相当于synchronized (ClassName.class) {}
ReentrantLock如果当一个线程试图获取一个它已经获取的锁时,会自动成功。
ReentrantLock可以指定公平性ReentrantLock fairLock = new ReentrantLock(true);
,为真时,会倾向于将锁赋予等待时间最久的线程。
ReentrantLock相比synchronized,因为可以像普通对象一样使用,所以可以利用其提供的各种便利方法,进行精细的同步操作:
- 带超时的获取锁尝试;
- 判断是否有线程或某个特定线程在排队等待获取锁;
- 可以响应中断请求;
- ……
条件变量
如果说 ReentrantLock 是 synchronized的替代,则Condition则是将wait、notify、notifyAll转化为相应的对象,将复杂而晦涩的同步操作转变为直观可控的对象行为。
典型场景
ArrayBlockingQueue
- 通过再入锁获取条件变量:
两个条件变量从同一个再入锁创建出来。
/** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts */
private final Condition notFull;
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
- 队列为空时,等待入队发生。
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
- 入队后通知notEmpty,触发后续take操作。
private void enqueue(E e) {
// assert lock.isHeldByCurrentThread();
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
items[putIndex] = e;
if (++putIndex == items.length) putIndex = 0;
count++;
notEmpty.signal(); // 通知等待的线程,非空条件已经满足
}