【并发基础】ReentrantLock详解(一)

简介

ReentrantLock重入锁,是实现Lock接口的一个类,也是在实际编程中使用频率很高的一个锁,支持重入性,表示能够对共享资源能够重复加锁,即当前线程获取该锁再次获取不会被阻塞。与此同时,ReentrantLock还支持公平锁和非公平锁两种方式。

数据结构

//自定义同步器,Sync为ReentrantLock的内部类,继承AbstractQueuedSynchronizer
private final Sync sync;

//默认为非公平锁
public ReentrantLock() {
    sync = new NonfairSync();
}

//可以指定公平策略
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

核心方法

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

lock()获取锁,主要调用AQS的acquire(1),此时可分为三种情况:

  • 当前线程成功获取锁,该方法直接返回,设置state为1;
  • 当前线程已经持有该锁,该方法直接返回,state加1,即重入次数加1;
  • 锁被其他线程持有,则当前线程进入同步队列等待。
lockInterruptibly()
public void lockInterruptibly() throws InterruptedException {
    sync.acquireInterruptibly(1);
}

该方法和lock类似,只是响应中断,会抛出InterruptedException异常,需要上层调用者自行进行中断异常处理。

该方法直接调用AQS相应的方法实现。

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

tryLock()尝试获取锁,获取成功则返回true,获取失败则返回false,不会一直阻塞。另一点值得注意的是,该方法始终采用非公平策略,即使初始化时已经声明使用公平策略,也就是说,调用tryLock方法时,会立即尝试获取锁,无论是否还有没有其他线程正在等待。

该方法调用ReentrantLock自定义同步器Sync里面的方法。

tryLock(long timeout, TimeUnit unit)
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
    return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}

tryLock()方法有如下特性:

  • 无法立即获取锁时,该方法会等待timeout时间;
  • 当锁声明为公平锁时,该方法支持使用公平策略;
  • 响应中断。

该方法直接调用AQS相应的方法实现。

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

unlock()方法会调用ReentrantLock的自定义同步器Sync的tryRelease方法,有如下三种情况:

  • 如果当前线程持有锁,则state减1;
  • 如果state为0时直接释放锁;
  • 如果当前线程不持有锁,则抛出IllegalMonitorStateException异常。
newCondition()
public Condition newCondition() {
    return sync.newCondition();
}

Condition对象可以起到Object监视器方法(wait, notify, notifyAll)的作用,Object的监视器方法需要在同步方法或同步块中调用,而Condition的方法需要在获取锁之后调用,不同在于一个锁可以创建多个Condition对象,可以对不同线程进行不同的控制(个人理解为是对线程的分类),而synchronized则类似于只有一个Condition对象的Lock。

getHoldCount()
public int getHoldCount() {
    return sync.getHoldCount();
}

HoldCount记录当前线程持有该锁的次数,如果当前线程持有锁,则该值为state的值,否则为0。

Sync详解

Sync同步器使用AQS的state变量表示持有锁的线程在当前锁重入的数量,当state为0时,表示锁未被其他线程持有。

在ReentrantLock中,重入一次则数量+1。

nonfairTryAcquire(int)

nonfairTryAcquire(int)方法实现非公平的tryLock。

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    //state为0时,表示没有其他线程持有锁
    if (c == 0) {
        //修改state的值,并设置当前线程为该独占锁的持有者
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    //如果当前线程已经持有该锁
    else if (current == getExclusiveOwnerThread()) {
        //更新state,state表示当前锁已被重入的数量
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}
tryRelease(int)

需要持有锁的线程才能调用该方法,否则会抛出IllegalMonitorStateException异常。

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;
}
newCondition()

ReentrantLock的Condition实现为ConditionObject

final ConditionObject newCondition() {
    return new ConditionObject();
}
其他核心方法
//获取锁的持有者
final Thread getOwner() {
    return getState() == 0 ? null : getExclusiveOwnerThread();
}

//HoldCount表示当前线程持有锁的数量
final int getHoldCount() {
    return isHeldExclusively() ? getState() : 0;
}

//AQS的state用来判断锁是否已被锁定
final boolean isLocked() {
    return getState() != 0;
}

//判断当前线程是否持有该锁
protected final boolean isHeldExclusively() {
    return getExclusiveOwnerThread() == Thread.currentThread();
}

NonfairSync(非公平同步器)

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

    final void lock() {
        //强制获取锁,如果锁未被锁定,也就是state为0,则下面操作返回true,当前线程获取锁
        //否则返回false,当前线程进入同步队列
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }

    //调用父类Sync的nonfairTryAcquire方法获取锁
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

FairSync(公平同步器)

static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;

    //直接调用AQS的acquire方法
    final void lock() {
        acquire(1);
    }
    
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            //hasQueuedPredecessors判断当前线程是否是下一个可执行结点
            //非公平锁会直接尝试CAS更新state,但是公平锁需要当前结点是头结点才能使用CAS更新state
            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;
    }
}

参考资料

彻底理解ReentrantLock

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
重入锁(ReentrantLock)是一种独占锁,也就是说同一时间只能有一个线程持有该锁。与 synchronized 关键字不同的是,重入锁可以支持公平锁和非公平锁两种模式,而 synchronized 关键字只支持非公平锁。 重入锁的实现原理是基于 AQS(AbstractQueuedSynchronizer)框架,利用了 CAS(Compare And Swap)操作和 volatile 关键字。 重入锁的核心思想是“可重入性”,也就是说如果当前线程已经持有了该锁,那么它可以重复地获取该锁而不被阻塞。在重入锁内部,使用了一个计数器来记录当前线程持有该锁的次数。每当该线程获取一次锁时,计数器就加 1,释放一次锁时,计数器就减 1,只有当计数器为 0 时,其他线程才有机获取该锁。 重入锁的基本使用方法如下: ```java import java.util.concurrent.locks.ReentrantLock; public class ReentrantLockTest { private static final ReentrantLock lock = new ReentrantLock(); public static void main(String[] args) { new Thread(() -> { lock.lock(); try { System.out.println(Thread.currentThread().getName() + " get lock"); Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); System.out.println(Thread.currentThread().getName() + " release lock"); } }, "Thread-1").start(); new Thread(() -> { lock.lock(); try { System.out.println(Thread.currentThread().getName() + " get lock"); } finally { lock.unlock(); System.out.println(Thread.currentThread().getName() + " release lock"); } }, "Thread-2").start(); } } ``` 在上面的示例代码中,我们创建了两个线程,分别尝试获取重入锁。由于重入锁支持可重入性,因此第二个线程可以成功地获取到该锁,而不被阻塞。当第一个线程释放锁后,第二个线程才获取到锁并执行相应的操作。 需要注意的是,使用重入锁时一定要记得在 finally 块中释放锁,否则可能导致死锁的问题。同时,在获取锁时也可以设置超时时间,避免由于获取锁失败而导致的线程阻塞问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值