Open Source, Open Future!
  menu
107 文章
ღゝ◡╹)ノ❤️

独占锁 ReentrantLock

简介

一种可重入的独占锁。

  • 从获取锁的机制上分为:公平锁、非公平锁;
  • 锁在同一时间只能被一个线程持有;同一个线程可多次获取到锁;
  • 加锁多少次,释放锁时也要释放同样的次数。

类图

image.png

说明:
FairSync:公平锁
NonFairSync:非公平锁

字段

private final Sync sync;

构造函数

  • 默认创建非公平锁
    public ReentrantLock() {
        sync = new NonfairSync();
    }

  • 根据参数fair确定创建的锁是否公平
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

内部类

abstract static class Sync extends AbstractQueuedSynchronizer 
// 非公平锁
static final class NonfairSync extends Sync
// 公平锁
static final class FairSync extends Sync

主要 API

获取公平锁

lock源码如下:

    public void lock() {
        sync.lock();
    }

接着调用FairSync类中的lock()

    final void lock() {
       acquire(1);
    }

然后调用AQS中的acquire获取独占锁(抽象队列同步器 AQS
AQS中完成了大部分的逻辑,其中tryAcquire需要子类去实现;
FairSync类中的tryAcquire源码如下:

        protected final boolean tryAcquire(int acquires) {
            // 获取当前线程
            final Thread current = Thread.currentThread();
            // 获取同步状态(共享资源)
            int c = getState();
            // 为 0 表示资源未被占用,可以获取
            if (c == 0) {
                // 先判断在同步队列中是否有比当前更早的等待线程
                // 没有的话当前线程就可以获取锁,并将同步状态置为 1
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    // 设置当前线程为持有锁的线程
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            // 不为 0 表示资源已被占用,判断下是否是当前线程占用的
            else if (current == getExclusiveOwnerThread()) {
                // 若是当前线程占用的则可以获取锁,并将同步状态加 1
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            // 同步状态不为 0 且资源不是当前线程占用的,则不能获取锁,返回 false
            return false;
        }

大概流程:

  1. 将获取公平锁的请求委托给FairSync处理;
  2. FairSync又将请求转发给AQS处理;(抽象队列同步器 AQS
  3. AQS中完成了大部分的逻辑,其中tryAcquire需要子类去实现;
  4. FairSync处理tryAcquire的逻辑;
    • 若资源(state)未被占用,判断有没有比当前更早的线程,若有则获取失败,否则可以获取,并将state置为1
    • 若资源(state)已被占用,判断是不是当前线程占用的,不是则获取失败,否则可以获取,并将state1
  5. 若以上获取失败,则由AQS完成后续处理(当前线程加入同步队列等)。

获取非公平锁

lock源码如下:

    public void lock() {
        sync.lock();
    }

接着调用NonfairSync类中的lock()

        final void lock() {
	    // 若state为0则改为1,并设置当前线程为持有锁的线程
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
		// 调用AQS处理请求
                acquire(1);
        }
		
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
		
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
	    // 获取同步状态
            int c = getState();
            // 为 0 表示资源未被占用,可以获取
            if (c == 0) {
                // CAS修改状态为 1
                if (compareAndSetState(0, acquires)) {
		     // 设置当前线程为持有锁的线程
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
	    // 不为 0 表示资源已被占用,判断下是否是当前线程占用的
            else if (current == getExclusiveOwnerThread()) {
		// 若是当前线程占用的则可以获取锁,并将同步状态加 1
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
	     // 同步状态不为 0 且资源不是当前线程占用的,则不能获取锁,返回 false
            return false;
        }

大概流程:

  1. 将获取非公平锁的请求委托给NonfairSync处理,若资源未被占用直接去抢占资源;
    • 若抢占成功则将state置为1,并设置当前线程为持有锁的线程;
    • 若抢占失败则将请求转发给AQS处理;(抽象队列同步器 AQS
  2. AQS中完成了大部分的逻辑,其中tryAcquire需要子类去实现;
  3. NonfairSync处理tryAcquire的逻辑;
    • 若资源未被占用,直接去抢占资源;
    • 若资源已被占用,判断是不是当前线程占用的,不是则获取失败,否则可以获取,并将state1
  4. 若以上获取资源失败,则由AQS完成后续处理(当前线程加入同步队列等)。

释放锁

unlock源码如下:

    public void unlock() {
        sync.release(1);
    }

将释放锁的请求转发给AQS处理;
AQS中完成了大部分的逻辑,其中tryRelease需要子类去实现;
ReentrantLock类中的tryRelease源码如下:

        protected final boolean tryRelease(int releases) {
            // 减去需要释放的资源数量
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            // 是否成功释放锁
            boolean free = false;
	    // 只有当state为0时,才表示释放锁成功
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

大概流程:
释放锁的逻辑比较简单,每调用一次unlockstatus就减1,当status0时,表示锁释放成功。

示例

测试

备注

使用JDK版本为1.8