概念

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. 【PHP数据结构】图的存储结构

    图的概念介绍得差不多了,大家可以消化消化再继续学习后面的内容.如果没有什么问题的话,我们就继续学习接下来的内容.当然,这还不是最麻烦的地方,因为今天我们只是介绍图的存储结构而已. 图的顺序存储结构:邻 ...

  2. SQLSTATE[HY000]: General error: 1366 Incorrect string value: '\xF0\x9F\x90\xA3\xF0\x9F...' for column

    在做微信公众号保存用户数据时出现这种错误,一直不知道是哪里的原因,后来发现那个用户昵称带着一只兔子表情,由于数据库编码限制不能保存数据,所有需要先编码, 用PHP的函数就是base64_encode, ...

  3. linux 安装配置 jdk1.8

    一.查看Linux系统是否有自带的jdk: 1.输入:java -version 2.输入:rpm -qa | grep java 检测jdk的安装包,(注意:rpm命令符没有时记得下载一个输入:ap ...

  4. Css3 3D 旋转动画效果

    需求: 1.一个列表滑动效果为360 旋转 准备: 1.css 基础 2.Css 动画基础animation 3.transform-style概念 4 transform 概念 5 JavaScri ...

  5. Win10删除电脑3D对象等7个文件夹

    把下面几个注册表项依次删除掉 "图片"文件夹:[-HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Expl ...

  6. P5782-[POI2001]和平委员会【2-SAT】

    正题 题目链接:https://www.luogu.com.cn/problem/P5782 题目大意 \(n\)对人,每对之间恰好有一个人出席.\(m\)对仇恨关系表示两个人不能同时出席. 求是否有 ...

  7. Winform 空闲时间(鼠标键盘无操作)

    前言 Winform 在特定情况下,需要判断软件空闲时间(鼠标键盘无操作),然后在做一下一些操作. 实现 做了一个简单的例子,新建一个窗体,然后拖两个控件(Timer控件和label控件) using ...

  8. centos7有关于防火墙的命令

    查看防火墙状态 firewall-cmd --state 开启防火墙 systemctl start firewalld.service 关闭防火墙 systemctl stop firewalld. ...

  9. 沈抚示范区·“华为云杯”2021全国AI大赛圆满落

    摘要:以赛促学,赛教结合!驱动AI产业繁荣发展 本文分享自华为云社区<云聚沈抚 · 智赢未来!沈抚示范区·"华为云杯"2021全国AI大赛圆满落幕>,作者:灰灰哒. 近 ...

  10. ping探测与Nmap扫描

    一.实验目的 学习信息收集的一般步骤 学会使用ping命令 利用Nmap工具进行信息搜集 二.实验环境 系统环境:一台windows7系统.一台XP系统.一台kali系统 软件环境:安装Wiresha ...