当前位置: 首页 > news >正文

Java锁自定义实现到aqs的理解

专栏系列文章地址:https://blog.csdn.net/qq_26437925/article/details/145290162

本文目标:

  1. 理解锁,能自定义实现锁
  2. 通过自定义锁的实现复习Thread和Object的相关方法
  3. 开始尝试理解Aqs, 这样后续基于Aqs的的各种实现将能更好的理解

目录

    • 锁的自定义实现
      • lock 自旋加锁
      • 改进1: 自旋加锁失败的尝试让出cpu(yield操作)
      • 改进2: yield 换成sleep
      • 改进3: 仿照ObjectMonitor,加上一个等待队列
    • AbstractQueuedSynchronizer
      • Aqs核心思想归纳
      • AbstractQueuedSynchronizer 抽象类的说明和实现
      • 同步器说明
      • Aqs独占模式
      • Aqs共享模式
      • 基于Aqs再次实现自定义锁

锁的自定义实现

线程基础和cas操作知晓后,可以开始自己实现锁了。

例子代码实现多线程的count++

import sun.misc.Unsafe;import java.lang.reflect.Field;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.TimeUnit;class MyLock {volatile int status = 0;Unsafe unsafe;// 引用Unsafe需使用如下反射方式,否则会抛出异常java.lang.SecurityException: Unsafepublic Unsafe reflectGetUnsafe() throws NoSuchFieldException, IllegalAccessException {Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");theUnsafe.setAccessible(true);//禁止访问权限检查(访问私有属性时需要加上)return (Unsafe) theUnsafe.get(null);}boolean compareAndSet(int expect, int newValue) {try {Field field = this.getClass().getDeclaredField("status");field.setAccessible(true);long offset = unsafe.objectFieldOffset(field);//cas操作return unsafe.compareAndSwapInt(status, offset, expect, newValue);} catch (Exception e) {return false;}}public MyLock() {try {unsafe = reflectGetUnsafe();} catch (Exception e) {e.printStackTrace();}}public void lock() {while (!compareAndSet(0, 1)) {}}public void unlock() {compareAndSet(1, 0);}}public class Main {static int count = 0;static int N = 100;public static void main(String[] args) throws Exception {MyLock lock = new MyLock();List<Thread> list = new ArrayList<>();for (int i = 0; i < N; i++) {Thread thread = new Thread(() -> {try {lock.lock();TimeUnit.MILLISECONDS.sleep(1);count++;} catch (Exception e) {} finally {lock.unlock();}}, "myThread-" + i);list.add(thread);}for (Thread thread : list) {thread.start();}for (Thread thread : list) {thread.join();}System.out.println("count:" + count);}
}

lock 自旋加锁

volatile int status = 0;public void lock() {while (!compareAndSet(0, 1)) {}
}

问题:没有竞争得到锁的线程会一直占用CPU资源进行CAS操作,假如一个线程耗费n秒处理业务逻辑,那另外一个线程就会白白花费n秒的CPU资源在那里自旋。

改进1: 自旋加锁失败的尝试让出cpu(yield操作)

