Java 中的锁之 ReentrantLock

目录

1. ReentrantLock 初始化

2. ReentrantLock 内部结构

3. ReentrantLock 常用方法

4.  非公平锁 NonfairSync

4.1 lock() 方法原理

4.2. TryAcquire() 方法原理

5.  公平锁 FairSync

5.1 lock()、TryAcquire() 方法原理

5.2 hasQueuedPredecessors() 方法原理

6. tryLock 方法

6.1 tryLock()

6.2 tryLock(long timeout, TimeUnit unit)

7. ReentrantLock 的使用

8. 总结


ReentrantLock 是可重入独享锁。根据参数可决定其内部是一个公平锁还是非公平锁

1. ReentrantLock 初始化

// 非公平锁
ReentrantLock lock = new ReentrantLock();
// 公平锁
ReentrantLock lock = new ReentrantLock(true);

ReentrantLock 默认为非公平锁,可传入参数 true 来获取公平锁。其子类 FairSync 和 NonfairSync 分别实现了锁的公平和非公平策略。ReentrantLock  初始化源码如下:

public ReentrantLock() {
    sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

2. ReentrantLock 内部结构

  • ReentrantLock 实现了 Lock 接口
    • 内部子类 Sync 继承 AbstractQueuedSynchronizer 抽象类
    • 内部子类 FairSync 和 NonfairSync 继承 Sync 类

3. ReentrantLock 常用方法

  • lock () 上锁方法
    • 执行逻辑:lock () -> acquire () -> tryAcquire ()
  • unlock () 解锁方法
  • tryLock(),tryLock(long timeout, TimeUnit unit) 尝试获取锁

4.  非公平锁 NonfairSync

4.1 lock() 方法原理

i. 基于 CAS 将 state 从 0 设置为 1

ii. 如果设置成功,将独享锁的线程设置为当前线程

iii. 请求失败后,进行一次重试

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;
    final void lock() {
        // 基于 CAS 尝试将 state 从 0 设置为 1
        if (compareAndSetState(0, 1))
            // 独享锁的线程设置为当前线程
            setExclusiveOwnerThread(Thread.currentThread());
        else
            // 重试一次(内部调用 tryAcquire 方法)
            acquire(1);
    }
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

如下获取锁失败后执行的 AQS 中的 acquire 方法。

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

由于 AQS 并没有提供可用的 tryAcquire 方法,tryAcquire 方法需要子类自己定制化,所以此处 tryAcquire 会调用 NonfairSync 中实现的 tryAcquire 方法。

4.2. TryAcquire() 方法原理

NonfairSync 的 tryAcquire 方法内调用了 nonfairTryAcquire 方法

nonfairTryAcquire 源码如下:

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    // 判断 state 是否为 0
    if (c == 0) {
        // 尝试 state 的值从 0 改为 1
        if (compareAndSetState(0, acquires)) {
            // 设置获取锁的线程为当前线程
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) { // 如果持有锁的线程为当前线程
        // state 的值 + acquire
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        // 设置 state 值为新值
        setState(nextc);
        return true;
    }
    return false;
}

i. 判断 state 是否为 0 ,是 0 则尝试获取锁

ii. 不为 0 ,判断持有锁的线程是否是当前线程

iii. 如果为当前线程,则将 state + acquire 重新对 state 赋值 (所以 ReentrantLock 为可重入锁)

iv. 持有锁的线程非当前线程,则返回 false

5.  公平锁 FairSync

5.1 lock()、TryAcquire() 方法原理

公平锁与非公平锁的原理类似,区别在于 tryAcquire 方法多了 hasQueuedPredecessors 的判断:

static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;
    final void lock() {
        acquire(1);
    }
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            // 公平锁比非公平锁多了 hasQueuedPredecessors 的判断
            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;
    }
}

5.2 hasQueuedPredecessors() 方法原理

public final boolean hasQueuedPredecessors() {
    Node t = tail;
    Node h = head;
    Node s;
    return h != t &&
              (
                (s = h.next) == null || 
                s.thread != Thread.currentThread()
            );
}

i. 如果头结点与尾结点不相等( h! = t ),说明不止一个线程请求锁

ii.  判断 head.next 是否为 null,如果为 null,返回 true

h != t 的同时 h.next==null 的情况:有其他线程第一次正在入队时,compareAndSetHead (node) 完成,还没执行 tail=head 语句时,此时 tail=null,head=newNode,head.next=null

iii. s.thread != Thread.currentThread() 判断当前线程是否为第二个线程

hasQueuedPredecessors 的判断保证了线程获取锁的顺序行,所以 FairSync 实现了公平锁。

6. tryLock 方法

6.1 tryLock()

尝试获取锁,获取到锁则返回 true,获取失败则返回 false

public boolean tryLock() {
    return sync.nonfairTryAcquire(1);
}

tryLock 内部调用的 nonfairTryAcquire 与非公平锁代码一致,所以 tryLock() 使用的是非公平策略。

6.2 tryLock(long timeout, TimeUnit unit)

与 tryLock() 的区别:此方法可以设置超时时间,超时时间到没有获取到该锁则返回 false。

7. ReentrantLock 的使用

lock.lock();
try {
    //TODO
} finally {
    lock.unlock();
}

8. 总结

i. ReentrantLock 的底层是使用 AQS 实现的可重入独享锁

ii. ReentrantLock 内部有公平与非公平两种实现,默认是非公平

iii. 对于 AQS 中 state,加锁 state + 1,解锁 state - 1,所以加锁几次就要解锁几次

iv. state 为 0 表示当前锁空闲,大于或等于 1 ,说明该锁已经被占用

发布了7 篇原创文章 · 获赞 3 · 访问量 309

猜你喜欢

转载自blog.csdn.net/sl285720967/article/details/105547762