一个合格的Java选手必须要掌握的并发锁知识

矫情吗;* 2024-05-06 21:29 178阅读 0赞

对于Java领域中的锁,其实从接触Java至今,我相信每一位Java Developer都会有这样的一个感觉?不论是Java对锁的实现还是应用,真的是一种“群英荟萃”,而且每一种锁都有点各有各的驴,各有各的本,各不相同。

在很多情况下,以及在各种锁的应用场景里,各式各样的定义,难免会让我们觉得无所适从,很难清楚该如何对这些锁做到得心应手?

在并发编程色世界中,一般情况下,我们只需了解其是如何使用锁之后就已经满足我们大部分的需求,但是作为一名对技术研究有执念和热情的人来说,深入探究和分析才是对技术的探秘之乐趣。

作为一名Java Developer来说,深入探究和分析和正确了解和掌握这些锁的机制和原理,需要我们带着一些实际问题,通过对其探究分析和加上实际应用分析,才能真正意义上理解和掌握。

一般来说,针对于不同场景提供的锁,都用于解决什么问题?不论是从实现方式上,还是从使用场景上,都可以应对这些锁的特点,我们又该如何认识和理解?

接下来,今天我们就一起来盘一盘,Java领域中那些并发锁,盘点一下相关的锁,从设计基本思想和设计实现,以及应用分析等方面来总体分析探讨一下。

4ed5ef9101a54eb39f774fe6a5492a03.png

在Java领域中,单纯从Java对其实现的方式上来看,我们大体上可以将其分为基于Java语法层面(关键词)实现的锁和基于JDK层面实现的锁。

基于这两个基本点,可以作为我们对于Java领域中的锁的一个基础认识,这对于我们认识和了解Java领域中的锁指导一个参考方向。

一般来说,锁是并发编程中最基础和最常用的一项技术,而且在Java的内部JDK中其使用也是非常地广泛。

接下来,我们便一起探究和认识一下Java领域中的各种各样的锁。

一.锁的基本理论

锁的基本理论主要是指从锁的基本定义和基本特点以及基本意义去分析的一般模型理论,是一套帮助我们认识和了解锁的简单的思维方法论。

Picture-Content

一般在了解一个事物之前,我们都会按照基本定义,基本特点以及基本意义去看待这个事物。在计算机的世界里,锁本身也和我们实际生活一样,也是一个比较普遍且应用场景繁多的一种事物。

比如,在操作系统中,也定义了各种各样的锁;在数据库系统中也出现了锁。甚至,在CPU处理器架构中都会看见锁的身影。

也就是我们一般说的Java线程等均属于用户线程,而内核线程主要是操作系统封装的函数库以及API等。

而且最关健的就是,我们平日里所提到Java线程和JVM都是位于用户空间之中,从Java层到操作系统系统的线程调度顺序来看,一般流程是:java.lang.Thread(Target Thread)->Java Thread->OSThread->pthread->Kernel Thread。

简单来说,在Java领域中,锁是用于控制多个线程访问共享资源的工具。一般,锁提供对共享资源的独立访问:一次只有一个线程可以获取锁,所有对共享资源的访问都需要先获取锁。但是,某些锁可以并发访问共享资源。

对于并发访问共享资源来说,主要是依据现在大多数操作系统的线程的调度方式是抢占式调度,因此加锁是为了维护数据的一致性和完整性,其实就是数据的安全性。

综上所述,我们便可以得到一个关于锁的基本概念模型,接下来我们便来一一盘点以下主要有哪些锁。

二.锁的基本分类

在Java领域中,我们可以将锁大致分为基于Java语法层面(关键词)实现的锁和基于JDK层面实现的锁。

e496de2cbe5b4488b28db1abaaebc3c9.png

