zoukankan      html  css  js  c++  java
  • 非公平锁加锁主要流程解读

    一、方法一

      

    final void lock() {
        //第一个线程获取锁,如果成功,则直接返回,这种情况是最快的 
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
           //如果不是第一个线程获取锁,则加锁流程
            acquire(1);
    }

    二、方法二

    // 3个主要方法 tryAcquire、addWaiter、  acquireQueued
    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();
        //如果这个线程进来正好前面持有锁的线程释放锁,则进入方法获取锁
        if (c == 0) {
            if (compareAndSetState(0, acquires)) {
                //获取锁成功,将当前执行线程设置为自己
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        //锁的重入
        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;
    }

    四、方法四

      

    //节点放入到队列的过程
    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;
        //如果tail不为空,即已经有队列了
        if (pred != null) {
                //当前线程的节点,前置节点设置为原来链表的tail
            node.prev = pred;
            //如果队列此时尾部节点还是pred,则设置node节点为尾部节点
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //当前锁中还没有队列,创建一个新队列
        enq(node);
        return node;
    }

    五、方法五

       

    //构建一个队列,用来排队
    private Node enq(final Node node) {
        //循环2次创建一个队列
        for (;;) {
            Node t = tail;
             //tail节点为空,说明 还没有队列
            if (t == null) { // Must initialize ,第一个循环会进到这个里面
                //创建一个空的node,并且空节点设置为头节点
                if (compareAndSetHead(new Node()))
                   //tail 也指向这个空节点
                    tail = head;
            } else {
                //当前节点的prev ,指向 前面 空的头节点
                node.prev = t;
                // 把 当前线程的 node节点,放到队列的尾部
                if (compareAndSetTail(t, node)) {
                    // 把原tail节点的next 指向当前节点,即 空的node节点(头节点)的next指向当前节点
                    t.next = node;
                    return t;
                }
            }
        }
    }

    到此为止,锁中的队列结构为:

     六,方法六

      

    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;
                    return interrupted;
                }
                //节点进入阻塞状态
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

    七、方法七 

      

    设置当前节点的前一个节点waitStatus 状态为 SIGNAL ,这样前一个节点释放锁以后才会唤醒后面的线程节点
    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) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

     八、方法八-线程阻塞

    private final boolean parkAndCheckInterrupt() {
        //线程阻塞,当被唤醒以后会接着这个地方继续执行
        LockSupport.park(this);
        return Thread.interrupted();
    }

     到此线程的加锁过程完成

     

  • 相关阅读:
    《程序员代码面试指南》第八章 数组和矩阵问题 数组排序之后相邻数的最大差值
    《程序员代码面试指南》第八章 数组和矩阵问题 数组中未出现的最小正整数
    《程序员代码面试指南》第八章 数组和矩阵问题 数组的partition 调整
    《程序员代码面试指南》第八章 数组和矩阵问题 不包含本位置值的累乘数组
    《程序员代码面试指南》第八章 数组和矩阵问题 打印N 个数组整体最大的Top K
    《程序员代码面试指南》第八章 数组和矩阵问题 数组中子数组的最大累乘积
    《程序员代码面试指南》第八章 数组和矩阵问题 在数组中找到一个局部最小的位置
    《程序员代码面试指南》第八章 数组和矩阵问题 子矩阵的最大累加和问题
    MySQL 进阶4 SQL常见函数: 字符函数/数学函数/日期函数/流程控制函数(if/case)
    MySQL 进阶3 排序查询
  • 原文地址:https://www.cnblogs.com/lean-blog/p/13719562.html
Copyright © 2011-2022 走看看