AQS源码解析;Reentrantlock实现原理

AQS(AbstractQueuedSynchronizer, 队列同步器)它是Java并发用来构建锁和其他同步组件的基础框架。像Reentrantlock,Countdownlantch,Semaphore,ReadWriteLock都是由AQS来实现的,本篇文章将通过分析ReentrantLock源码来解释AQS

先看一段代码:

package com.example.AQS;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantlockTest {
    /**
     * 资源类
     */
    private static class Resource{
        Lock lock = new ReentrantLock();
        public void getResource(){
            //加锁
            lock.lock();
            //睡眠两秒钟代表业务逻辑
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("got resource");
            //解锁
            lock.unlock();
        }
    }
    public static void main(String[] args) {
        Resource resource = new Resource();
        //五个线程竞争资源
        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                resource.getResource();
            }).start();
        }
    }
}

在这里插入图片描述
程序执行结果是每隔两秒打印获取到资源,说明存在锁竞争
我们从头来分析一下代码,首先看new ReentrantLock(),

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

 }

我们可以在新建锁资源时选择公平锁还是非公平锁,至于两种锁的区别我们后续讲解

public ReentrantLock(boolean fair) {
		//传入true为公平,false为非公平
        sync = fair ? new FairSync() : new NonfairSync();
    }

看完构造方法来看下加锁方法,lock.lock()

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

ReentrantLock里面主要使用了其内部类Sync。Sync则是AbstractQueuedSynchronizer的实现类,也就是AQS的实现类。
在这里插入图片描述
通过继承关系我们看到公平锁和非公平锁都是Sync的子类。lock方法是一个抽象方法,我们先分析FairSync公平锁的实现。

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

这个判断语句逻辑很复杂,当进入到acquire方法后,会尝试获取锁,当获取失败,返回false,会进入acquireQueued方法,不妨剧透一下,这个方法就是对获取锁失败的线程进行排队。我们先来看tryacquire方法的实现。

protected final boolean tryAcquire(int acquires) {  //acquires=1
            final Thread current = Thread.currentThread();
            //c就是状态标识,如果c为0,说明能够尝试获取锁
            int c = getState();
            if (c == 0) {
            	//hasQueuedPredecessors返回false才能进行占有锁的动作,hasQueuedPredecessors主要作用是判断队列中有没有等待的线程。有返回true,没有返回false
            	//compareAndSetState是进行锁的占有动作,将c改为acquire的值
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    //setExclusiveOwnerThread设置当前锁占有的线程为当前线程
                    setExclusiveOwnerThread(current);
                    //都设置成功代表占有锁成功,返回true
                    return true;
                }
            }
            //当前线程为锁正在被占有的线程,即锁的冲入过程
            else if (current == getExclusiveOwnerThread()) {
               //对c进行+1
                int nextc = c + acquires;
                if (nextc < 0)   //超过最大值抛异常
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);  //设置state的值
                return true;
            }
            //获取锁失败,返回false
            return false;
        }

分析下if判断中的hasQueuedPredecessors方法:

//方法的主要作用就是判断有没有等待中的线程
public final boolean hasQueuedPredecessors() {
        Node t = tail; 		//队列的尾
        Node h = head;  //队列的头
        Node s;
        //如果头尾都为空,返回false,如果不为空,但是头的next不为空,说明有等待的线程,至于头和尾我们后续再说明
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

这样我们的tryacquire(1)方法就分析完了,我们再来分析acquireQueued(addWaiter(Node.EXCLUSIVE), arg))方法,只有获取锁失败了,才会进入此方法
先来看addWaiter方法

private Node addWaiter(Node mode) {
		//封装当前线程为Node对象,方便对链表数据结构的操作
        Node node = new Node(Thread.currentThread(), mode);
        //得到队尾
        Node pred = tail;
        if (pred != null) {  //当队尾不为空,说明前面有等待线程或者说明队列已经被初始化完成了
            node.prev = pred;  //将node的prev设置为队尾
            if (compareAndSetTail(pred, node)) {  //再将node通过cas设置成队尾
                pred.next = node;  //双向链表,设置原tail的next为node
                return node;
            }
        }
        //到这里说明队列还没进行初始化,这个方法就是初始化方法
        enq(node);
        return node;
    }

来分析enq方法:

private Node enq(final Node node) {
		//无限循环
        for (;;) {
            Node t = tail;
            if (t == null) {  //如果队尾为空
                if (compareAndSetHead(new Node()))  //通过cas将tail替换为new Node(),node里面的thread为空,这里设置线程为空的节点非常重要。
                    tail = head;  //将head也指向这个节点
            } else {
                node.prev = t;  //队尾不为空
                if (compareAndSetTail(t, node)) {  将node插到队尾
                    t.next = node;  //设置head的next
                    return t;
                }
            }
        }
    }

第一个线程获取锁失败后:
在这里插入图片描述
到这里addWriter方法就解析完了。接着看acquireQueued方法

  final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
       		//死循环
            for (;;) {
            	//找到当前node的前置节点
                final Node p = node.predecessor();
                //当前置节点为head节点并且获取锁成功后,说明已经占有锁
                if (p == head && tryAcquire(arg)) {
                    setHead(node);  //设置head为当前节点,这里面主要讲node的thread设置为空,恢复成原来的head的样子
                    p.next = null; // 将原来的头节点指向空
                    failed = false; //失败标识
                    return interrupted;
                }
                //走到这里说明没有获取成功,应该讲该线程park掉,shouldParkAfterFailedAcquire这个方法是判断前面的节点ws标识是不是0,如果是0就将它改成-1,返回true。parkAndCheckInterrupt就是当前线程park掉
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
 private final boolean parkAndCheckInterrupt() {
 		//park当前线程
        LockSupport.park(this);
        return Thread.interrupted();
    }

到这里lock方法基本解析完成,接下来看unlock方法

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

看Sync类的release方法

public final boolean release(int arg) {
		//tryRelease方法尝试释放资源,释放成功的话进入下面逻辑
        if (tryRelease(arg)) {
        	//head节点
            Node h = head;
            //h不为空,并且h的waitStatus 不为0,说明当前有线程在排对获取资源
            if (h != null && h.waitStatus != 0)
            	//取到该线程并且将其unpark
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

我们主要阅读tryRelease和unparkSuccessor方法

protected final boolean tryRelease(int releases) {
			//将原来状态-1
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            //已经没有线程占有锁,可以返回true,这里主要是考虑重入锁的情况,因此只能-1,不能直接赋值0
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);  //将资源拥有者改为空
            }
            //设置状态
            setState(c);
            return free;
        }

再来看unparkSuccessor方法

private void unparkSuccessor(Node node) {
		//得到当前node的状态
        int ws = node.waitStatus;
        if (ws < 0)   //如果小于1说明后面有线程等待,将其改为0
            compareAndSetWaitStatus(node, ws, 0);
          //当前传过来的node为head,得到head的next才是真正要进行释放的节点
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {   //s为空的情况,这里基本不会发生,除非人为更改node
            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);   //unpark此线程
    }

到这里锁的释放完成,代码回到上锁的park处。然后再次回到循环,获取锁。
至于interrupted主要是为lockInterruptibly方法准备的,在这里基本没用,因为无论返回什么都要进行下一次循环,这也是使用lock.lock(),不能在其他线程对其进行interrupt,但不能打断的原因。
再来看下非公平锁的实现

final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

发现只是在最开始尝试获取一下锁,如果没有获取到就按公平锁进行排队了。两者也只是这点区别。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值