单纯从Java对其实现的方式上来看,我们大体上可以将其分为基于Java语法层面(关键词)实现的锁和基于JDK层面实现的锁。其中:

  • Java内置锁:基于Java语法层面(关键词)实现的锁,主要是根据Java语义来实现,最典型的应用就是synchronized。
  • Java显式锁:基于JDK层面实现的锁,主要是根据基于Lock接口和ReadWriteLock接口,以及统一的AQS基础同步器等来实现,最典型的有ReentrantLock。
  • 从加锁对象角度方面上来看,线程要不要锁住同步资源 ? 如果是需要加锁,锁住同步资源的情况下,一般称其为悲观锁;否则,如果是不需要加锁,且不用锁住同步资源的情况就属于为乐观锁。
  • 从获取锁的处理方式上来看,假设锁住同步资源,其对该线程是否进入睡眠状态或者阻塞状态?如果会进入睡眠状态或者阻塞状态,一般称其为互斥锁,否则,不会进入睡眠状态或者阻塞状态属于一种非阻塞锁,即就是自旋锁。
  • 从锁的变化状态方面来看,多个线程在竞争资源的流程细节上是否有差别?
  • 首先,对于不会锁住资源,多个线程只有一个线程能修改资源成功,其他线程会依据实际情况进行重试,即就是不存在竞争的情况,一般属于无锁。
  • 其次,对于同一个线程执行同步资源会自动获取锁资源,一般属于偏向锁。
  • 然而,对于多线程竞争同步资源时,没有获取到锁资源的线程会自旋等待锁释放,一般属于轻量级锁。
  • 最后,对于多线程竞争同步资源时,没有获取到锁资源的线程会阻塞等待唤醒,一般属于重量级锁。
  • 从锁竞争时公平性上来看,多个线程在竞争资源时是否需要排队等待?如果是需要排队等待的情况,一般属于公平锁;否则,先插队,然后再尝试排队的情况属于非公平锁。
  • 从获取锁的操作频率次数来看,一个线程中的多个流程是否可以获取同一把锁?如果是可以多次进行加锁操作的情况,一般属于可重入锁,否则,可以多次进行加锁操作的情况属于非可重入锁。
  • 从获取锁的占有方式上来看,多个线程能不能共享一把锁?如果是可以共享锁资源的情况,一般属于共享锁;否则,独占锁资源的情况属于排他锁。

三.Java内置锁

在Java领域中,Java内置锁主要是指基于Java语法层面(关键词)实现的锁。

在Java领域中,我们把基于Java语法层面(关键词)实现的锁称为内置锁,比如synchronized 关键字。

对于synchronized 关键字的解释,最直接的就是Java语言中为开发人员提供的同步工具,可以看作是Java中的一种“语法糖”。主要宗旨在于解决多线程并发执行过程中数据同步的问题。

不像其他的编程语言(C++),在处理同步问题时都需要自己进行锁处理,主要特点就是简单,直接声明即可。

在 Java 程序中,利用 synchronized 关键字来对程序进行加锁,其实现同步的语义是互斥锁。既可以用来声明一个 synchronized 代码块,也可以直接标记静态方法或者实例方法。

其中,对于互斥的概念来说,在数学范畴来讲,是一个数学名词,表示和描述的是事件A与事件B在任何一次试验中都不会同时发生,则称事件A与事件B互斥。

因此,对于互斥锁可以理解为: 对于某一个锁来说,任意时刻只能有一个线程获得该锁,对于其他线程想获取锁的时候就得等待或者被阻塞。

1.使用方式

在Java领域中,synchronized关键字互斥锁主要有作用于对象方法上面,作用于类静态方法上面,作用于对象方法里面,作用于类静态方法里面等4种方式。

4c383b9da5824c66887fcb53031f01a9.png

