AQS 之 ReentrantLock

AQS 之 ReentrantLock

AQS关键变量
state: 用于标记锁的状态,在reentrantLock中,state为0时表示锁没有被线程占用,可以被获取

本文基于ReentrantLock的非公平锁
1、从lock()方法开始

final void lock() {
//lock时会先去尝试获取锁,并通过cas操作去修改state的值
  if (compareAndSetState(0, 1))
  //cas成功,表示获取到了锁,则将设置获取独占锁的线程为当前线程
  	setExclusiveOwnerThread(Thread.currentThread());
  else
  //cas失败,继续向下执行
	acquire(1);
        }

2、第一次获取锁失败时会执行acquire(1)方法,若tryAcquire()方法return true,则表示当前线程抢到了锁,不需要向下继续执行了

 public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

3、执行acquire(1)方法时先执行tryAcquire(1)

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

**4、非公平锁则执行到nonfairTryAcquire(1)方法**
final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            //继续向下执行时,会再次尝试去获取一次锁
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //此处是判断是否是线程重入,若是线程重入,则将state状态进行修改,如之前state=1,会将state更改为state=2,此处也可以说明lock()与unlock()为什么必须要成双成对的出现
            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;
        }

5、当执行完第四步后还没有抢到锁的线程会去排队addWaiter(Node.EXCLUSIVE),标记节点以排他模式等待

 private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        //获取尾节点
        Node pred = tail;
        if (pred != null) {
        //尾节点不为空时将当前要入队的节点变为尾节点
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //若尾节点不存在或者尾节点存在但此时出现并发,有其他线程也在入队,尾节点变化了,导致当前节点cas入队失败则继续向下执行
        enq(node);
        return node;
    }


   private Node enq(final Node node) {
   //此处自旋是为了保证并发情况下始终入队成功
        for (;;) {
        //先获取尾节点
            Node t = tail;
            //尾节点为空时,表示队列是空的
            if (t == null) { // Must initialize
            //先初始化一个空的头节点
                if (compareAndSetHead(new Node()))
                //并将头节点也标记为尾节点
                    tail = head;
            } else {
            //尾节点不为空时
                node.prev = t;
                //将当前节点设置为尾部节点
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

6、入队成功后,继续执行acquireQueued()

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)) {
                //成功获取后,将自己设置为头节点
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    //一次就获取锁成功或者在park之前就获取到了锁时,不会被标记中断
                    return interrupted;
                }
                //获取锁失败后,会继续执行
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
   //setHead会将head节点包含的thread信息清空 
   private void setHead(Node node) {
        head = node;
        node.thread = null;
        node.prev = null;
    }

/**
每个节点的等待状态由其前驱节点记录,在锁被释放时去唤醒等待线程就只需要判断头节点的等待状态就可以知道是否需要唤醒,该方法主要为了去阻塞当前线程,并告诉前驱节点,在可以获取锁的时候需要来唤醒我

默认waitStatus是0
还有其他四种状态
CANCELLED =  1  线程被取消
SIGNAL    = -1  下一个线程需要unpark(即需要唤醒)
CONDITION = -2  线程在条件等待状态
PROPAGATE = -3  下一个共享请求应该被无条件传播
**/
   private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
         //获取当前线程节点的前驱节点的等待状态
        int ws = pred.waitStatus;
        //Node.SIGNAL,waitStatus的一种状态值:表示是后续线程需要unpark(即需要唤醒)
        if (ws == Node.SIGNAL)
            return true;
        if (ws > 0) {
         //大于0表示当前节点前驱节点是取消状态
            do {
            //则循环去寻找前面节点,直到找到一个没被取消的
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

7、shouldParkAfterFailedAcquire()返回true 时会执行 parkAndCheckInterrupt()

   private final boolean parkAndCheckInterrupt() {
   //阻塞当前线程
        LockSupport.park(this);
        //线程被唤醒后返回中断状态,并清理中断标记
        return Thread.interrupted();
    }

通过CAS操作、自旋、阻塞去确保正确的获取锁

然后可以看一下释放锁
1、释放锁使用unlock

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

   public final boolean release(int arg) {
  		//尝试释放锁
        if (tryRelease(arg)) {
        //成功后,若头节点不为空且等待状态不为0就去尝试唤醒后续等待线程
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

    protected final boolean tryRelease(int releases) {
    		//修改state状态
            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;
        }

2、释放锁成功后(state = 0)会去唤醒后面的等待线程

 private void unparkSuccessor(Node node) {
 		//获取头节点的等待状态
        int ws = node.waitStatus;
        //等待状态属于正常状态时,其状态修改为0
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);
		//获取头节点的下一节点
        Node s = node.next;
        //如果没有下一节点,或者下一节点的等待状态不正常
        if (s == null || s.waitStatus > 0) {
            //将s置为null
            s = null;
            //从尾部开始向前找,找到排在队列最前面且等待状态正常的节点(被取消的节点有可能正在被踢出队列,不存在后节点,所以从后向前找)
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        //若获取到了正常的节点,则唤醒对应的线程
        if (s != null)
            LockSupport.unpark(s.thread);
    }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值