深入理解 ReentrantLock

ReentrantLock
是一种可重入,它指的是一个线程能够对资源重复加锁。 ReentrantLock
synchronized
类似,能够保证解决线程安全问题,但是却提供了比 synchronized
更强大、灵活的机制,例如可中断式的获取锁、可定时的获取锁等。

另外, ReentrantLock
也提供了公平锁与非公平锁的选择,它们之间的区别主要就是看对锁的获取与获取锁的请求的顺序是否是一致的,选择公平锁时,等待时间最长的线程会最优先获取到锁,但是公平锁获取的效率通常比非公平锁要低。可以在构造方法中通过传参的方式来具体指定选择公平或非公平。

公平锁

ReentrantLock
中,有一个抽象内部类 Sync
,它继承自 AQS
ReentrantLock
的大部分功能都委托给 Sync
进行实现,其内部定义了 lock()
抽象方法,默认实现了 nonfairTryAcquire()
方法,它是非公平锁的默认实现。

Sync
有两个子类:公平锁 FairSync
NonFairSync
,实现了 Sync
中的 lock()
方法和 AQS
中的 tryAcquire()
方法。

NonFairSync

NonFairSync
lock()
方法的实现如下:

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

首先,非公平锁可以立即尝试获取锁,如果失败的话,会调用 AQS
中的 acquire
方法,其中 acquire
方法又会调用由自定义组件实现的 tryAcquire
方法:

protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}
复制代码

nonfairTryAcquire()
方法在 Sync
中已经默认实现:

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        // 使用 CAS 设置同步状态
        if (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;
}
复制代码

这里,首先会判断的当前线程的状态是否为 0
,也就是该锁是否处于空闲状态,如果是的话则尝试获取锁,设置成功将当前线程设置为持有锁的线程。

否则的话,就判断当前线程是否为持有锁的线程,如果是的话,则增加同步状态值,获取到锁,这里也就验证了锁的可重入,再获取了锁之后,可以继续获取锁,只需增加同步状态值即可。

FairSync

FairSync
lock()
方法的实现如下:

final void lock() {
    acquire(1);
}
复制代码

公平锁只能调用 AQS
acquire()
方法,再去调用由自定义组件实现的 tryAcquire()
方法:

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;
}
复制代码

这里唯一与非公平锁不同的是在获取同步状态时,会调用 hasQueuedPredecessors
方法,这个方法用来判断同步队列中是否有前驱节点。也就是当前线程前面再没有其他线程时,它才可以尝试获取锁。

释放锁

ReentrantLock
unlock
方法内部调用 AQS
release
方法释放锁,而其中又调用了自定义组件实现的 tryRelease
方法:

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    // 当前线程不是持有锁的线程,不能释放
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}
复制代码

首先,判断当前线程是否是持有锁的线程,如果不是会抛出异常。如果是的话,再减去同步状态值,判断同步状态是否为 0
,即锁被完全释放,其他线程可以获取同步状态了。

如果没有完全释放,则仅使用 setState
方法设置同步状态值。

指定公平性

ReentrantLock
的构造函数中可以指定公平性:

  • 默认创建一个非公平的锁
public ReentrantLock() {
    sync = new NonfairSync();
}
复制代码
  • 创建一个指定公平性的锁。
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}
复制代码

synchronized 和 ReentrantLock 区别

这里总结一下 synchronized 和 ReentrantLock 的异同,它们之间的相同点如下:

  • 都可以用于实现线程间的同步访问;
  • 两者都是可重入锁,即一个线程能够对资源重复加锁;

其不同点如下:

  • 同步实现机制不同:
    • synchronized
      通过 Java
      对象关联的 Monitor
      监视器实现(不考虑偏向锁、轻量级锁);
    • ReentrantLock
      通过 CAS
      AQS
      LockSupport
      等共同实现;
  • 可见性实现机制不同:
    • synchronized
      依赖 JVM
      内存模型保证包含共享变量的多线程内存可见性。
    • ReentrantLock
      通过 ASQ
      volatile
      类型的 state
      同步状态值保证包含共享变量的多线程内存可见性。
  • 使用方式不同:
    • synchronized
      可以用于修饰实例方法(锁住实例对象)、静态方法(锁住类对象)、同步代码块(指定的锁对象)。
    • ReentrantLock
      需要显式地调用 lock
      方法,并在 finally
      块中释放。
  • 功能丰富程度不同:
    • synchronized
      只提供最简单的加锁。
    • ReentrantLock
      提供定时获取锁、可中断获取锁、 Condition
      (提供 await
      signal
      等方法)等特性。
  • 锁类型不同:
    synchronized
    ReentrantLock
    

synchronized
优化以前,它比较重量级,其性能比 ReentrantLock
要差很多,但是自从 synchronized
引入了偏向锁、轻量级锁(自旋锁)、锁消除、锁粗化等技术后,两者的性能就相差不多了。

一般来说,仅当需要使用 ReentrantLock
提供的其他特性时,例如:可中断的、可定时的、可轮询的、公平地获取锁等,才考虑使用 ReentrantLock
。否则应该使用 synchronized
,简单方便。

原文 

https://juejin.im/post/5cb33a046fb9a0689173a19a

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » 深入理解 ReentrantLock

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址