zoukankan      html  css  js  c++  java
  • ReentrantLock & AQS

    概念

    Syncronized由于其使用的不灵活性,逐渐的被抛弃~ 常用解决方案,有以下三种使用方式:(暂时的不考虑condition的应用,暂时还没有总结出来)

    • 同步普通方法,锁的是当前对象。
    • 同步静态方法,锁的是当前 Class 对象。
    • 同步块,锁的是 () 中的对象。

    实现原理

    JVM 是通过进入、退出对象监视器( Monitor )来实现对方法、同步块的同步的。

    具体实现是在编译之后在同步方法调用前加入一个 monitor.enter 指令,在退出方法和异常处插入 monitor.exit 的指令。

    其本质就是对一个对象监视器( Monitor )进行获取,而这个获取过程具有排他性从而达到了同一时刻只能一个线程访问的目的。

    而对于没有获取到锁的线程将会阻塞到方法入口处,直到获取锁的线程 monitor.exit 之后才能尝试继续获取锁。

    image

    ReentrantLock 是一个可重入的排他锁。其主要的方法有 lock tryLock unlock。
    主要讲公平锁的lock方法。ReentrantLock的lock方法借助了FairSync的lock方法,先放类图,有个简单的了解
    是一个重入锁:一个线程获得了锁之后仍然可以反复的加锁,不会出现自己阻塞自己的情况。

    image

    代码演示

    final void lock() {
                acquire(1);   //定义成final类型,不允许被重写  lock是个同步阻塞的方法,会堵塞到获取锁成功
            }
       public final void acquire(int arg) {
            if (!tryAcquire(arg) &&                            //收先尝试去获取锁
                acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) //获取不到锁的话把自身封装成一个一个node放入到等待的队列中去
                selfInterrupt();
        }
         //关于队列的形状,jdk还画了个图,可以简单的对照
         *      +------+  prev +-----+       +-----+
         * head |      | <---- |     | <---- |     |  tail
         *      +------+       +-----+       +-----+
            protected final boolean tryAcquire(int acquires) {
                final Thread current = Thread.currentThread();
                int c = getState();    //判断aqs的同步状态 为0 说明 现在aqs没有被任何的线程所占有
                if (c == 0) {
                    if (!hasQueuedPredecessors() &&   //判断当前节点是否是头结点 或者当前队列为空(因为是公平锁,当然是在最前面的才可以执行)
                        compareAndSetState(0, acquires)) {  //使用cas将aqs的status增加  表明线程重入的次数
                        setExclusiveOwnerThread(current);   //设置当前的线程为 执行线程
                        return true;                       //成功的抢到锁了,可以happy的返回,并执行同步语句了
                    }
                }
                else if (current == getExclusiveOwnerThread()) {  //继续判断抢锁的线程是否是执行线程 与上同义不再讲解
                    int nextc = c + acquires; 
                    if (nextc < 0)
                        throw new Error("Maximum lock count exceeded");
                    setState(nextc);
                    return true;
                }
                return false;
            }
    
    //如果上面的方法执行失败,要么是有线程已经持有了锁还没释放,或者是还有其他的线程在此线程之前在队里面排队,于是此线程将自己封装成一个node也加入到队列里面去
    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)) {  //进行一次cas将自己插入到tail后面,如果失败,那么走enq
                    pred.next = node;
                    return node;
                }
            }
            enq(node);
            return node;
        } 
        private Node enq(final Node node) {
            for (;;) {
                Node t = tail;
                if (t == null) { // Must initialize
                    if (compareAndSetHead(new Node()))  //其实就是使用一个死循环直到cas成功的将node加入到tail位置,这是个乐观锁的设计,但是不堵塞别的线程
                        tail = head;
                } else {
                    node.prev = t;
                    if (compareAndSetTail(t, node)) {
                        t.next = node;
                        return t;
                    }
                }
            }
        }
    

    插入成功之后,此node会继续的挣扎一下,看是否可以取得aqs的锁,如下:

    final boolean acquireQueued(final Node node, int arg) {
            boolean failed = true;
            try {
                boolean interrupted = false;
                for (;;) {
                    final Node p = node.predecessor();  //如果当前线程的前任线程是head,那么继续的尝试去获得锁,就是前面的介绍
                    if (p == head && tryAcquire(arg)) {
                        setHead(node);
                        p.next = null; // help GC
                        failed = false;
                        return interrupted;
                    }
                    if (shouldParkAfterFailedAcquire(p, node) &&  //如果失败,那么继续的判断是否将自己park,免得一直的for浪费时间
                        parkAndCheckInterrupt())
                        interrupted = true;
                }
            } finally {
                if (failed)
                    cancelAcquire(node);
            }
        }
    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; //前任节点都在乖乖的park了,自己也park吧
            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); //cas尝试将ws设置为signal
            }
            return false;
        }
    

    等于说如上的方法要么获得锁要么把自己park起来,等待被唤醒那什么时候会被唤醒呢,当然是线程执行unlock的时候了

    public void unlock() {
            sync.release(1);   //仍然是调用同步器来释放锁
        }
    
    public final boolean release(int arg) {
            if (tryRelease(arg)) {  //释放锁,将aqs的state减少
                Node h = head;
                if (h != null && h.waitStatus != 0)   //h的status等于0说明这个已经获得了锁在执行的过程中,不用打扰,减一,退出就可以,这用在同一个线程重入的情况下
                    unparkSuccessor(h); //不然的话准备的唤醒
                return true;
            }
            return false;
        }
    
  • 相关阅读:
    Android设计模式(三)--装饰模式
    kmp算法总结
    SDWebImage源代码解析(二)
    关于C++构造函数一二
    逆向随笔
    iOS中的crash防护(二)KVC造成的crash
    git-osc自己定义控件之:CircleImageView
    java中继承关系学习小结
    openwrt针对RT5350代码下载,配置和编译
    MySQL监控
  • 原文地址:https://www.cnblogs.com/chenyihui/p/15503951.html
Copyright © 2011-2022 走看看