【Java 并发编程】ReentrantLock

ReentrantLock

ReentrantLock 是一个可重入的互斥锁,又被称为“独占锁”。

ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。

ReentrantLock 锁在同一个时间点只能被一个线程锁持有;而可重入的意思是,ReentrantLock 锁,可以被单个线程多次获取。

ReentrantLock 特性概览

ReentrantLock 意思为可重入锁,指的是一个线程能够对一个临界资源重复加锁。这里,我们先将 ReentrantLock 跟常用的 Synchronized 进行比较:

特性 ReentrantLock Synchronized
锁实现机制 基于AQS 监视器模式
灵活性 支持响应中断、超时、尝试获取锁 不灵活
释放形式 必须显式调用 unlock() 释放锁 自动释放监视器
锁类型 公平锁、非公平锁 非公平锁
条件队列 可关联多个条件队列 关联一个条件队列
是否可重入

【示例】

public class LockTest {
    public void test () throws Exception {
        // 1.初始化选择公平锁、非公平锁
        ReentrantLock lock = new ReentrantLock(true);
        // 2.可用于代码块
        lock.lock();
        try {
            try {
                // 3.支持多种加锁方式,比较灵活; 具有可重入特性
                if(lock.tryLock(100, TimeUnit.MILLISECONDS)){ }
            } finally {
                // 4.手动释放锁
                lock.unlock();
            }
        } finally {
            lock.unlock();
        }
    }
}

源码分析

ReentrantLock 总共有三个内部类Sync、NonfairSync、FairSync。其源码如下:

public class ReentrantLock implements Lock, java.io.Serializable {
   private final Sync sync;
   abstract static class Sync extends AbstractQueuedSynchronizer {
      ...
   }

   static final class FairSync extends Sync {
      ...
   }

   static final class NonfairSync extends Sync {
      ...
   }
   ...
}

NonfairSync 与 FairSync 类继承自 Sync 类,Sync 类继承自 AbstractQueuedSynchronizer 抽象类:

image

抽象同步器:Sync

Sync 类的方法和作用如下:

方法 作用
void lock() 锁定,抽象方法
boolean nonfairTryAcquire(int acquires) 非公平地方式锁定
boolean tryRelease(int releases) 尝试在共享模式下获取对象状态
boolean isHeldExclusively() 判断资源是否被当前线程占有
ConditionObject newCondition() 新生成一个条件
Thread getOwner() 返回占有资源的线程
int getHoldCount() 返回状态
boolean isLocked() 资源是否被占用
void readObject(java.io.ObjectInputStream s) 自定义反序列化逻辑

部分源码如下:

public class ReentrantLock implements Lock, java.io.Serializable {

   abstract static class Sync extends AbstractQueuedSynchronizer {
      ...
      abstract void lock();

      // 非公平方式获取
      final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) { // 抢占式,CAS 方式设置状态成功,状态 0 表示锁没有被占用
                    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;
        }

        // 试图在共享模式下获取对象状态,此方法应该查询是否允许它在共享模式下获取对象状态,如果允许,则获取它
        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;
        }
   }
   ...
}

具体的流程:

image

非公平同步器:NonfairSync

NonfairSync 类继承了 Sync 类,表示采用非公平策略获取锁,实现了 Sync 类中抽象的 lock 方法,源码如下:

public class ReentrantLock implements Lock, java.io.Serializable {

   static final class NonfairSync extends Sync {
      final void lock() {
            if (compareAndSetState(0, 1)) // 抢占式,比较并设置状态成功,状态0表示锁没有被占用
                setExclusiveOwnerThread(Thread.currentThread()); // 把当前线程设置独占了锁
            else // 锁已经被占用,或者set失败
                acquire(1); // 以独占模式获取对象,忽略中断
        }

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

每一次 lock 调用,都会尝试获取锁,然而它并不会按照公平等待的原则进行等待,而是抢占式地获取锁。

如果线程抢占失败,就会调用 acquire(1),进而通过 tryAcquire() 获取锁。在这种情况下,如果获取锁失败,就会调用 addWaiter() 加入到等待队列中去。

公平同步器:FairSync

FairSync 类也继承了 Sync 类,表示采用公平策略获取锁,其实现了 Sync 类中的抽象 lock 方法,源码如下:

public class ReentrantLock implements Lock, java.io.Serializable {
    static final class FairSync extends Sync {
        final void lock() {
            acquire(1); // 以独占模式获取对象,忽略中断
        }

        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()) { // 状态不为0,即资源已经被线程占据
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }
    ...
}

公平锁 FairSync 的 lock 方法,使用了 AQS 的默认实现,当资源空闲时,它总是会先判断 sync 队列是否有等待时间更长的线程,如果存在,则将该线程加入到等待队列的尾部,实现了公平获取原则。

小结

在 ReentrantLock 里面,不管是公平锁(FairSync#tryAcquire),还是非公平锁(Sync#nonfairTryAcquire),都有一段通过 state 控制重入的类似逻辑。其中,state 这个字段主要的作用:

  • state 初始化的时候为 0,表示没有任何线程持有锁。

  • 当有线程持有该锁时,值就会在原来的基础上 +1,同一个线程多次获得锁是,就会多次 +1,这里就是可重入的概念。

  • 解锁也是对这个字段 -1,一直到 0,此线程对锁释放。


参考:

posted @ 2023-10-17 11:36  LARRY1024  阅读(13)  评论(0编辑  收藏  举报