概念

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. 手把手教你实现栈以及C#中Stack源码分析

    定义 栈又名堆栈,是一种操作受限的线性表,仅能在表尾进行插入和删除操作. 它的特点是先进后出,就好比我们往桶里面放盘子,放的时候都是从下往上一个一个放(入栈),取的时候只能从上往下一个一个取(出栈), ...

  2. Nginx系列(2)- 正向代理和反向代理

    Nginx作用 Http代理,反向代理:作为web服务器最常用的功能之一,尤其是反向代理 正向代理是代理客户端,反向代理是代理服务端 正向代理要知道访问服务器的地址,反向代理不需要知道访问服务器的真实 ...

  3. dubbo微服务架构

    架构 节点角色说明 调用关系说明 服务容器负责启动,加载,运行服务提供者. 服务提供者在启动时,向注册中心注册自己提供的服务. 服务消费者在启动时,向注册中心订阅自己所需的服务. 注册中心返回服务提供 ...

  4. selenium下拉选择框处理

    HTML: (一)通过xpath层级标签定位 driver.find_element_by_xpath(".//*[@id='Resolution']/option[2]").cl ...

  5. Java-Ide快速创建getter&setter方法

    1.右键 选择Generate 2.创建 类的equals方法

  6. Redis三种集群模式介绍

    三种集群模式 redis有三种集群模式,其中主从是最常见的模式. Sentinel 哨兵模式是为了弥补主从复制集群中主机宕机后,主备切换的复杂性而演变出来的.哨兵顾名思义,就是用来监控的,主要作用就是 ...

  7. Sentry 监控 - Distributed Tracing 分布式跟踪

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

  8. 【C++ Primer Plus】编程练习答案——第3章

    1 void ch3_1() { 2 using namespace std; 3 unsigned int factor = 12; 4 unsigned int inch, feet; 5 cou ...

  9. FastAPI(59)- 详解使用 OAuth2PasswordBearer + JWT 认证

    JWT JSON Web Tokens 它是一个将 JSON 对象编码为密集且没有空格的长字符串的标准 使用 JWT token 和安全密码 hash 使应用程序真正安全 JWT 小栗子 eyJhbG ...

  10. 安装SpaCy出现报错:requests.exceptions.ConnectionError: HTTPSConnectionPool(host='raw.githubusercontent.com', port=443):

    内含安装步骤及报错解决:https://www.cnblogs.com/xiaolan-Lin/p/13286885.html