在Java领域中,synchronized关键字从使用方式来看,主要可以分为:

  • 作用于对象方法上面:

    • 描述对象的方法,表示该对象的方法具有同步性。由于描述的对象的方法,作用范围是在对象(Object),整个对象充当了锁。
    • 需要注意的是,类可以实例化多个对象,这时每一个对象都是一个锁,每个锁的范围相当于是当前对象来说的。
  • 作用于类静态方法上面:

    • 描述类的静态方法,表示该方法具有同步性。由于描述的类静态的方法,作用范围是在类(Class),整个类充当了锁。
    • 需要注意的是,某一个类的本身也是一个对象,JVM使用这个对象作为模板去生成该类的对象时,每个锁的范围相当于是当前类来说的。
  • 作用于对象方法里面:

    • 描述方法内部的某块逻辑,表示该代码块具有同步性。
    • 需要注意的是,一般需要我们指定对象,比如synchronized(this){xxx}是指当前对象的,也可以创建一个对象来作为锁。
  • 作用于类静态方法里面:

    • 描述静态方法内部的某块逻辑,表示该代码块具有同步性。
    • 需要注意的是,一般需要我们指定锁对象,比如synchronized(this){xxx}是指当前类class作为锁对象的,也可以创建一个对象来作为锁。

2.基本思想

在Java领域中,synchronized关键字互斥锁主要基于一个阻塞队列和等待对列,类似于一种“等待-通知”的工作机制来实现。

33b9e26665e94bd6abe842ae2ba6f6c9.png

一般情况下,“等待 - 通知”的工作机制的要求是线程首先获取互斥锁,其中:

  • 当线程要求的条件不满足时,释放互斥锁,进入等待状态。
  • 当要求的条件满足时,通知等待的线程,重新获取互斥锁。

在Java领域中, Java 语言内置的 synchronized 配合java.lang.Object类定义的 wait()、notify()、notifyAll() 这三个方法就能轻松实现等待 - 通知机制,其中:

  • wait: 表示持有对象锁的线程A准备释放对象锁权限,释放cpu资源并进入等待。
  • notify:表示持有对象锁的线程A准备释放对象锁权限,通知jvm唤醒某个竞争该对象锁的线程X。线程A synchronized 代码作用域结束后,线程X直接获得对象锁权限,其他竞争线程继续等待(即使线程X同步完毕,释放对象锁,其他竞争线程仍然等待,直至有新的notify ,notifyAll被调用)。
  • 表示持有对象锁的线程A准备释放对象锁权限,通知jvm唤醒所有竞争该对象锁的线程,线程A synchronized 代码作用域结束后,jvm通过算法将对象锁权限指派给某个线程X,所有被唤醒的线程不再等待。线程X synchronized代码作用域结束后,之前所有被唤醒的线程都有可能获得该对象锁权限,这个由JVM算法决定。

  • 当一个线程进入临界区后,由于某些条件不满足,需要进入等待状态,Java 对象的 wait() 方法就能够满足这种需求。

  • 当调用 wait() 方法后,当前线程就会被阻塞,并且进入到右边的等待队列中,这个等待队列也是互斥锁的等待队列。
  • 线程在进入等待队列的同时,会释放持有的互斥锁,线程释放锁后,其他线程就有机会获得锁,并进入临界区了。

3.基本实现

在Java领域中,synchronized关键字互斥锁主要基于Java HotSpot(TM) VM 虚拟机通过Monitor(监视器)来实现monitorenter和monitorexit指令的。

在Java HotSpot(TM) VM 虚拟机中,主要是通过Monitor(监视器)来实现monitorenter和monitorexit指令的,Monitor(监视器)一般包括一个阻塞队列和一个等待队列,其中:

  • 阻塞队列 : 用来保存锁竞争失败的线程,它们处于阻塞状态。
  • 等待队列:用来保持synchronized关键字块中的调用 wait()方法后放置的队列。

其中,需要注意的是,当调用 wait()方法后会释放锁并通知阻塞队列。

一般来说,当Java字节码(class)被托管到Java HotSpot(TM) VM 虚拟机后,Monitor(监视器)就被采用ObjectMonitor接管,其中:

  • 每⼀个对象都有⼀个属于⾃⼰的monitor,其次如果线程未获取到singal (许可),则线程阻塞。
  • monitor相当于⼀个对象的钥匙,只有拿到此对象的monitor,才能访问该对象的同步代码。 相反未获得monitor的只能阻塞来等待持有monitor的线程释放monitor。

