概念

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. js设计模式之发布订阅模式

    1. 定义 发布-订阅模式其实是一种对象间一对多的依赖关系,当一个对象的状态发送改变时,所有依赖于它的对象都将得到状态改变的通知. 订阅者(Subscriber)把自己想订阅的事件注册(Subscri ...

  2. (一)es 概述与安装

    一.基本概念介绍 1. es 核心术语 核心概念 ES -> 数据库 索引index -> 表 文档 document -> 行(记录) 字段 fields -> 列 早期版本 ...

  3. PolarDB PostgreSQL 架构原理解读

    背景 PolarDB PostgreSQL(以下简称PolarDB)是一款阿里云自主研发的企业级数据库产品,采用计算存储分离架构,兼容PostgreSQL与Oracle.PolarDB 的存储与计算能 ...

  4. 卧槽,redis分布式如果用不好,坑真多

    前言 在分布式系统中,由于redis分布式锁相对于更简单和高效,成为了分布式锁的首先,被我们用到了很多实际业务场景当中. 但不是说用了redis分布式锁,就可以高枕无忧了,如果没有用好或者用对,也会引 ...

  5. Sentry 监控 - Alerts 告警

    系列 1 分钟快速使用 Docker 上手最新版 Sentry-CLI - 创建版本 快速使用 Docker 上手 Sentry-CLI - 30 秒上手 Source Maps Sentry For ...

  6. Dapr + .NET Core实战(八)服务监测

    服务监测 分布式服务性能指标,链路追踪,运行状况,日志记录都很重要,我们日常开发中为了实现这些功能需要集成很多功能,替换监控组件时成本也很高. Dapr 可观测性模块将服务监测与应用程序分离.它自动捕 ...

  7. SpringBoot 添加本地 jar 文件

    前言 有时候我们在项目中,会用到一些本地 jar 包文件,比如隔壁公司自己打包的: 此时无法从maven远程仓库拉取: 那么我们可以考虑把 jar 文件安装到本地 maven 库中,然后再添加依赖. ...

  8. 三千字介绍Redis主从+哨兵+集群

    一.Redis持久化策略 1.RDB 每隔几分钟或者一段时间会将redis内存中的数据全量的写入到一个文件中去. 优点: 因为他是每隔一段时间的全量备份,代表了每个时间段的数据.所以适合做冷备份. R ...

  9. iptables配置操作

    1.防火墙添加配置规则(正向) vim /etc/sysconfig/iptables 指定服务器的ip访问内访问某个端口 -A INPUT -p tcp -m iprange --src-range ...

  10. Spatial Analyst 工具-数学分析

    数学分析 # Process: Abs arcpy.gp.Abs_sa("", 输出栅格) # Process: Exp arcpy.gp.Exp_sa("", ...