概念

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中的国际化日历类

    在 PHP 的国际化组件中,还有一个我们并不是很常用的跟日期相关的操作类,它就是日历操作类.说是日历,其实大部分还是对日期时间的操作,一般也是主要用于日期的格式化和比较之类的.但是通常我们直接使用 d ...

  2. Nacos注册中心和配置中心流程原理

    一.Nacos注册中心 1.服务启动后---->服务注册原理 springCloud集成Nacos实现原理: 服务启动时,在spring-cloud-commons包下 spring.facto ...

  3. Docker系列(17)- MySQL同步数据

    #获取镜像 [root@localhost ~]# docker pull mysql:5.7 #启动容器,需要做数据挂载!安装启动mysql,需要配置密码的,这是注意点! #官方安装文档:docke ...

  4. Jmeter线程组设置

    添加线程组:右键测试计划->添加->Threads->线程组 左侧树形标签栏中,显示标签信息. 选中线程组,右侧内容栏中显示线程组的相关信息. 名称:可以给线程组设置一个个性化的命名 ...

  5. MYSQL分页 limit 太慢优化

    limit分页原理 当我们翻到最后几页时,查询的sql通常是:select * from table where column=xxx order by xxx limit 1000000,20.查询 ...

  6. hibernate 初学

    1. hibernate的基本操作 执行流程: 执行流程细节:基本的配置文件         可以与mybatis进行对比着记                  hibernate 的主键生成策略   ...

  7. 深入浅出WPF-09.Command(命令)

    命令 1)命令系统的基本元素 命令(Command),WPF的命令实际上就是实现了ICommand接口的类,平时使用最多的是RoutedCommand类 命令源(Command Source),即命令 ...

  8. NOIP 模拟八 考试总结

    T1星际旅行 给出n个点,m条边,求满足一条路径使得m-2条边经过2次,2条边经过1次的方案数.并且题目中给出有自环. 看到题面我以为是个计数DP,可能是计数题做多了吧哈哈.其实仔细朝图的方向想一想就 ...

  9. Windows 11正式版来了,下载、安装教程、一起奉上!

    Windows 11正式版已经发布了,今天给大家更新一波Win11系统的安装方法,其实和Win10基本一样,有多种方法.   安装Win11前请先查看电脑是否支持Win11系统,先用微软自家的PC H ...

  10. windows下编译caffe出现错误 C4996: 'std::_Copy_impl': Function call with parameters that may be unsafe?

    解决方案来自http://blog.csdn.net/u012556077/article/details/50353818