ReentrantLock说明

ReentrantLock

ReentrantLock是基于AQS实现的可重入的独占锁,只有一个线程可以获取锁,如果获取锁的其他线程处于阻塞则会被放入AQS的等待队列。

其内部有公平锁和非公平锁两种实现方式,都是对AQS的实现。如下类图,Sync实现与AQS,NonfairSync与FairSync继承与Sync分别代表非公平锁和公平锁的实现。默认创建(即构造函数未指定参数)的锁时非公平锁。

在这里插入图片描述

这里AQS的state代表线程获取该锁的重入次数,默认情况下state状态为0,表示没有线程获取到锁。当一个线程第一次获取锁时会尝试CAS设置state为1,如果成功则改线程获取到锁且标记该锁的持有者为当前线程。在该线程未释放锁的情况下第二次获取锁。状态设置为2,表示该线程获取了两次锁,即可重入,state表示重入的次数。当该线程释放锁时,会尝试CAS减一,如果减一后state状态为0则释放该锁。

获取锁

1、void Lock()

如果线程调用此方法说明需要获取锁。如果没有其他线程获取到锁则马上返回,设置AQS状态值为1;如果该线程已经获取到了锁也马上返回,AQS状态值加1;如果其他线程已经获取到锁了则该线程会被放到AQS阻塞队列被挂起等待获取锁。

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

如上方法,调用的是AQS的acquire方法,此方法中tryAcquire需要子类重写,所以代码走到了ReentrantLock重写的tryAcquire方法,在非公平锁策略中最终调用的是nonfairTryAcquire方法:

final boolean nonfairTryAcquire(int acquires) {
    //获取当前线程
    final Thread current = Thread.currentThread();
    //获取当前线程的状态值state
    int c = getState();
    //如果c==0说明还未有其他线程获取到锁,该线程是第一个来获取锁的线程
    if (c == 0) {//1
        //CAS操作改变状态值(保不齐在这两行代码执行的某个瞬间其他线程已经获取到锁了)
        if (compareAndSetState(0, acquires)) {
            //设置锁持有者为当前线程
            setExclusiveOwnerThread(current);
            //返回
            return true;
        }
    }
    //如果c不等于0,说明已经有线程获取过锁了,这里判断是否是当前线程曾经获取过的
    else if (current == getExclusiveOwnerThread()) {//2
        //如果是当前线程曾经获取过的,那就获取重入锁,增加state状态值
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        //设置状态值
        setState(nextc);
        return true;
    }
    //返回false,说明获取锁失败,当前线程会被放入AQS阻塞队列
    return false;
}

这里怎么体现非公平锁呢?

假设有线程1执行到代码1处发现c不等于0,再执行到代码2发现线程1不是锁持有者,随机返回false;此时,线程2来了,假设期间锁已经释放了,线程2成功获取到了锁。线程2晚于线程1请求锁,但是线程2先于线程1获取到锁,这就不公平了。

既然如此我们索性就来看下公平锁是怎么实现的,我们直接看FairSync的tryAcquire方法吧:

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        //公平锁实现关键
        //返回true说明当前线程前面有等待时间更长的线程,false说明当前线程为AQS等待队列的头节点或者等待队列为空
        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;
}

公平锁的实现代码跟非公平锁的实现极为相似,不同之处就是在hasQueuedPredecessors方法,此为关键:

public final boolean hasQueuedPredecessors() {
    Node h, s;
    if ((h = head) != null) {
        //由于头结点是哨兵节点,实际有用的节点为下一个节点
        //如果下一节点为空,或者下一节点的等待值大于0(即取消状态)
        if ((s = h.next) == null || s.waitStatus > 0) {
            //1
            s = null; // traverse in case of concurrent cancellation
            //搜索到等待队列中最靠前的ws值非正且非null的节点
            for (Node p = tail; p != h && p != null; p = p.prev) {
                if (p.waitStatus <= 0)
                    s = p;
            }
        }
        //队列中最靠前的待获取锁的节点不为空且所代表的节点不是当前节点返回true
        if (s != null && s.thread != Thread.currentThread())
            return true;
    }
    //如果头结点为空,返回false说明当前获取锁线程为第一个线程
    return false;
}

代码1处有点难理解,既然head节点的next节点为空,那不是说明等待队列没了吗?

