ReentrantLock之lock和unlock方法

ReentrantLock类结构

ReentrantLock 包结构

  1. 该类实现Lock接口。Lock接口定义了锁最基本的方法。
  2. 该类的final成员变量Sync同步器,以静态内部类的形式提供了公平同步器和非公平同步器。(构造器提供选择是否公平,默认非公平。公平锁性能要劣于非公平锁)

Sync类结构

在这里插入图片描述

  1. Sync接口继承了AbstractQueuedSynchronizer抽象类,也就是我们常听说的AQS。
  2. AbstractOwnableSynchronizer抽象类,提供设置获取锁的当前线程的方法(便于互斥和可重入功能)。

LOCK方法(非公平锁为例)

在这里插入图片描述

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

        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
            if (compareAndSetState(0, 1)) // 原子实现状态的设置(内部调用unsafe的方法)。0 -> 1
                setExclusiveOwnerThread(Thread.currentThread()); // 设置获锁的线程
            else
                acquire(1);  // 尝试获锁,排队
        }

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

Sync的原理很简单就是维护一个volatile的status的值,使用CAS的方式来实现状态改变原子操作。

 public final void acquire(int arg) {
        if (!tryAcquire(arg) && // 再次尝试获取锁
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 失败后排队
            selfInterrupt(); // 自断
  }

对于没有立刻获取锁的线程,会加入到等待队列当中。

        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread(); // 当前线程
            int c = getState(); // 当前锁状态,0没人持有
            if (c == 0) {
            // 获取锁
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
           // 判断当前线程为持锁者,锁状态+1 (实现锁的可重入功能)
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

通过判断当前线程是否为持锁的线程,如果是的话通过改变Staus的值(+1)来实现可重入锁的功能,因为只有一个线程,设置state不需要原子操作。

看一下排队代码:

 private Node addWaiter(Node mode) { Node.EXCLUSIVE
        Node node = new Node(Thread.currentThread(), mode); // 构建节点
        // Try the fast path of enq; backup to full enq on failure // 这种方式快一点,,先CAS,失败在enq
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) { // CAS 保证原子性
                pred.next = node;
                return node;
            }
        }
        enq(node); // 头节点或其他CAS失败的,该方法使用for死循环直到入队成功
        return node;
    }

自旋队列获取锁的过程:

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor(); // 前继节点是头节点
                if (p == head && tryAcquire(arg)) {  //  尝试CAS 获取锁(原因:1.FIFO,2.头节点是获取锁的,释放后会唤醒下一节点)
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) && //  如果节点状态是SIGANL但是没有获取锁可以安全挂起当前线程,如果是退出节点忽略,获取未推出的前继节点再尝试获取,如果状态是可以获取锁,将节点状态改为SIGNAL,再尝试获取锁
                    parkAndCheckInterrupt())   // 检测该线程是否中断
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

这里符合FIFO,乍一看可能觉得挺公平的。但是非公平锁是先CAS获取锁,没有获取后才会去排队。因此刚释放锁的线程还是有机会去获取锁。所以对于公平锁来将要实现公平需要满足队列FIFO,但是直接排队采取一个一个获取锁方法又不是太可取,是怎么做的呢,看一下源码:
在这里插入图片描述

可以看到代码只比非公平锁多了一个判断是否有前继节点的方法hasQueuedPredecessors(),这样保证每次获取锁的时候就会先让排在队头的节点获取锁。当然这样肯定会造成线程上下文切换次数变多,吞吐量降低。

  public final boolean hasQueuedPredecessors() {
        // The correctness of this depends on head being initialized
        // before tail and on head.next being accurate if the current
        // thread is first in queue.
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&  
            ((s = h.next) == null || s.thread != Thread.currentThread());  // true: 队列不为空,头节点的下一个节点为null(只有第一次入空队才会出现enq()方法,说明当前线程晚于一个线程进入acquire(1)方法,之后也需要排队),或者下一节点不是当前线程,说明有前继节点。
    }

UNLOCK方法

锁的释放就很简单,直接看一下源码:
在这里插入图片描述
unparkSuccessor(h)方法用于唤醒后继节点的线程。

其他方法tryLock

Boolean tryLock(): 直接调用sync的CAS方法,返回是否获取到锁

tryLock(longtimeout,TimeUnitunit): 先调用CAS方法,没有获取锁,再调用带有过期时间的CAS方法(采用LockSupport.parkNanos指定挂起指定时间,这段时间都没有被唤醒获取锁的话返回false)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值