• AQS初探


    AQS的设计核心原理:

    1.自旋,也就是死循环

    2.cas:比较并且交换,

    3.LockSupport.lock 阻塞

    4.入队列

    4大核心思想:假设一个线程进来要抢锁,一直进行死循环的话,那么如果抢到锁的线程进行的业务时间很长,就会一直耗费cpu的时间片在进行死循环抢锁,这是我们就会使用cas来进行抢锁,如果成功地话,就会记录一个抢锁成功地状态,如果抢锁失败就会进行阻塞,同时把阻塞的线程记录在队列中,当抢到锁的线程执行玩业务释放锁, 那么在队列中的阻塞线程就会进行抢线程操作,这里有个问题,是否抢锁的线程是公平的,也就是锁,如果有很长队列那么刚进来的线程到底是插队抢锁还是排队抢锁呢?这就涉及到公平锁和非公平锁。公平锁也就是会从队列中取队头的线程。非公平锁直接去抢cpu时间片,抢到的话就立马执行。这既是AQS的核心思想。

    下面我们来看看具体的代码实现:

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

    判断当前线程是否获取到锁,如果获取到锁,同时判断队列中是否还有其他线程,如果没有其他线程那么进行AQS,同时设置当前线程为排他线程,下面的else if是判断当前线程是否是二次获取到锁,这也从侧面验证了ReentrantLock是可重入锁,如果是的话,那么锁状态自加1.

     protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                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;
        }
    }

    如果没有抢到锁那么直接进入队列,具体代码如下:

    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;
            }
        }
        enq(node);
        return node;
    }

    入队列也就是通过双向链表,CHL三个大神写的。当第一个线程进来抢锁,此时双向链表的队列头为空,尾也是空,那么进入enq逻辑。

    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;
                }
            }
        }
    }

    这里有个死循环,也就是自旋的目的就是保证必须入队列必须成功,否则就会导致线程丢失。线面那个cas的作用是为了保证入队列的原子性,比如双向链表的指针一个操作好了头结点,然后尾结点插入的时候被打断了,这样就会导致各种问题,为了保证成功入队列就需要用cas,没拿到锁的线程一定不能被丢弃,

    看下这行代码:

    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {

    这里设计到线程的几个状态:

    signal=-1 代表可被唤醒

    cancelled=1 代表出现异常,由中断引起的。,也就是说第一轮循环的时候会修改head的状态,将signal=-1标记可被唤醒的状态,第二轮在进行阻塞线程,但在节点阻塞前还得尝试获取一次锁,能够获取到锁的线程则出队列,并且把head往后挪动一个节点,新节点就是当前节点。不能获取到锁则阻塞等待被唤醒。

    释放锁的逻辑代码:

    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;
    }

    线程当前的状态-1,如果该状态为0,那么代表可以释放,同时把变量exclusive置为null,state=0

  • 相关阅读:
    Mysql高级——性能分析工具(2)
    再拯救一下,语言搞多了总是忘,曲不离口,敲敲基础的fortran77小程序
    Effective C++条款22——将成员变置声明为private(设计与声明)
    HTML5期末大作业:基于html企业官网项目的设计与实现【艺术官网】
    sqllab第二十四关通关笔记
    17.Java的反射机制
    探索 ONLYOFFICE:开源办公套件的魅力
    linux常见命令-文件目录类
    云计算革命:多云管理与混合云的实践指南
    Hudi 数据湖的插入,更新,查询,分析操作示例
  • 原文地址:https://blog.csdn.net/u012222011/article/details/125439985