最近我从cnaaa.com购买了云服务器。
在 Java 领域中,我们可以将锁大致分为基于 Java 语法层面 (关键词) 实现的锁和基于 JDK 层面实现的锁。
在 Java 领域中,尤其是在并发编程领域,对于多线程并发执行一直有两大核心问题:同步和互斥。其中:
- 互斥 (Mutual Exclusion):一个公共资源同一时刻只能被一个进程或线程使用,多个进程或线程不能同时使用公共资源。即就是同一时刻只允许一个线程访问共享资源的问题。
- 同步 (Synchronization):两个或两个以上的进程或线程在运行过程中协同步调,按预定的先后次序运行。即就是线程之间如何通信、协作的问题。
针对对于这两大核心问题,利用管程是能够解决和实现的,因此可以说,管程是并发编程的万能钥匙。 虽然,Java 在基于语法层面 (synchronized 关键字) 实现了对管程技术,但是从使用方式和性能上来说,内置锁 (synchronized 关键字) 的粒度相对过大,不支持超时和中断等问题。 为了弥补这些问题,从 JDK 层面对其 “重复造轮子”,在 JDK 内部对其重新设计和定义,甚至实现了新的特性。 在 Java 领域中,从 JDK 源码分析来看,基于 JDK 层面实现的锁大致主要可以分为以下 4 种方式:
- 基于 Lock 接口实现的锁:JDK1.5 版本提供的 ReentrantLock 类
- 基于 ReadWriteLock 接口实现的锁:JDK1.5 版本提供的 ReentrantReadWriteLock 类
- 基于 AQS 基础同步器实现的锁:JDK1.5 版本提供的并发相关的同步器 Semaphore,CyclicBarrier 以及 CountDownLatch 等�
- 基于自定义 API 操作实现的锁:JDK1.8 版本中提供的 StampedLock 类
从阅读源码不难发现,在 Java SDK 并发包主要通过 AbstractQueuedSynchronizer (AQS) 实现多线程同步机制的封装与定义,而通过 Lock 和 Condition 两个接口来实现管程,其中 Lock 用于解决互斥问题,Condition 用于解决同步问题。
一.AQS 基础同步器基本理论在 Java 领域中,同步器是专门为多线程并发设计的同步机制,主要是多线程并发执行时线程之间通过某种共享状态来实现同步,只有当状态满足这种条件时线程才往下执行的一种同步机制。
一个标准的 AQS 同步器主要有同步状态机制,等待队列,条件队列,独占模式,共享模式等五大核心要素组成。 在 Java 领域中,JDK 的 JUC (java.util.concurrent.) 包中提供了各种并发工具,但是大部分同步工具的实现基于 AbstractQueuedSynchronizer 类实现,其内部结构主要如下:
- 同步状态机制 (Synchronization Status):主要用于实现锁 (Lock) 机制,是指同步状态,其要求对于状态的更新必须原子性的
- 等待队列 (Wait Queue):主要用于存放等待线程获取到的锁资源,并且把线程维护到一个 Node (节点) 里面和维护一个非阻塞的 CHL Node FIFO (先进先出) 队列,主要是采用自旋锁 + CAS 操作来保证节点插入和移除的原子性操作。
- 条件队列 (Condition Queue):用于实现锁的条件机制,一般主要是指替换 “等待 - 通知” 工作机制,主要是通过 ConditionObject 对象实现 Condition 接口提供的方法实现。
- 独占模式 (Exclusive Mode):主要用于实现独占锁,主要是基于静态内部类 Node 的常量标志 EXCLUSIVE 来标识该节点是独占模式
- 共享模式 (Shared Mode):主要用于实现共享锁,主要是基于静态内部类 Node 的常量标志 SHARED 来标识该节点是共享模式
我们可以得到一个比较通用的并发同步工具基础模型,大致包含如下几个内容,其中:
- 条件变量 (Conditional Variable): 利用线程间共享的变量进行同步的一种工作机制
- 共享变量 ((Shared Variable)):一般指对象实体对象的成员变量和属性
- 阻塞队列 (Blocking Queue):共享变量 (Shared Variable) 及其对共享变量的操作统一封装
- 等待队列 (Wait Queue):每个条件变量都对应有一个等待队列 (Wait Queue), 内部需要实现入队操作 (Enqueue) 和出队操作 (Dequeue) 方法
- 变量状态描述机 (Synchronization Status):描述条件变量和共享变量之间状态变化,又可以称其为同步状态
- 工作模式 (Operation Mode): 线程资源具有排他性,因此定义独占模式和共享模式两种工作模式
综上所述,条件变量和等待队列的作用是解决线程之间的同步问题;共享变量与阻塞队列的作用是解决线程之间的互斥问题。
二. JDK 显式锁统一概念模型在并发编程领域,有两大核心问题:一个是互斥,即同一时刻只允许一个线程访问共享资源;另一个是同步,即线程之间如何通信、协作。
综合 Java 领域中的并发锁的各种实现与应用分析来看,一把锁或者一种锁,基本上都会包含以下几个方面:
- 锁的同步器工作机制:主要是考虑共享模式还是独享模式,是否支持超时机制,以及是否支持超时机制?
- 锁的同步器工作模式:主要是基于 AQS 基础同步器封装内部同步器,是否考虑公平 / 非公平模式?
- 锁的状态变量机制: 主要锁的状态设置,是否共享状态变量?
- 锁的队列封装定义:主要是指等待队列和条件队列,是否需要条件队列或者等待队列定义?
- 锁的底层实现操作: 主要是指底层 CL 锁和 CAS 操作,是否需要考虑自旋锁或者 CAS 操作实例对象方法?
- 锁的组合实现新锁: 主要是基于独占锁和共享锁,是否考虑对应 API 自定义操作实现?
综上所述,大致可以根据上述这些方向,我们便可以清楚🉐️知道 Java 领域中各种锁实现的基本理论时和实现思想。
三.ReentrantLock (可重入锁) 的设计与实现在 Java 领域中,ReentrantLock (可重入锁) 是针对于 Java 多线程并发控制中对一个线程可以多次对某个锁进行加锁操作,主要是基于内置的 AQS 基础抽象队列同步器实现的一种并发控制工具类。
一般来说,对于同一个线程是否可以重复占有同一个锁对象的角度来分,大致主要可以分为可重入锁与不可重入锁。其中:
- 可重入锁:一个线程可以多次抢占同一个锁,也就意味着能够支持一个线程对资源的重复加锁,或者说,一个线程可以多次进入同一个锁所同步的临界区代码块。
- 不可重入锁:一个线程只能抢占一次同一个锁,也就意味着在同一时刻只能有一个线程获取到锁,而其他获取锁的线程只能等待,只有拥有锁的线程释放了锁后,其他的线程才能够获取锁。
ReentrantLock 是 JDK 中显式锁一个主要基于 Lock 接口 API 实现的基础实现类,拥有与内置锁 (synchronized) 相同的并发性和内存语义,同时提供了限时抢占、可中断抢占等一些高级锁特性。 除此之外,ReentrantLock 基于内置的 AQS 基础抽象队列同步器实现,在线程参与锁资源竞争比较激烈的场景下,能表现出比内置锁较佳的性能。 而且,ReentrantLock 是一种独占锁,在独占模式下只能逐一使用锁,也就是说,任意时刻最多只会有一个线程持有锁的控制权。
1. 设计思想ReentrantLock 类最早是在 JDK1.5 版本提供的,从设计思想上来看,主要包括同步器工作模式,获取锁方法,释放锁方法以及定义 Condition 队列方法等 4 个核心要素。其中:
- 实现 Lock 接口 :主要基于 Lock 接口 API 实现对应方法,拥有与内置锁 (synchronized) 相同的并发性和内存语义,用于支持和解决解决互斥问题。
- 同步器工作模式:基于 AQS 基础抽象队列同步器封装内置实现一个静态的内置同步器抽象类,然后基于这个抽象类分别实现了公平同步器和非公平同步器,用来指定和描述同步器工作模式是公平模式还是非公平模式。
- 公平 / 非公平模式:主要描述的是多个线程在同时获取锁时是否按照先到先得的顺序获取锁,如果是则为公平模式,否则为非公平模式。
- 获取锁方法:主要定义了一个 lock () 方法来获取锁,表示假如锁已经被其他线程占有或持有,其当前获取锁的线程则进入等待状态。
- 释放锁方法:主要定义了一个 unlock () 方法来释放锁,表示假如锁已经被其他线程放弃或释放,其当前获取锁的线程则获得该锁。
- 定义 Condition 队列操作方法: 主要是基于 Condition 接口来定义一个方法实现锁的条件机制,用于支持线程的阻塞和唤醒功能即就是解决同步问题,也就是我们说的线程间的通信方式。
- 定义等待队列操作方法: 主要是依据条件队列来时进行对应的操作,间接适配 AQS 基础同步器中对于等待队列的功能,保证获取锁的顺序的公平性
在 ReentrantLock 类的 JDK1.8 版本中,对于 ReentrantLock 的基本实现如下:
public class ReentrantLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = 7373984872572414699 L;
/**
* ReentrantLock锁-定义支持同步器实现
*/
private final Sync sync;
/**
* ReentrantLock锁-基于AQS定义支持同步器实现
*/
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860 L;
/**
* ReentrantLock锁-定义支持同步器Sync获取锁方法
*/
abstract void lock();
//......其他方法代码
}
/**
* ReentrantLock锁-构造同步器默认工作模式(默认非公平模式)
*/
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* ReentrantLock锁-构造同步器指定工作模式(可选公平/非公平模式)
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
/**
* ReentrantLock锁-获取锁(普通模式)
*/
public void lock() {
sync.lock();
}
/**
* ReentrantLock锁-释放锁
*/
public void unlock() {
sync.release(1);
}
/**
* ReentrantLock锁-创建锁的条件机制
*/
public Condition newCondition() {
return sync.newCondition();
}
//......其他方法代码
}
- 内部同步器:基于 AQS 基础同步器封装和定义了一个静态内部 Sync 抽象类,其中抽象了一个内置锁 lock () 方法
- 同步器工作模式:提供了 2 个构造方法,其中无参数构造方法表示的是默认的工作模式,有参数构造方法主要依据参数来实现指定的工作模式
- 获取锁: 主要是提供了 lock () 方法,调用的静态内部 Sync 抽象类内置锁 lock () 方法,而本质上是 AQS 同步器中的 acquire () 方法
- 释放锁: 主要是提供了 unlock () 方法,而本质上是调用的 AQS 同步器中的 release () 方法
- 创建条件队列: 主要是基于 Condition 接口定义了 newCondition () 方法,调用的静态内部 Sync 抽象类 ewCondition () 方法,而本质上是调用的 AQS 同步器中的 ConditionObject 中的 newCondition () 方法
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
/**
* ReentrantLock锁-内部同步器Sync的内置加锁方法
*/
abstract void lock();
/**
* ReentrantLock锁-内部同步器Sync的非公平获取锁
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?