您的位置:首页 > 编程语言 > Java开发

Java并发——重入锁ReentrantLock的实现原理及源码解析

2018-03-16 14:21 1096 查看
1、什么是可重入锁?

可重入锁就是对于已经获得锁的线程,可以重复的多次的获得该锁。
而不可重入的锁在线程获得该锁后,该线程如果再次请求获得该锁,就会在调用tryAquires()的时候返回false,从而阻塞自己。
2、可重入锁的实现原理?
要实现可重入锁的关键有两个,一个怎么识别当前请求锁的线程是不是已经获取锁的线程,另一个因为在一个线程重复的获取了n次锁以后,必须要释放n次锁才能完全释放锁,这怎么实现?
对于第一个问题,因为可重入锁是独占锁,所以只要比较当前线程是不是独占锁的线程,如果是则可以再次加锁,如果不是则返回false。
对于第二个问题,需要一个与锁关联的计数器,来对加锁的重数进行计数,每次获得锁都应该让计数器自增,而每次释放锁都应该让计数器自减,当计数器为0时,表示锁已经成功释放。
源码分析:

那么我们来看看源码:public class ReentrantLock implements Lock, java.io.Serializable ▶从继承关系上,可见ReentrantLock是实现了Lock接口,因此Lock接口的功能ReentrantLock也有Lock接口的可中断等待锁的线程,可以实现Condition等功能。 private final Sync sync;
 abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
abstract void lock();
        //重写nonFairAquire
         final boolean nonfairTryAcquire(int acquires) {
//省略此处,在公平锁和非公平锁的实现中会讲到
}
        //重写tryRelease
protected final boolean tryRelease(int releases) {
//省略此处
}从实现上可以看出,ReentrantLock是通过组合自定义同步器来实现锁的获取与释放,在ReentrantLock自定义了内部类sync,内部类sync继承了AbstractQueuedSynchronizor,并重写了nonfairTryAquire()和tryRelease()方法来获得锁和释放锁。
▶然后再看ReentrantLock的构造方法:
public ReentrantLock() {
sync = new NonfairSync();
}

public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}可以看出,ReentrantLock默认实现非公平锁,如果传入表示公平的boolean变量fair,那么fair为true的时候,实现公平锁,fair为false的时候,实现非公平锁。而在ReentrantLock中实现公平锁和非公平锁都是通过实现一个静态内部类来实现了,该静态内部类继承了sync静态内部类。源码如下所示:
公平锁静态内部类 protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}因为在sync内部类中没有重写公平锁的获取锁的方法,因此在公平锁的内部类中重写tryAquire()方法,实现公平锁的获取。
通过tryAquire()方法我们可以看出ReentrantLock的实现流程:首先获取锁的同步状态值,这个值是被volatile修饰的,这样可以保证这个值在多线程之间是可见的,如果锁没有被占用(c==0)那么让队列头的线程来获取锁,如果锁已经被占用,因为ReentrantLock是排他锁,因此可以把当前线程和独占的线程比较,如果当先线程就是独占锁的线程,那么可以获取锁,如果不是,则获取锁失败。

非公平锁静态内部类static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;

final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}

protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}因为在sync内部类中已经重写了nonFairAquire()方法,因此直接调用nonFairAquire()方法即可。
此外因为在sync内部类中已经重写了tryRelease()方法,因此在这两个子类中都没有重写这个方法,需要的时候直接调用即可。

3、细节:
①synchronized关键字也是一种可重入锁,比如synchronized修饰的递归方法,在方法执行时,执行的线程在获得了锁以后仍然能够多次的获得锁,而不会因为锁之前已经被占有被阻塞线程本身。
重入锁与synchronized的区别:
○性能上:
    ▶重入锁是显式的重入,而synchronized是隐式的重入。也就是说重入锁是用户自己添加和释放,由JDK实现,而 synchronized由操作系统来添加和释放锁,由JVM实现。因此便利性是synchronized优于重入锁。
     ▶锁的细粒度和灵活度:重入锁优于synchronized
     ▶synchronized在优化前,性能远不如重入锁,但是在synchronized引入偏向锁后、轻量级锁(自旋锁)后,二者的性能区别就不大了,在两者都可用的情况下,官方甚至建议使用synchronized。
○功能上:

    ▶因为重入锁实现了lock接口,因此提供了Condition类,可以实现分组唤醒需要唤醒的线程,而不是像synchronized一样要么随机唤醒,要么全部唤醒。
    ▶重入锁可以实现公平锁,但是默认的是实现非公平锁;而synchronized只能实现非公平锁;(关于公平锁与非公平锁,参见:点击打开链接

    ▶重入锁提供了一种能够终端等待锁的线程,通过lock.lockInterruptibly( )实现。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息