其实不然,在并发环境下一切皆有可能,如下情况会出现h.next==null,但是队列里还有其他元素:

1、在当前线程进入hasQueuedPredecessors if判断的同时,已经有其他的线程通过addWaiter方法改变了尾结点(tail),(第一次往队列里加元素的时候)但是首节点的后继指针还没指向该尾节点。这时,其实队列中已经有等待的结点了。

2、将head赋值给当前的h,但是有其他线程执行p == head && tryAcquire(arg)将原先的头结点移出了队列,所以导致h.next为null。

2、void lockInterruptibly()

该方法与lock方法类似,只不过会响应中断,当该线程在获取锁的过程中,如果其他线程调用了该线程的interrupt方法,该线程就抛出InterruptException异常并返回。

public void lockInterruptibly() throws InterruptedException {
    sync.acquireInterruptibly(1);
}
public final void acquireInterruptibly(int arg)
        throws InterruptedException {
    //如果线程被中断,抛出异常
    if (Thread.interrupted())
        throw new InterruptedException();
    if (!tryAcquire(arg))
        //获取锁失败后调用方法
        doAcquireInterruptibly(arg);
}
private void doAcquireInterruptibly(int arg)
    throws InterruptedException {
    //这里大部分代码与AQS篇里面的acquireQueued类似,不再说明
    final Node node = addWaiter(Node.EXCLUSIVE);
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                return;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())//这里需要说明下:该线程被挂起后当再次被唤醒会检查是否被										 //中断,如果是则抛出异常
                throw new InterruptedException();
        }
    } catch (Throwable t) {
        cancelAcquire(node);
        throw t;
    }
}

3、boolean tryLock()

尝试获取锁,如果获取到锁则马上返回true,否则放回false,注意此方法不会引起阻塞。

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

最终调用的是非公平锁的nonfairTryAcquire方法,上面有讲解。

4、boolean tryLock(long timeout, TimeUnit unit)

该方法与tryLock类似,只不过加了超时时间,如果超过设置时间还未获取到锁则直接返回false。

public boolean tryLock(long timeout, TimeUnit unit)
        throws InterruptedException {
    return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    //如果线程被中断抛出异常
    if (Thread.interrupted())
        throw new InterruptedException();
    //tryAcquire见上面
    return tryAcquire(arg) ||
        doAcquireNanos(arg, nanosTimeout);
}

我们看doAcquireNanos方法

private boolean doAcquireNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    if (nanosTimeout <= 0L)
        return false;
    //计算等待截止时间
    final long deadline = System.nanoTime() + nanosTimeout;
    //在等待队列队尾加上独占模式的节点
    final Node node = addWaiter(Node.EXCLUSIVE);
    try {
        //自旋
        for (;;) {
            //这里跟AQS那边逻辑一样
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                return true;
            }
            //计算离截止时间还有多少
            nanosTimeout = deadline - System.nanoTime();
            if (nanosTimeout <= 0L) {
                //如果已经超过截止时间,取消请求直接返回false
                cancelAcquire(node);
                return false;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                //如果剩余时间大于1000纳秒才将自己挂起,否则继续自旋,没必要计算太精确,下次自旋基本				 //上就超时了,提高响应效率
                nanosTimeout > SPIN_FOR_TIMEOUT_THRESHOLD)
                LockSupport.parkNanos(this, nanosTimeout);//将自己挂起
            if (Thread.interrupted())
                //响应中断
                throw new InterruptedException();
        }
    } catch (Throwable t) {
        cancelAcquire(node);
        throw t;
    }
}

看如上源码,该方法同样可以被中断.

SPIN_FOR_TIMEOUT_THRESHOLD表示的是1000纳秒,官方注释表示给个粗糙的时间计算以提高响应效率

5、void unlock

释放锁,调用此方法会是AQS状态值减1,如果减完等于0则释放锁,否则只是减1而已,如果当前线程未持有锁就调用该方法会抛出IllegalMonitorStateException异常

public void unlock() {
    sync.release(1);
}
//AQSrelease方法
public final boolean release(int arg) {
    //由子类实现
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            //唤醒后继节点,在AQS中有讲解
            unparkSuccessor(h);
        return true;
    }
    return false;
}

ReentrantLock中的tryRelease方法

protected final boolean tryRelease(int releases) {
    //获取state并减去给定值
    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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xhjwyy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值