可重入锁 ReentrantLock 及其他显式锁相关问题
1、跟 Synchronized 相比,可重入锁 ReentrantLock 其实现原理有什么不同
(1)底层实现
synchronized是JVM层面的锁,是java关键字。而ReentrantLock是API层面的互斥锁。
(2)是否可以手动释放
synchronized不需要手动释放,代码执行完之后系统自动让线程释放对锁的占用;
ReentrantLock则需要用户去手动释放锁,如果没有手动释放锁,可能会导致死锁。一般通过lock()和unlock()方法配合try/finally语句来实现。
public class testReentrantLock{
private Lock lock = new ReentrantLock();
public void increment() throws Exception {
lock.lock();
try {
...........
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
(3)是否可中断
synchronized是不可中断的类型,除非加锁的代码中出现异常或正常执行完成。
ReentrantLock是可以中断的,可以通过 tryLock(long timeout, TimeUnit unit)方法设置超时,或者调用lockInterruptibly()方法进行中断。
源码:
tryLock(long timeout, TimeUnit unit)
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
lockInterruptibly()
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
(4)是否公平锁
Synchronized是非公平锁。
ReentrantLock默认是非公平锁,但是也可以通过参数设置为公平锁。通过构造方法传入boolean类型参数,false默认是非公平锁,true为公平锁。
源码:
// 默认是非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
// 根据传入Boolean类型决定公平锁还是非公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
(5)锁是否可以绑定Condition
synchronized不能绑定
ReentrantLock可以绑定,需要结合await() 、signal()、signalAll()等方法进行操作。
await方法类似Object类的wait()方法,阻塞一个线程;signal()、signalAll()类似Object类的notify()/notifyAll()唤醒一个线程。
每一个Lock可以有任意数据的Condition对象,Condition是与Lock绑定的。Condition的强大之处在于它可以为多个线程间建立不同的Condition。
比如,使用Condition实现生产者-消费者模型:
其中一个是生产者,用于将消息放入缓冲区;消费者从缓冲区取数据。
现在有一个问题,当缓冲区满了的时候,而此时生产者还要往缓冲区放数据的时候,此时解决办法就是让生产者休眠,等消费者从缓冲区取出一个数据之后,再唤醒生产者。
同样的,当缓冲区已经空了,而消费者还要来取数据时,此时要让消费者进行休眠,等待生产者放入一个数据之后再唤醒消费者。
class BoundedBuffer {
final Lock lock = new ReentrantLock();//锁对象
final Condition notFull = lock.newCondition();//写线程条件
final Condition notEmpty = lock.newCondition();//读线程条件
final Object[] items = new Object[100];//缓存队列
int putptr/*写索引*/, takeptr/*读索引*/, count/*队列中存在的数据个数*/;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)//如果队列满了
notFull.await();//阻塞写线程
items[putptr] = x;//赋值
if (++putptr == items.length) putptr = 0;//如果写索引写到队列的最后一个位置了,那么置为0
++count;//个数++
notEmpty.signal();//唤醒读线程
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)//如果队列为空
notEmpty.await();//阻塞读线程
Object x = items[takeptr];//取值
if (++takeptr == items.length) takeptr = 0;//如果读索引读到队列的最后一个位置了,那么置为0
--count;//个数--
notFull.signal();//唤醒写线程
return x;
} finally {
lock.unlock();
}
}
}
(6)锁的对象
synchronized锁的是对象,锁是保存在对象头里面的,根据对象头标识是否有线程获得锁/争抢锁。
ReentrantLock锁的是线程,根据进入的线程和int类型的state标识锁的获得/争抢。
2、ReentrantLock 是如何实现可重入性的?
ReentrantLock 内部自定义了 同步器Sync,其实就是加锁的时候通过CAS算法,将线程对象放到一个双向链表中,每次获取锁的时候,看下当前维护的那个线程ID和当前请求的线程ID是否一样,就可以实现重入了。