对于monitorenter指令来说,其中:

  • ⼀个对象都会和⼀个监视器monitor关联。监视器被占⽤时会被锁住,其他线程⽆法来获取该monitor。当JVM执⾏某个线程的某个⽅法内部的monitorenter时,它会尝试去获取当前对象对应的monitor的所有权。
  • synchronized的锁对象会关联⼀个monitor,这个monitor不是我们主动创建的,是JVM的线程执⾏到这个同步代码块,发现锁对象没有monitor就会创建monitor,monitor内部有两个重要的成员变量owner:拥有这把锁的线程,recursions会记录线程拥有锁的次数,当⼀个线程拥有monitor后其他线程只能等待。

主要工作流程如下:

  • 若monior的进⼊数为0,线程可以进⼊monitor,进入后将monitor的进数置为1。当前线程成为monitor的owner(所有者) 。
  • 若线程已拥有monitor的所有权,允许它重⼊monitor,则进⼊monitor的进⼊数再加1。
  • 若其他线程已经占有monitor的所有权,那么当前尝试获取monitor的所有权的线程会被阻塞。直到monitor的进⼊数变为0,才能重新尝试获取monitor的所有权。

对于monitorexit指令来说,其中:

  • 能执⾏monitorexit指令的线程,⼀定是拥有当前对象的monitor的所有权的线程。
  • 执⾏monitorexit时会将monitor的进⼊数减1。当monitor的进⼊数减为0时,当前线程退出monitor,不再拥有monitor的所有权,此时其他被这个monitor阻塞的线程可以尝试去获取这个monitor的所有权。

主要工作流程如下:

  • monitorexit,指令出现了两次,第1次为同步正常退出释放锁;第2次为发生异常退出释放锁。
  • monitorexit释放锁monitorexit插⼊在⽅法结束处和异常处,JVM保证每个monitorenter必须有对应的monitorexit。

综上所述,monitorenter和monitorexit两个指令的执行是JVM通过调用操作系统的互斥原语mutex来实现。被阻塞的线程会被挂起、等待重新调度,会导致”用户态和内核态”两个态之间来回切换,对性能有较大影响。

4.具体实现

在Java领域中,JVM中每个对象都会有一个监视器,监视器和对象一起创建、销毁。监视器相当于一个用来监视这些线程进入的特殊房间,其义务是保证(同一时间)只有一个线程可以访问被保护的临界区代码块。

2c065fe8ef98474dad211ecd9db7c993.png

本质上,监视器是一种同步工具,也可以说是一种同步机制,主要特点是:

  • 同步:监视器所保护的临界区代码是互斥地执行的。一个监视器是一个运行许可,任一线程进入临界区代码都需要获得这个许可,离开时把许可归还。
  • 协作:监视器提供Signal机制,允许正持有许可的线程暂时放弃许可进入阻塞等待状态,等待其他线程发送Signal去唤醒;其他拥有许可的线程可以发送Signal,唤醒正在阻塞等待的线程,让它可以重新获得许可并启动执行。

四.Java显式锁

在Java领域中,Java显式锁主要是指基于JDK层面实现的锁。

在Java领域中,基于JDK层面实现的锁都存在于java.util.concurrent.locks包下面,大致可以分为:

  • 基于Lock接口实现的锁
  • 基于ReadWriteLock接口实现的锁
  • 基于AQS基础同步器实现的锁
  • 基于自定义API操作实现的锁

1.JDK源码

在Java领域中,Java显式锁从JDK源码表现出来的锁大致可以分为基于Lock接口实现的锁,基于ReadWriteLock接口实现的锁,基于AQS基础同步器实现的锁,以及基于自定义API操作实现的锁等。