所以可以yield尝试让出CPU(可复习:https://blog.csdn.net/qq_26437925/article/details/145400968)

 public void lock() {while (!compareAndSet(0, 1)) {Thread.yield();}
}

问题:yield是将线程设置为就绪状态,需要获取到CPU时间片才能执行变成真正的RUNNABLE状态。但是当线程很多的时候,重新竞争的时候很可能某些线程老是获取不到CPU执行,这样就会出现线程饥饿现象

改进2: yield 换成sleep

public void lock() {while (!compareAndSet(0, 1)) {try {TimeUnit.MILLISECONDS.sleep(100);} catch (Exception e) {}}
}

sleep 也是让出CPU,但是和yield不同,其是把线程设置为TIMED_WAITING状态(A thread that is waiting for another thread to perform an action for up to a specified waiting time is in this state)。不过sleep使用的问题是,不确定sleep多久

改进3: 仿照ObjectMonitor,加上一个等待队列

获取不到的锁的线程,加入到一个等待队列中,释放其CPU; 当有线程要释放锁了,则从头部队列移除线程,开始继续获取锁

import sun.misc.Unsafe;import java.lang.reflect.Field;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.TimeUnit;class MyLock {volatile int status = 0;Unsafe unsafe;Queue<Thread> waitQueue;// 引用Unsafe需使用如下反射方式,否则会抛出异常java.lang.SecurityException: Unsafepublic Unsafe reflectGetUnsafe() throws NoSuchFieldException, IllegalAccessException {Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");theUnsafe.setAccessible(true);//禁止访问权限检查(访问私有属性时需要加上)return (Unsafe) theUnsafe.get(null);}boolean compareAndSet(int expect, int newValue) {try {Field field = this.getClass().getDeclaredField("status");field.setAccessible(true);long offset = unsafe.objectFieldOffset(field);//cas操作return unsafe.compareAndSwapInt(status, offset, expect, newValue);} catch (Exception e) {return false;}}public MyLock() {try {unsafe = reflectGetUnsafe();waitQueue = new ArrayDeque<>();} catch (Exception e) {e.printStackTrace();}}public void lock() {while (!compareAndSet(0, 1)) {park(Thread.currentThread());}}public void unlock() {compareAndSet(1, 0);// 有线程要释放锁了,公平方式,头部线程可以不等待了, 开始去获取锁// 1.得到要唤醒的线程头部线程Thread t = waitQueue.poll();// 2. 唤醒等待线程if (t != null) {unsafe.unpark(t);}}private void park(Thread thread) {if (waitQueue.contains(thread)) {return;}// 将线程加入到等待队列中waitQueue.add(thread);// 将当期线程释放CPU 阻塞thread.yield();}
}public class Main {static int count = 0;static int N = 100;public static void main(String[] args) throws Exception {MyLock lock = new MyLock();List<Thread> list = new ArrayList<>();for (int i = 0; i < N; i++) {Thread thread = new Thread(() -> {try {lock.lock();TimeUnit.MILLISECONDS.sleep(1);count++;} catch (Exception e) {} finally {lock.unlock();}}, "myThread-" + i);list.add(thread);}for (Thread thread : list) {thread.start();}for (Thread thread : list) {thread.join();}System.out.println("count:" + count);}
}

AbstractQueuedSynchronizer

如上上述自定义锁实现能够理解,那么就是理解aqs了

  • 锁的实现框架

参考:美团技术文章:从ReentrantLock的实现看AQS的原理及应用

参考:《The java.util.concurrent Synchronizer Framework》 JUC同步器框架(AQS框架)原文翻译

论文地址

docs api

Provides a framework for implementing blocking locks and related synchronizers (semaphores, events, etc) that rely on first-in-first-out (FIFO) wait queues. This class is designed to be a useful basis for most kinds of synchronizers that rely on a single atomic int value to represent state. Subclasses must define the protected methods that change this state, and which define what that state means in terms of this object being acquired or released. Given these, the other methods in this class carry out all queuing and blocking mechanics. Subclasses can maintain other state fields, but only the atomically updated int value manipulated using methods getState(), setState(int) and compareAndSetState(int, int) is tracked with respect to synchronization.

Aqs核心思想归纳

AQS核心思想是:如果被请求的共享资源空闲,那么就将当前请求资源的线程设置为有效的工作线程,将共享资源设置为锁定状态;如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁分配。这个机制主要用的是CLH队列的变体实现的,将暂时获取不到锁的线程加入到队列中

CLH:Craig、Landin and Hagersten队列,链表结构,AQS中的队列是CLH变体的虚拟双向队列(FIFO),AQS是通过将每条请求共享资源的线程封装成一个节点来实现锁的分配。

在这里插入图片描述

在这里插入图片描述

AQS使用一个volatile的int类型的成员变量state来表示同步状态,通过内置的FIFO队列来完成资源获取的排队工作,通过CAS完成对state值的修改。

AbstractQueuedSynchronizer 抽象类的说明和实现

  1. AbstractQueuedSynchronizer 提供了一个框架,用来实现blocking locks 和 一些同步器,且是基于一个FIFO队列的
  2. AbstractQueuedSynchronizer 被设计为使用一个single atomic {@code int} value来表示状态
  3. AbstractQueuedSynchronizer的子类必须去定义状态,并提供protected方法去操作状态:getState、setState以及compareAndSet
  4. 基于AQS的具体实现类必须根据暴露出的状态相关的方法定义tryAcquiretryRelease方法,以控制acquire和release操作。当同步状态满足时,tryAcquire方法必须返回true,而当新的同步状态允许后续acquire时,tryRelease方法也必须返回true。这些方法都接受一个int类型的参数用于传递想要的状态。

同步器说明

两个操作

  1. acquire操作:阻塞调用的线程,直到或除非同步状态允许其继续执行。
  2. release操作:则是通过某种方式改变同步状态,使得一或多个被acquire阻塞的线程继续执行。

同步器需要支持如下:

  • 阻塞和非阻塞(例如tryLock)的同步
  • 可选的超时设置,让调用者可以放弃等待
  • 通过中断实现的任务取消,通常是分为两个版本,一个acquire可取消,而另一个不可以

同步器的实现根据其状态是否独占而有所不同。独占状态的同步器,在同一时间只有一个线程可以通过阻塞点,而共享状态的同步器可以同时有多个线程在执行。一般锁的实现类往往只维护独占状态,但是,例如计数信号量在数量许可的情况下,允许多个线程同时执行。为了使框架能得到广泛应用,这两种模式都要支持。

j.u.c包里还定义了Condition接口,用于支持监控形式的await/signal操作,这些操作与独占模式的Lock类有关,且Condition的实现天生就和与其关联的Lock类紧密相关

Aqs独占模式

在这里插入图片描述

Aqs共享模式

在这里插入图片描述

基于Aqs再次实现自定义锁

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;class MyLock  {private static class Sync extends AbstractQueuedSynchronizer {// 加锁的cas操作@Overrideprotected boolean tryAcquire (int arg) {return compareAndSetState(0, 1);}@Overrideprotected boolean tryRelease (int arg) {setState(0);return true;}@Overrideprotected boolean isHeldExclusively () {return getState() == 1;}}private Sync sync = new Sync();public void lock () {sync.acquire(1);}public void unlock () {sync.release(1);}
}public class Main {static int count = 0;static int N = 100;public static void main(String[] args) throws Exception {MyLock lock = new MyLock();List<Thread> list = new ArrayList<>();for (int i = 0; i < N; i++) {Thread thread = new Thread(() -> {try {lock.lock();TimeUnit.MILLISECONDS.sleep(1);count++;} catch (Exception e) {} finally {lock.unlock();}}, "myThread-" + i);list.add(thread);}for (Thread thread : list) {thread.start();}for (Thread thread : list) {thread.join();}System.out.println("count:" + count);}
}

相关文章:

Java锁自定义实现到aqs的理解

专栏系列文章地址&#xff1a;https://blog.csdn.net/qq_26437925/article/details/145290162 本文目标&#xff1a; 理解锁&#xff0c;能自定义实现锁通过自定义锁的实现复习Thread和Object的相关方法开始尝试理解Aqs, 这样后续基于Aqs的的各种实现将能更好的理解 目录 锁的…...

仿真设计|基于51单片机的温度与烟雾报警系统

目录 具体实现功能 设计介绍 51单片机简介 资料内容 仿真实现&#xff08;protues8.7&#xff09; 程序&#xff08;Keil5&#xff09; 全部内容 资料获取 具体实现功能 &#xff08;1&#xff09;LCD1602实时监测及显示温度值和烟雾浓度值&#xff1b; &#xff08;2…...

文件读写操作

写入文本文件 #include <iostream> #include <fstream>//ofstream类需要包含的头文件 using namespace std;void test01() {//1、包含头文件 fstream//2、创建流对象ofstream fout;/*3、指定打开方式&#xff1a;1.ios::out、ios::trunc 清除文件内容后打开2.ios:…...

【后端开发】字节跳动青训营Cloudwego脚手架

Cloudwego脚手架使用 cwgo脚手架 cwgo脚手架 安装的命令&#xff1a; GOPROXYhttps://goproxy.cn/,direct go install github.com/cloudwego/cwgolatest依赖thriftgo的安装&#xff1a; go install github.com/cloudwego/thriftgolatest编辑echo.thrift文件用于生成项目&…...

SQL UCASE() 函数详解

SQL UCASE() 函数详解 在SQL中&#xff0c;UCASE() 函数是一个非常有用的字符串处理函数&#xff0c;它可以将字符串中的所有小写字母转换为大写字母。本文将详细介绍UCASE() 函数的用法、语法、示例以及其在实际应用中的优势。 一、UCASE() 函数简介 UCASE() 函数是SQL标准…...

99.23 金融难点通俗解释:小卖部经营比喻PPI(生产者物价指数)vsCPI(消费者物价指数)

目录 0. 承前1. 简述&#xff1a;价格指数对比2. 比喻&#xff1a;两大指数对比2.1 简单对比2.2 生动比喻 3. 实际应用3.1 价格传导现象 4. 总结5. 有趣的对比6. 数据获取实现代码7. 数据可视化实现代码 0. 承前 本文主旨&#xff1a; 本文使用小卖部比喻PPI和CPI&#xff0c;…...

【Elasticsearch】match_bool_prefix 查询 vs match_phrase_prefix 查询

Match Bool Prefix Query vs. Match Phrase Prefix Query 在 Elasticsearch 中&#xff0c;match_bool_prefix 查询和 match_phrase_prefix 查询虽然都支持前缀匹配&#xff0c;但它们的行为和用途有所不同。以下是它们之间的主要区别&#xff1a; 1. match_bool_prefix 查询…...

H. Mad City

题目链接&#xff1a;Problem - H - Codeforces 题目大意&#xff1a;给定一个带环的图&#xff0c; 以及a, b两点 判断再图上不断的移动&#xff0c; b想不与a相遇&#xff0c; a想捉到b, 并且二者只能移动一步。 若b跑不掉 NO 否则YES. 具体题目看链接 输入&#xff1a; …...

【图床配置】PicGO+Gitee方案

【图床配置】PicGOGitee方案 文章目录 【图床配置】PicGOGitee方案为啥要用图床图床是什么配置步骤下载安装PicGoPicGo配置创建Gitee仓库Typora中的设置 为啥要用图床 在Markdown中&#xff0c;图片默认是以路径的形式存在的&#xff0c;类似这样 可以看到这是本地路径&#x…...

《程序人生》工作2年感悟

一些杂七杂八的感悟&#xff1a; 1.把事做好比什么都重要&#xff0c; 先树立量良好的形象&#xff0c;再横向发展。 2.职场就是人情世故&#xff0c;但也不要被人情世故绑架。 3.要常怀感恩的心&#xff0c;要记住帮助过你的人&#xff0c;愿意和你分享的人&#xff0c;有能力…...

当当网近30日热销图书的数据采集与可视化分析(scrapy+openpyxl+matplotlib)

当当网近30日热销图书的数据采集与可视化分析(scrapy+openpyxl+matplotlib) 当当网近30日热销书籍官网写在前面 实验目的:实现当当网近30日热销图书的数据采集与可视化分析。 电脑系统:Windows 使用软件:Visual Studio Code Python版本:python 3.12.4 技术需求:scrapy、…...

unity学习25:用 transform 进行旋转和移动,简单的太阳地球月亮模型,以及父子级关系

目录 备注内容 1游戏物体的父子级关系 1.1 父子物体 1.2 坐标关系 1.3 父子物体实际是用 每个gameobject的tranform来关联的 2 获取gameObject的静态数据 2.1 具体命令 2.2 具体代码 2.3 输出结果 3 获取gameObject 的方向 3.1 游戏里默认的3个方向 3.2 获取方向代…...

【项目集成Husky】

项目集成Husky 安装初始化 Husky在.husky → pre-commit文件中添加想要执行的命令 安装 使用 Husky 可以帮助你在 Git 钩子中运行脚本&#xff0c;例如在提交代码前运行测试或格式化代码pnpm add --save-dev husky初始化 Husky npx husky init这会在项目根目录下创建一个 .hu…...

基于Spring Security 6的OAuth2 系列之七 - 授权服务器--自定义数据库客户端信息

之所以想写这一系列&#xff0c;是因为之前工作过程中使用Spring Security OAuth2搭建了网关和授权服务器&#xff0c;但当时基于spring-boot 2.3.x&#xff0c;其默认的Spring Security是5.3.x。之后新项目升级到了spring-boot 3.3.0&#xff0c;结果一看Spring Security也升级…...

【Matlab高端绘图SCI绘图模板】第006期 对比绘柱状图 (只需替换数据)

1. 简介 柱状图作为科研论文中常用的实验结果对比图&#xff0c;本文采用了3组实验对比的效果展示图&#xff0c;代码已调试好&#xff0c;只需替换数据即可生成相关柱状图&#xff0c;为科研加分。通过获得Nature配色的柱状图&#xff0c;让你的论文看起来档次更高&#xff0…...

Java 大视界 -- Java 大数据在生物信息学中的应用与挑战(67)

&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…...

.NET Core 中依赖注入的使用

ASP.NET Core中服务注入的地方 在ASP.NET Core项目中一般不需要自己创建ServiceCollection、IServiceProvider。在Program.cs的builder.Build()之前向builder.Services中注入。在Controller中可以通过构造方法注入服务。 低使用频率的服务 把Action用到的服务通过Action的参…...

deepseek 潜在变量Z的计算;变分自编码器(VAE); 高斯混合模型(GMM)

潜在注意力:潜在变量 Z Z Z的计算 潜在变量 Z Z Z...

rsync安装与使用-linux015

使用 rsync 可以非常高效地将文件或目录从一个服务器传输到另一个服务器。 能力&#xff1a; 支持 64 位文件、64 位 inode、64 位时间戳、64 位长整型支持套接字对、符号链接、符号链接时间、硬链接、硬链接特殊文件、硬链接符号链接支持 IPv6、访问时间&#xff08;atimes&…...

CAP 定理的 P 是什么

分布式系统 CAP 定理 P 代表什么含义 作者之前在看 CAP 定理时抱有很大的疑惑&#xff0c;CAP 定理的定义是指在分布式系统中三者只能满足其二&#xff0c;也就是存在分布式 CA 系统的。作者在网络上查阅了很多关于 CAP 文章&#xff0c;虽然这些文章对于 P 的解释五花八门&am…...

调用支付宝接口响应40004 SYSTEM_ERROR问题排查

在对接支付宝API的时候&#xff0c;遇到了一些问题&#xff0c;记录一下排查过程。 Body:{"datadigital_fincloud_generalsaas_face_certify_initialize_response":{"msg":"Business Failed","code":"40004","sub_msg…...

Unity3D中Gfx.WaitForPresent优化方案

前言 在Unity中&#xff0c;Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染&#xff08;即CPU被阻塞&#xff09;&#xff0c;这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案&#xff1a; 对惹&#xff0c;这里有一个游戏开发交流小组&…...

Debian系统简介

目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版&#xff…...

pam_env.so模块配置解析

在PAM&#xff08;Pluggable Authentication Modules&#xff09;配置中&#xff0c; /etc/pam.d/su 文件相关配置含义如下&#xff1a; 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块&#xff0c;负责验证用户身份&am…...

ffmpeg(四):滤镜命令

FFmpeg 的滤镜命令是用于音视频处理中的强大工具&#xff0c;可以完成剪裁、缩放、加水印、调色、合成、旋转、模糊、叠加字幕等复杂的操作。其核心语法格式一般如下&#xff1a; ffmpeg -i input.mp4 -vf "滤镜参数" output.mp4或者带音频滤镜&#xff1a; ffmpeg…...

CSS设置元素的宽度根据其内容自动调整

width: fit-content 是 CSS 中的一个属性值&#xff0c;用于设置元素的宽度根据其内容自动调整&#xff0c;确保宽度刚好容纳内容而不会超出。 效果对比 默认情况&#xff08;width: auto&#xff09;&#xff1a; 块级元素&#xff08;如 <div>&#xff09;会占满父容器…...

HarmonyOS运动开发:如何用mpchart绘制运动配速图表

##鸿蒙核心技术##运动开发##Sensor Service Kit&#xff08;传感器服务&#xff09;# 前言 在运动类应用中&#xff0c;运动数据的可视化是提升用户体验的重要环节。通过直观的图表展示运动过程中的关键数据&#xff0c;如配速、距离、卡路里消耗等&#xff0c;用户可以更清晰…...

AirSim/Cosys-AirSim 游戏开发(四)外部固定位置监控相机

这个博客介绍了如何通过 settings.json 文件添加一个无人机外的 固定位置监控相机&#xff0c;因为在使用过程中发现 Airsim 对外部监控相机的描述模糊&#xff0c;而 Cosys-Airsim 在官方文档中没有提供外部监控相机设置&#xff0c;最后在源码示例中找到了&#xff0c;所以感…...

Python 高效图像帧提取与视频编码:实战指南

Python 高效图像帧提取与视频编码:实战指南 在音视频处理领域,图像帧提取与视频编码是基础但极具挑战性的任务。Python 结合强大的第三方库(如 OpenCV、FFmpeg、PyAV),可以高效处理视频流,实现快速帧提取、压缩编码等关键功能。本文将深入介绍如何优化这些流程,提高处理…...

全面解析数据库:从基础概念到前沿应用​

在数字化时代&#xff0c;数据已成为企业和社会发展的核心资产&#xff0c;而数据库作为存储、管理和处理数据的关键工具&#xff0c;在各个领域发挥着举足轻重的作用。从电商平台的商品信息管理&#xff0c;到社交网络的用户数据存储&#xff0c;再到金融行业的交易记录处理&a…...