• 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

  • 相关阅读:
    python 线程池ThreadPoolExecutor
    Docker学习4-常用命令之重要的容器命令
    关于电脑一天24小时多少度电电脑的一天用电量计算
    姿态估计评价指标
    jmeter模拟多用户并发
    ES6如何声明一个类?类如何继承?
    油溶性硫化锌镉/硫化锌量子点|ZnCdS/ZnS量子点|高亮蓝光
    ROS系统02——matlab读取ros话题消息代码
    对各种指针,数组的联立回顾复习(字符指针,指针数组,数组指针,函数指针,函数指针数组)
    计算机毕业设计python毕设项目之django本地健康宝微信小程序
  • 原文地址:https://blog.csdn.net/u012222011/article/details/125439985