在Java领域中,基于JDK源码层面体现出来的锁,主要分为如下几种:

  • 基于Lock接口实现的锁:基于Lock接口实现的锁主要有ReentrantLock。
  • 基于ReadWriteLock接口实现的锁:基于ReadWriteLock接口实现的锁主要有ReentrantReadWriteLock。
  • 基于AQS基础同步器实现的锁:基于AQS基础同步器实现的锁主要有CountDownLatch,Semaphore,ReentrantLock,ReentrantReadWriteLock等。
  • 基于自定义API操作实现的锁: 不依赖于上述三种方式来直接封装实现的锁,最典型是JDK1.8版本中提供的StampedLock。

从一定程度上说,Java显式锁都是基于AQS基础同步器实现的锁,其中JDK1.8版本中提供的StampedLock是是对ReentrantReadWriteLock读写锁的一种改进。

2.基本思想

在Java领域中,Java显式锁的基本思想来源于JDK并发包JUC的作者Doug Lea,发表的论文为java.util.concurrent Synchronizer Framework 。

在Java领域中,同步器是指专门为多线程并发而设计的同步机制,在这种机制下,多线程并发执行时线程之间通过某种共享状态实现同步,只有满足某种条件时线程才能执行。

在不同的应用场景中,对同步器的需求也不同,JDK将各种同步器的相同部分抽象封装成一个统一的基础同步器,然后基于这个同步器为模板,通过继承的方式来实现不同的同步器,即就是我们说的统一的基础AQS同步器。

在JDK的并发包java.util.concurrent.下面,提供了各种同步工具,其中大部分同步工具都基于AbstractQueuedSynchronizer类实现,即就是AQS同步器,为不同场景提供了实现锁以及同步机制的基础框架,为同步状态的原子性管理,线程阻塞与解除以及排队管理提供一种通用的机制。

3.基本分类

在Java领域中,Java显式锁的基本分类大致可以分为可重入锁和不可重入锁、悲观锁和乐观锁、公平锁和非公平锁、共享锁和独占锁、可中断锁和不可中断锁。

显式锁有很多种,从不同的角度来看,显式锁大概有以下几种分类:可重入锁和不可重入锁、悲观锁和乐观锁、公平锁和非公平锁、共享锁和独占锁、可中断锁和不可中断锁。

从同一个线程是否可以重复占有同一个锁对象的角度来分,显式锁可以分为可重入锁与不可重入锁。其中:

  • 可重入锁也叫作递归锁,指的是一个线程可以多次抢占同一个锁,JUC的ReentrantLock类是可重入锁的一个标准实现类。
  • 不可重入锁与可重入锁相反,指的是一个线程只能抢占一次同一个锁。

从线程进入临界区前是否锁住同步资源的角度来分,显式锁可以分为悲观锁和乐观锁。其中:

  • 悲观锁:就是悲观思想,每次进入临界区操作数据的时候都认为别的线程会修改,所以线程每次在读写数据时都会上锁,锁住同步资源,这样其他线程需要读写这个数据时就会阻塞,一直等到拿到锁。总体来说,悲观锁适用于写多读少的场景,遇到高并发写时性能高。Java的synchronized重量级锁是一种悲观锁。
  • 乐观锁是一种乐观思想,每次去拿数据的时候都认为别的线程不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,采取在写时先读出当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样就更新),如果失败就要重复读-比较-写的操作。总体来说,乐观锁适用于读多写少的场景,遇到高并发写时性能低。Java中的乐观锁基本都是通过CAS自旋操作实现的。CAS是一种更新原子操作,比较当前值跟传入值是否一样,是则更新,不是则失败。在争用激烈的场景下,CAS自旋会出现大量的空自旋,会导致乐观锁性能大大降低。Java的synchronized轻量级锁是一种乐观锁。另外,JUC中基于抽
    象队列同步器(AQS)实现的显式锁(如ReentrantLock)都是乐观锁。

从抢占资源的公平性来说,显示锁可以分为公平锁和非公平锁,其中:

  • 公平锁是指不同的线程抢占锁的机会是公平的、平等的,从抢占时间上来说,先对锁进行抢占的线程一定被先满足,抢锁成功的次序体现为FIFO(先进先出)顺序。简单来说,公平锁就是保障各个线程获取锁都是按照顺序来的,先到的线程先获取锁。
  • 非公平锁是指不同的线程抢占锁的机会是非公平的、不平等的,从抢占时间上来说,先对锁进行抢占的线程不一定被先满足,抢锁成功的次序不会体现为FIFO(先进先出)顺序。

