概念

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

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

实现原理

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

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

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

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

ReentrantLock 是一个可重入的排他锁。其主要的方法有 lock tryLock unlock。

主要讲公平锁的lock方法。ReentrantLock的lock方法借助了FairSync的lock方法,先放类图,有个简单的了解

是一个重入锁:一个线程获得了锁之后仍然可以反复的加锁,不会出现自己阻塞自己的情况。

代码演示

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

ReentrantLock & AQS的更多相关文章

  1. AQS1---走向稳定态

    AQS的思想(稳定思想):即使确定了正常节点,这个节点也可能下一秒异常,即使找到了正常节点,这个节点可能只是异常status=0/-1的节点,这些都不要紧,都只是在自己旋转‘生命周期’里面和自己所看到 ...

  2. 从synchronize到CSA和

    目录 导论 悲观锁和乐观锁 公平锁和非公平锁 可重入锁和不可重入锁 Synchronized 关键字 实现原理 Java 对象头 Monitor JVM 对 synchronized 的处理 JVM ...

  3. 基于ReentrantLock的AQS的源码分析(独占、非中断、不超时部分)

    刚刚看完了并发实践这本书,算是理论具备了,看到了AQS的介绍,再看看源码,发现要想把并发理解透还是很难得,花了几个小时细分析了一下把可能出现的场景尽可能的往代码中去套,还是有些收获,但是真的很费脑,还 ...

  4. 扒一扒ReentrantLock以及AQS实现原理

    提到JAVA加锁,我们通常会想到synchronized关键字或者是Java Concurrent Util(后面简称JCU)包下面的Lock,今天就来扒一扒Lock是如何实现的,比如我们可以先提出一 ...

  5. Java并发编程总结3——AQS、ReentrantLock、ReentrantReadWriteLock(转)

    本文内容主要总结自<Java并发编程的艺术>第5章——Java中的锁. 一.AQS AbstractQueuedSynchronizer(简称AQS),队列同步器,是用来构建锁或者其他同步 ...

  6. ReentrantLock 以及 AQS 实现原理

    什么是可重入锁?       ReentrantLock是可重入锁,什么是可重入锁呢?可重入锁就是当前持有该锁的线程能够多次获取该锁,无需等待.可重入锁是如何实现的呢?这要从ReentrantLock ...

  7. 并发编程(三):从AQS到CountDownLatch与ReentrantLock

    一.目录      1.AQS简要分析      2.谈CountDownLatch      3.谈ReentrantLock      4.谈消费者与生产者模式(notfiyAll/wait.si ...

  8. ReentrantLock 与 AQS 源码分析

    ReentrantLock 与 AQS 源码分析 1. 基本结构    重入锁 ReetrantLock,JDK 1.5新增的类,作用与synchronized关键字相当,但比synchronized ...

  9. ReentrantLock是如何基于AQS实现的

    ReentrantLock是一个可重入的互斥锁,基于AQS实现,它具有与使用 synchronized 方法和语句相同的一些基本行为和语义,但功能更强大. lock和unlock ReentrantL ...

随机推荐

  1. ESP8266- ESP01之AT固件下载及其他问题

    注意: 本文基于淘宝上买的安信可原装ESP-01,文章中出现的问题在另一片ESP-01S上均未出现.由于在刷固件前没有进行完整测试,因此无法判断是固件导致的还是版本不同造成的. 问题: 1.发热严重. ...

  2. Centos7 搭建sonarQube

       可以支持包括java,C#,C/C++,PL/SQL,Cobol,JavaScrip,Groovy等等二十几种编程语言的代码质量管理与检测: SonarQube官网:https://www.so ...

  3. P4022-[CTSC2012]熟悉的文章【广义SAM,dp,单调队列】

    正题 题目链接:https://www.luogu.com.cn/problem/P4022 题目大意 给出\(m\)个模板串. 然后\(n\)次询问给出一个串\(S\)要求找到一个最大的\(L\)使 ...

  4. pymysql链接时,密码含有特殊符号。

    类如含有@之类的特殊符号,在链接数据库时,需要提前url转码,不然会报密码错误. python3/2分别引用是同样的第三方库,但是引用方式不同 python2 from urllib import q ...

  5. Java AES 加密小试牛刀

    目录 问题出处 解决方法 方法一 方法二 方法三 补充 总结 在java开发过程中,很多时候我们都需要加密数据,例如声音.敏感信息等.我们通常使用的是 MD5加密.SHA加密.DES 加密.AES 加 ...

  6. HBase基础

    Hadoop生态系统 HBase简介 HBase – Hadoop Database,是一个高可靠性.高性能.面向列.可伸缩.实时读写的分布式数据库 利用Hadoop HDFS作为其文件存储系统,利用 ...

  7. 7.JVM调优-方法区,堆,栈调优详解

    通常我们都知道在堆空间新生代Eden区满了,会触发minor GC, 在老年代满了会触发full GC, 触发full GC会导致Stop The World, 那你们知道还有一个区域满了一会触发Fu ...

  8. Server Tools(服务器工具)

    服务器工具 1.发布 # Process: MXD 转 Web 地图 arcpy.MXDToWebMap_server("", "", "" ...

  9. .NET Core 基于Quartz的UI可视化操作组件 GZY.Quartz.MUI 简介

    前言 最近在用Quartz做定时任务.虽然很方便,但是Quartz自己貌似是没有UI界面的..感觉操作起来 就很难受.. 查了一下,貌似有个UI组件 不过看了一下文档..直接给我劝退了..太麻烦了 我 ...

  10. 洛谷4475 巧克力王国(KD-Tree + 维护子树和)

    (嘤嘤嘤 又是一个自闭了一晚上的题) qwq果然不是平面上的点的问题,也可以直接用KDTree打暴力 我们对于巧克力直接建kdtree 维护一个\(mx[i],mn[i]\) 但是有一个非常不友好的事 ...