JUC之JDK自带锁ReentrantLock

一、初识

ReentrantLock
出身自Java 1.5,中文名可重入锁
是Java JDK自带独占锁的唯一实现,也是最常用的锁,是synchronized的升级版。

1. 我们中间有个synchronized

我们已经认识过synchronized了,知道她能帮我们实现线程同步提供原子性语义,同时又有可重入性。同时我们也已经知道了可重入性是什么意思,也知道公平性的含义。

当然,我们很JRE如何实现synchronized,实现它的可重入性。
但我们可以通过阅读ReentrantLock的源码,来加深对sychronized一些特性的理解。

2. 升级版,升了什么

前面说了ReentrantLock是sychronized的升级版,那么ReentrantLock升级了什么,为我们带哪些新特性呢?

  1. tryLock 尝试获取锁,直接穿透(无视)公平性
  2. isLocked 当前锁是不是被持有(含自己和他人),用来监控系统(锁)状态
  3. hasQueuedThreas(has) 提供更多监控,这一系列含has方法都是实现状态监控
  4. Fair And Non-Fair Model ReentrankLock提供公平与非公平两个模式
  5. Condition 在Lock内用Condition代替Synchronized的Object监控

这些都是非常好用,非常实用的功能,而synchronized却没有的特征。
还有还有,ReentrankLock还提供了一个可中断的方法。

3. Condition

当把ReentrantLock当成synchronized时,你需要把Condition当成Object监控。功能和用法都一样,只是名字不同而已。

项目 等待 唤醒 唤醒所有 用法
Object wait notify notifyAll 在synchronized语块内
Condition await signal signalAll 在Lock语块内

二、你好!ReentrankLock

我们已经认识过公平性了,我们知道她的语义是是否先到先得。那么她怎么实现的呢,我们好像还没看过。接下来我们将通过阅读ReentrantLock的源码,来看看她是怎么实现公平性的。先贴代码吧

1. 公平

// ReentranLock$FiarSync
final void lock() {
    acquire(1);
}
public final void acquire(int arg) {
 if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
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;
}

你应该从一小段代码里看出三个东西,
1. 公平
2. 可重入
3. 独占式

JDK在实现锁的时候通常用’0’和’大于0`表示锁状态,即没锁上和已上锁。

从这一小段源码里面可以看到
- ReentrantLock先自己尝试去获取锁,即是检查state的状态码
1. 对于state为0(即锁没被持有的时候),且前面没人在排队,理论上对这种情况特别简单直接上锁就可以了,如锁操作是线程安全的。然后并没有,我们之前在整理AQS框架的时候已经了解了AQS是通过CAS实现同步,即通过一组原子性的操作完成的。

  1. 当state不为0时,她就会去判断是谁持有,如果是自己的话,依然可以再次获得,这就是可重入性。同时state自增,此时可以反映两个问题,state还代表递归层级,最多能支持Integer.MAX_VALUE层。

    • ReentrantLock在尝试失败之后,将进入等待队列继续等待,就是阻塞

2. 不公平

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

public final void acquire(int arg) {
 if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) { // #1
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

乍一看,好像没啥差。
只不过一来就尝试通过CAS修改条件来获取独占锁。其它,好像没啥了。对了,还改了个不要脸的名字叫nonfairTryAcquire

细心的你应该还能看到#1语句里少了条件!hasQueuedPredecessors(),即是不再管前面有没有人在排队,就伸手要去取锁。正因为这两个“不道德”的插队操作完成了非公开性,从而获到更高的OPS。

ReentrankLock默认实现就是非公平的,因为它能有更高的吞吐量。

三、再见AQS框架

在整理AQS框架的时候,我们说AQS框架提供一个基于FIFO等待队列,可以用于构建锁或者其他同步装置的基础框架。意在能够成为实现大部分同步需求的基础,她用法可以参考ReentrantLock的实现。

这里再多说几句,ReentrantLock是实现Lock接口、同时也实现Serializable。Serializable是说它的状态可以被系列,而Lock提供Lock的相关语义和操作。到这里为止,好像跟AQS没什么关系。对对对您说得对,没错没错是这样。不过我们说Lock是通过AQS实现同步的。因此,ReentrantLock实际上是通过一个承继AQS的内部类——同步器(Sync),实现同步,从而完全Lock功能的。

再看看ReentrantLock提供两种Sync的实现完全公平性语义,即FairSyncNonFairSync两个类。而Lock的功能完全Forwarding到Sync,由Sync具体实现。所以说AQS可以用来实现同步锁。

四、CU! ReentrantLock

  • ReentrantLock是一个独占锁,可重入。
  • ReentrantLock可实现公平和非公平,通过她的构造器的参数设定
  • ReentrantLock是synchronized的升级版,可代替synchonized,更好用、更高性能

你可能感兴趣的:(JUC)