默认情况下,ReentrantLock实例是非公平锁,但是,如果在实例构造时传入了参数true,所得到的锁就是公平锁。另外,ReentrantLock的tryLock()方法是一个特例,一旦有线程释放了锁,正在tryLock的线程就能优先取到锁,即使已经有其他线程在等待队列中。

从在抢锁过程中能通过某些方法终止抢占过程角度来看,显式锁可以分为可中断锁和不可中断锁,其中:

  • 可中断锁:什么是可中断锁?如果某一线程A正占有锁在执行临界区代码,另一线程B正在阻塞式抢占锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己的阻塞等待,
  • 不可中断锁: 什么是不可中断锁?一旦这个锁被其他线程占有,如果自己还想抢占,只能选择等待或者阻塞,直到别的线程释放这个锁,如果别的线程永远不释放锁,那么自己只能永远等下去,并且没有办法终止等
    待或阻塞。

简单来说,在抢锁过程中能通过某些方法终止抢占过程,这就是可中断锁,否则就是不可中断锁。

Java的synchronized内置锁就是一个不可中断锁,而JUC的显式锁(如ReentrantLock)是一个可中断锁。

  • 独占锁指的是每次只有一个线程能持有的锁。独占锁是一种悲观保守的加锁策略,它不必要地限制了读/读竞争,如果某个只读线程获取锁,那么其他的读线程都只能等待,这种情况下就限制了读操作的
    并发性,因为读操作并不会影响数据的一致性。JUC的ReentrantLock类是一个标准的独占锁实现类。
  • 共享锁允许多个线程同时获取锁,容许线程并发进入临界区。与独占锁不同,共享锁是一种乐观锁,它放宽了加锁策略,并不限制读/读竞争,允许多个执行读操作的线程同时访问共享资源。JUC的ReentrantReadWriteLock(读写锁)类是一个共享锁实现类。使用该读写锁时,读操作可以有很多线程一起读,但是写操作只能有一个线程去写,而且在写入的时候,别的线程也不能进行读的操作。用ReentrantLock锁替代ReentrantReadWriteLock锁虽然可以保证线程安全,但是也会浪费一部分资源,因为多个读操作并没有线程安全问题,所以在读的地方使用读锁,在写的地方使用写锁,可以提高程序执行效率。

综上所述,对于Java显式锁的基本分类,一般情况下我们都可按照这样的方式去分析。

  • await()方法:唤醒一个等待队列
  • awaitUninterruptibly() 方法:唤醒一个不可中断的等待队列
  • awaitNanos(long nanosTimeout) 方法:唤醒一个带超时的等待队列
  • await(long time, TimeUnit unit)方法:唤醒一个带超时的等待队列
  • awaitUntil(Date deadline) 方法:唤醒一个带超时的等待队列
  • signal()方法:随机地通知等待队列中的一个线程
  • signalAll()方法:通知等待队列中的所有线程

同时,JUC提供的一个线程阻塞与唤醒的工具类(java.util.concurrent.locks.LockSupport),该工具类可以让线程在任意位置阻塞和唤醒,其所有的方法都是静态方法。

  • void park()方法: 对当前线程执行阻塞操作,直到获取许可后才解除阻塞
  • void parkNanos(long nanos)方法:对当前线程执行阻塞操作,直到获取许可后才解除阻塞,最大等待时间有参数传入指定,一旦超过最大时间也会解除阻塞
  • void parkNanos(Object blocker, long nanos)方法:对当前线程执行阻塞操作,直到获取许可后才解除阻塞,最大等待时间有参数传入指定,一旦超过最大时间也会解除阻塞,需要指定阻塞对象
  • void parkUntil(long deadline)方法:对当前线程执行阻塞操作,直到获取许可后才解除阻塞最大等待时间为指定最后期限
  • void parkUntil(Object blocker, long deadline)方法: 对当前线程执行阻塞操作,直到获取许可后才解除阻塞最大等待时间为指定最后期限,需要指定阻塞对象
  • void unpark(Thread thread)方法: 将指定线程设置为可用

