• Java 并发编程解析 | 如何正确理解Java领域中的并发锁,我们应该具体掌握到什么程度?


    写在开头

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

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

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

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

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

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

    关健术语

    本文用到的一些关键词语以及常用术语,主要如下:

    • 进程(Process): 计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。
    • 线程(thread): 操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。在Unix System V及SunOS中也被称为轻量进程(Light-Weight Processes),但轻量进程更多指内核线程(Kernel Thread),而把用户线程(User Thread)称为线程。

    基本概述

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

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

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

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

    一.锁的基本理论

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

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

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

    但是,这里就会有一个问题:既然都在使用锁,可是对于锁该去如何定义,似乎都很难给出一个准确的定义? 换而言之,这也许就是我们对于锁只是知道有这个东西,但是一直有云里雾里的基本原因。

    从本质上讲,计算机软件开发领域中的锁是一种协调多个进程 或者多个线程对某一个资源的访问的控制机制,其核心是作用于资源,也作用于着这个定义中提到的进程和线程等。其中:

    • 进程(Process): 操作系统进行资源分配和调度的基本单位,是计算机程序中的实体,其中,程序是指令、数据及其组织形式的描述。
    • 线程(Thread) : 操作系统能够进行运算调度的最小单位,一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

    一般来说,线程主要分为位于系统内核空间的线程称为内核线程(Kernel Thread)和位于应用程序的用户空间的线程被称为用户线程(User Thread)两种,其中:

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

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

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

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

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

    二.锁的基本分类

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

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

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

    需要特别注意的是,在Java领域中,基于JDK层面的锁通过CAS操作解决了并发编程中的原子性问题,而基于Java语法层面实现的锁解决了并发编程中的原子性问题和可见性问题。

    除此之外之外,在Java并发容器中曾用到过一种Segment数组结构来实现的分段锁。

    而从具体到对应的Java线程资源来说,我们按照是否含有某一特性来定义锁,主要可以从如下几个方面来看:

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

    针对于上述描述的各种情况,接下来,我们便来一起详细看看,在Java领域中,这个锁的具体情况。

    三.Java内置锁

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

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

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

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

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

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

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

    1.使用方式

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

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

    • 作用于对象方法上面:
      • 描述对象的方法,表示该对象的方法具有同步性。由于描述的对象的方法,作用范围是在对象(Object),整个对象充当了锁。
      • 需要注意的是,类可以实例化多个对象,这时每一个对象都是一个锁,每个锁的范围相当于是当前对象来说的。
    • 作用于类静态方法上面:
      • 描述类的静态方法,表示该方法具有同步性。由于描述的类静态的方法,作用范围是在类(Class),整个类充当了锁。
      • 需要注意的是,某一个类的本身也是一个对象,JVM使用这个对象作为模板去生成该类的对象时,每个锁的范围相当于是当前类来说的。
    • 作用于对象方法里面:
      • 描述方法内部的某块逻辑,表示该代码块具有同步性。
      • 需要注意的是,一般需要我们指定对象,比如synchronized(this){xxx}是指当前对象的,也可以创建一个对象来作为锁。
    • 作用于类静态方法里面:
      • 描述静态方法内部的某块逻辑,表示该代码块具有同步性。
      • 需要注意的是,一般需要我们指定锁对象,比如synchronized(this){xxx}是指当前类class作为锁对象的,也可以创建一个对象来作为锁。

    一般当我们在编写代码的过程中,如果按照上述方式声明时,被synchronized关键字声明的代码会比普通代码在编译之后,使用javap -c xxx.class 查看字节码,就会发现多两个monitorenter和

  • 相关阅读:
    Hbase安装和使用
    [最长回文字符串]manacher马拉车
    Python3----------抽象(多态、封装、继承等)
    高新技术企业认定工作指导
    Spring使用注解开发
    力扣(103、236、104)
    一文区分路由策略和策略路由!
    PTA题目 计算符号函数的值
    kali没有wlan0
    Linux系统LVM逻辑卷
  • 原文地址:https://blog.csdn.net/zhaohuodian/article/details/126744372