相比之下,Java显式锁比Java内置锁的锁粒度更细腻,可以设置超时机制,更加可控,使用起来更加灵活。

写在最后

对于Java 领域中锁,我们一般可以从如下两个方面去认识,其中:

  • Java内置锁:基于Java语法层面(关键词)实现的锁,主要是根据Java语义来实现,最典型的应用就是synchronized。
  • Java显式锁:基于JDK层面实现的锁,主要是根据基于Lock接口和ReadWriteLock接口,以及统一的AQS基础同步器等来实现,最典型的有ReentrantLock。

对于Java内置锁来说:

  • 使用方式:synchronized关键字互斥锁主要有作用于对象方法上面,作用于类静态方法上面,作用于对象方法里面,作用于类静态方法里面等4种方式。
  • 基本思想:synchronized关键字互斥锁主要基于一个阻塞队列和等待对列,类似于一种“等待-通知”的工作机制来实现。
  • 基本实现:synchronized关键字互斥锁主要基于Java HotSpot(TM) VM 虚拟机通过Monitor(监视器)来实现monitorenter和monitorexit指令的。
  • 具体实现:JVM中每个对象都会有一个监视器,监视器和对象一起创建、销毁。监视器相当于一个用来监视这些线程进入的特殊房间,其义务是保证(同一时间)只有一个线程可以访问被保护的临界区代码块。
  • 基本分类: synchronized关键字互斥锁主要中内置锁一共有4种状态:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这些状态随着竞争情况逐渐升级,其中升级顺序为:无锁->偏向锁->轻量级锁状态->重量级,其顺序不可逆转。
  • 应用分析: synchronized关键字互斥锁主要中内置锁使用简单,但是锁的粒度比较大,无法支持超时等。

对于Java显式锁来说:

  • 使用方式:Java显式锁从JDK源码表现出来的锁大致可以分为基于Lock接口实现的锁,基于ReadWriteLock接口实现的锁,基于AQS基础同步器实现的锁,以及基于自定义API操作实现的锁等。
  • 基本思想:Java显式锁的基本思想来源于JDK并发包JUC的作者Doug Lea,发表的论文为java.util.concurrent Synchronizer Framework 。
  • 基本实现:Java显式锁从一定程度上说,Java显式锁都是基于AQS基础同步器实现的锁。
  • 具体实现:Java显式锁中基于AQS基础同步器实现的锁主要都是采用自旋锁(CLH锁)+CAS操作来实现。
  • 基本分类:Java显式锁的基本分类大致可以分为可重入锁和不可重入锁、悲观锁和乐观锁、公平锁和非公平锁、共享锁和独占锁、可中断锁和不可中断锁。
  • 应用分析: Java显式锁的Java显式锁比Java内置锁的锁粒度更细腻,可以设置超时机制,更加可控,使用起来更加灵活。

最后,技术研究之路任重而道远,愿我们熬的每一个通宵,都撑得起我们想在这条路上走下去的勇气,未来仍然可期!

发表评论

表情:
评论列表 (有 0 条评论,178人围观)

还没有评论,来说两句吧...

相关阅读

    相关 滤波器必须掌握关键知识

    一个理想滤波器应该是在通频带内具有均匀且稳定的增益,对信号的其余频带则具有无穷大的衰减。然后,各种实际的频率响应曲线从阻带或从通带到阻带总有一个逐渐过渡的过程,距离理想情况有一

    相关 必须掌握HTTPS

    一、前言 一开始去真正接触HTTPS是由于在上线小程序的时候,小程序官方限定接口必须需是https协议,后面就去弄了腾讯云的云服务器,还有免费的https证书等,跟着官方