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

「JVM 高效并发」线程安全

  • 面向过程编程,把数据和过程分别作为独立的部分考虑,数据代表问题空间中的客体,程序代码则用于处理这些数据;
  • 面向对象编程,把数据和行为都看做对象的一部分,以符合现实世界的思维方式来编写和组织程序;

对象在一项工作进行期间会不停的中断和切换线程,对象的数据(数据)可能会在中断期间被修改和变脏,这将使的并发变得不安全;

  • 线程安全,当多个线程同时访问同一对象,若不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行其他协调操作,调用这个对象的行为都可以获得正确的结果,则称这个对象是线程安全的;(代码本身封装了所有必要的正确性保障手段,调用者不需要关心多线程的调用问题);

文章目录

      • 1. Java 语言中的线程安全
      • 2. 线程安全的实现方法

1. Java 语言中的线程安全

线程安全的五个级别

  • 不可变Immutable),不可变对象一定是线程安全的,最直接、最纯粹的安全;一旦不可变对象被正确的构建出来,它永远不会在多线程中处于不一致的状态;

若多线程共享的数据是一个基本数据类型,只要限定为 final 类型,则它是不可变的;

若多线程共享的数据是一个对象,由于 Java 语言暂时没有值类型,需要对象自行保证其行为不会对其状态产生任何影响(如 String 类型,它的 subString()、replace()、concat() 都不会影响它原来的值,而是返回一个新构造的对象);最简单的方式是将对象中带有状态的变量都声明为 final;

Java 类库 API 中不可变对象还有 java.lang.Number 的部分子类(Long、Double、BigInteger、BigDecimal 等),AotmicInteger、AtomicLong 是可变类型;

  • 绝对线程安全,完全满足上文线程安全定义;不管运行时环境如何,调用者都不需要任何额外的同步措施;
private static Vector<Integer> vector = new Vector<Integer>();public static void main(String[] args) {while (true) {for (int i = 0; i < 10; i++) {vector.add(i);}Thread removeThread = new Thread(() -> {for (int i = 0; i < vector.size(); i++) {vector.remove(i);}});Thread printThread = new Thread(() -> {for (int i = 0; i < vector.size(); i++) {System.out.println((vector.get(i)));}});removeThread.start();printThread.start();// 不要同时产生过多的线程,否则会导致操作系统假死while (Thread.activeCount() > 20) ;}
}

运行结果

Exception in thread "Thread-24207" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 9at java.util.Vector.get(Vector.java:753)at edu.aurelius.jvm.concurrent.VectorTest.lambda$main$1(VectorTest.java:25)at java.lang.Thread.run(Thread.java:750)

Vector 是一个线程安全的容器,但并非绝对线程安全,其 add()、get()、size() 等方法都被 synchronized 修饰了,但在多线程环境下若不对调用端做额外同步限制,这段代码仍不安全;

线程安全的写法

Thread removeThread = new Thread(() -> {synchronized (vector) {for (int i = 0; i < vector.size(); i++) {vector.remove(i);}}
});Thread printThread = new Thread(() -> {synchronized (vector) {for (int i = 0; i < vector.size(); i++) {System.out.println((vector.get(i)));}}
});
  • 相对线程安全,通常意义上的线程安全,保障单次操作的线程安全,但不保证一些特定顺序的联系调用安全(见上例),如 Vector、HashTable、Collections 的 synchronizedCollection() 方法包装的集合等;

  • 线程兼容,对象本身不是线程安全的,到哪可以通过在调用端正确的使用同步手段保证对象在并发环境中的安全(通常说的不是线程安全的类型,如集合类 ArrayList、HashMap 等);

  • 线程对立,不管调用端使用采用同步措施,都无法在多线程环境中并发使用的代码(如同一个 Thread 类对象的 suspend() 和 resume() 同时在两个线程中调用,一个尝试中断线程,一个尝试恢复线程,无论是否进行了同步,线程都存在死锁的分析;还有 System.setIn、System.setOut、System.runFinalizersOnExit);

2. 线程安全的实现方法

只要明白了 JVM 线程安全措施的原理与运作过程,如何编写并发安全的代码便不再困难;

  • 互斥同步Mutual Exclusion & Synchronization),在多线程并发访问共享数据时,保障共享数据在同一时刻只被一条(或者一些,当使用信号量时)线程使用,互斥可以通过临界区Critical Selection)、互斥量Mutex)、信号量Semaphore)等实现;

synchronized 关键字

synchronized 经过 javac 编译,会在同步快的前后分别形成 monitorenter 和 monitorexit 两个字节指令,这两个字节指令通过一个 reference 类型的参数指明要锁定和解锁的对象;若没有指定 reference 对象,synchronized 修饰的是实例方法则锁定方法所在类的实例,synchronized 修饰的是静态方法则锁定方法所在类的 Class 对象;

《Java 虚拟机规范》要求执行 monitorenter 指令时,首先要尝试获取对象所,如果这个对象没有被锁定,或者当前线程已经获得了这个对象的所,则把锁的计数器的值加 1,执行 monitorexit 时将锁的计数器值减 1;一旦计数器值为 0,锁随机被释放;若获取锁失败,则当前线程被阻塞,等待其他线程释放;

持有锁是一个重量级操作,阻塞或唤醒一个线程需要操作系统来完成,这会陷入用户态和核心态的转换,这需要耗费很多处理器时间;

java.util.concurrent.locks.Lock 接口

Lock 接口是 Java 的另一种互斥同步手段,用户可以以非块结构(Non-Block Structured)来实现互斥同步;

重入锁ReentrantLock)是 Lock 接口最常见的一种实现,与 synchronized 相似,只是多了一些高级功能:等待可中断、可实现公平锁、可绑定多个条件;

等待可中断,当持有锁的线程长期不释放锁,正在等待的线程可以选择放弃等待,这对处理较长时间的同步块很有帮助;

公平锁,多个线程在等待同一锁时,必须按照申请锁的时间顺序来依次获得锁,synchronized 的锁是非公平的 ReentrantLock 默认也是非公平的,公平锁会导致 ReentrantLock 的性能急剧下降,明显影响吞吐量;

锁绑定多个条件,一个 ReentrantLock 对象绑定多个 Condition 对象,在 synchronized 中,锁对象的 wait() 和它的 notify()/notifyAll() 配合可以实现一个隐含条件;ReentrantLock 对象可以多次调用 newCondition() 绑定多个条件;

synchronized vs. ReentrantLock

JDK 5 时 synchronized 有非常大的优化余地,ReentrantLock 表现更稳定;
JDK 6 时 synchronized 锁得到优化,与 ReentrantLock 的性能基本持平,性能不再试选择的关键因素;
ReentrantLock 在功能上是 synchronized 的超集,但 synchronized 是 Java 语法层面的同步,更清晰简单,且自动处理异常时的锁释放,JVM 也更容易战队 synchronized 进行优化,所以在功能皆满足情况下,推荐使用 synchronized;

  • 非阻塞同步Non-Blocking Synchronized),基于冲突检测的乐观并发策略;不管风险,先进性操作,若没有其他线程争用共享数据,则操作成功,若共享数据被争用,产生了冲突,则进行补偿操作(如不停尝试,直到没有竞争为止);这种乐观并发策略不需要阻塞挂起线程,因此称为非阻塞同步;

原子性处理器指令集

a. 测试并设置(Test-and-Set)
b. 获取并增加(Fetch-and-Increment)
c. 交换(Swap)
d. 比较并交换(Compare-and-Swap,CAS)
e. 加载链接/条件存储(Load-Linked/Store-Conditional,LL/SC)

Java 最终暴露出来的是 CAS 操作,通过三个操作数(内存位置 V、就的预期值 A、准备设置的新值 B),当且仅当 V 符合 A 时,处理器才会用 B 更新 V;不管是否更新成功,都返回 V 的旧值;

Atomic 原子自增运算

public static AtomicInteger race = new AtomicInteger(0);public static void increase() {race.incrementAndGet();
}private static final int THREADS_COUNT = 20;public static void main(String[] args) throws Exception {Thread[] threads = new Thread[THREADS_COUNT];for (int i = 0; i < THREADS_COUNT; i++) {threads[i] = new Thread(() -> {for (int i1 = 0; i1 < 10000; i1++) {increase();}});threads[i].start();}while (Thread.activeCount() > 1) Thread.yield();System.out.println(race);
}

incrementAndGet() 的 JDK 源码

/**
* Atomically increment by one the current value.
* @return the updated value
*/
public final int incrementAndGet() {for (;;) {int current = get();int next = current + 1;if (compareAndSet(current, next))return next;}
}

不断尝试将一个比当前值大一的新值赋给自己,若失败,则说明旧值发生了变化,再次循环操作,直到设置成功为止;

CAS 操作的 ABA 问题:当变量 V 初次读取时是 A 值,准备复制时检测它还是 A,但实际他已经被改成 B,并改回 A 了;可通过原子引用类 AtomicStampedReference 保证 CAS 的正确性(通过给 V 添加版本号),不过传统的互斥同步可能会更高效;

  • 无同步方案,若让一段代码本来就不涉及线程共享数据,那它天生就是线程安全的;

可重入代码Reentrant Code),又称纯代码Pure Code),指在多线程的上下文语境中不涉及信号量等因素,不依赖全局变量、存储在堆上的数据和公用的系统资源,用到的状态量都是由参数传入,不调用非可重入的方法等;可重入代码线程安全代码真子集

线程本地存储Thread Local Storage),若共享数据能保证只在同一线程中共享,可将共享数据的可见范围限制在同一线程内,这样就无需同步也能保证线程不出现数据争用问题;

Java 语言中,若一个变量被多个线程访问,可使用 volatile 将之声明为易变的;若一个变量只被单线程独享,可以通过 ThreadLocal 类实现线程本地存储(每个 Thread 对象中都有一个 ThreadLocalMap 对象,以 ThreadLocal.threadLocalHashCode 为键,以本地线程变量为值,ThreadLocal 对象为当前线程的 Map 的访问入口);


上一篇:「JVM 高效并发」Java 协程

PS:感谢每一位志同道合者的阅读,欢迎关注、评论、赞!


参考资料:

  • [1]《深入理解 Java 虚拟机》

相关文章:

「JVM 高效并发」线程安全

面向过程编程&#xff0c;把数据和过程分别作为独立的部分考虑&#xff0c;数据代表问题空间中的客体&#xff0c;程序代码则用于处理这些数据&#xff1b;面向对象编程&#xff0c;把数据和行为都看做对象的一部分&#xff0c;以符合现实世界的思维方式来编写和组织程序&#…...

微信扫码登录

一、准备工作 微信开发者平台&#xff1a;https://open.weixin.qq.com 1、注册 2、邮箱激活 3、完善开发者资料 4、开发者资质认证&#xff1a;仅能企业注册&#xff08;后面提供学习的使用渠道&#xff09;准备营业执照&#xff0c;1-2个工作日审批、300元 5、创建网站应用&…...

Unity协程的简单应用

Unity协程是一种特殊的函数&#xff0c;可以让你在Unity中创建一种类似于多线程的异步操作。它可以在需要等待某个操作完成时&#xff0c;暂停执行当前代码&#xff0c;等待某个条件满足后再继续执行。 在一般情况下 unity中调用函数时&#xff0c;函数将运行到完成状态&#x…...

LeetCode 1250. Check If It Is a Good Array【数论】

本文属于「征服LeetCode」系列文章之一&#xff0c;这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁&#xff0c;本系列将至少持续到刷完所有无锁题之日为止&#xff1b;由于LeetCode还在不断地创建新题&#xff0c;本系列的终止日期可能是永远。在这一系列刷题文章…...

ETHDenver 2023

ETHDenver是全球最大、持续时间最长的以太坊活动之一&#xff0c;今年的活动定于2月24日至3月5日在美国科罗拉多州丹佛市盛大举行。这次活动将面向以太坊和其他区块链协议爱好者、设计者和开发人员。Moonbeam作为ETHDenver 2023的Meta赞助商&#xff0c;将在本次活动中展示令人…...

React架构演变

老版React架构 React 16之前的架构 其实就分为两个部分&#xff1a; Reconciler协调器Render渲染器 Reconciler协调器负责本次更新有什么组件需要被渲染&#xff0c;diff算法就发生在这个步骤中&#xff0c;在diff算法中会将上次更新的组件和本次更新的组件做一个对比&…...

安全认证--JWT介绍及使用

安全认证--JWT介绍及使用1.无状态登录原理1.1.什么是有状态&#xff1f;1.2.什么是无状态1.3.如何实现无状态1.4.JWT1.4.1.简介1.4.2.数据格式2.编写JWT工具2.1.添加JWT依赖2.2.载荷对象2.3.工具2.4.测试2.4.1.配置秘钥2.4.2.测试类1.无状态登录原理 有状态登录和无状态登录详…...

【计算机组成原理】计算机硬件的基础组成、认识各个硬件部件

计算机组成原理&#xff08;一&#xff09; 计算机内部是通过电信号传递数据 电信号&#xff1a;分为高电平和低电平&#xff0c;分别代表1/0 数字、文字、图像如何用二进制表示? CPU如何对二进制数进行加减乘除? 如何存储这些二进制数的? 如何从内存中取出想要的数…...

使用ChIPSeeker进行ChIP-seq, ATAC-seq,cuttag等富集峰的基因组注释

二代测序产生的数据类型 常规的下一代高通量测序&#xff08;next generation sequencing, NGS&#xff09;实验通常产生大量短片段(reads)&#xff0c;通常我们需要将这些reads比对到参考基因组/转录组上&#xff0c;即将它们置于生物学上有意义的基因背景下&#xff0c;才能…...

第九届蓝桥杯省赛——7缩位求和

题目&#xff1a;在电子计算机普及以前&#xff0c;人们经常用一个粗略的方法来验算四则运算是否正确。比如&#xff1a;248 * 15 3720把乘数和被乘数分别逐位求和&#xff0c;如果是多位数再逐位求和&#xff0c;直到是1位数&#xff0c;得2 4 8 14 > 1 4 5;1 5 65…...

【c++】STL常用容器5—list容器

文章目录list基本概念list构造函数list赋值和交换list大小操作list插入和删除list数据存取list反转和排序list基本概念 功能&#xff1a;将数据进行链式存储。 链表&#xff08;list&#xff09;是一种物理存储单元上非连续的存储结构&#xff0c;数据元素的逻辑顺序是通过链…...

【牛客刷题专栏】0x0D:JZ5 替换空格(C语言编程题)

前言 个人推荐在牛客网刷题(点击可以跳转)&#xff0c;它登陆后会保存刷题记录进度&#xff0c;重新登录时写过的题目代码不会丢失。个人刷题练习系列专栏&#xff1a;个人CSDN牛客刷题专栏。 题目来自&#xff1a;牛客/题库 / 在线编程 / 剑指offer&#xff1a; 目录前言问题…...

聚观早报 | 苹果2024年放弃高通;腾讯回应进军类 ChatGPT

今日要闻&#xff1a;苹果2024年放弃高通&#xff1b;腾讯回应进军类 ChatGPT&#xff1b;小米发布无线AR眼镜探索版&#xff1b;50%的美国企业已在使用ChatGPT&#xff1b;Snap推出ChatGPT驱动的聊天机器人 苹果2024年放弃高通 高通公司 CEO 兼总裁克里斯蒂亚诺・安蒙&#xf…...

Elasticsearch:如何正确处理 Elasticsearch 摄取管道故障

在我之前的文章 “Elastic&#xff1a;开发者上手指南” 中的 “Ingest pipeline” 章节中个&#xff0c;我有很多文章是关于 ingest pipeline 的。在今天的文章中&#xff0c;我将重点介绍如何处理在摄取管道中的错误。在我之前的文章 “Elasticsearch&#xff1a;如何处理 in…...

指标体系—北极星指标体系

北极星指标体系 每个产品都有很多指标,每个指标都反映了对应业务的经营情况。但是在实际业务经营中,却要求我们在不同的产品阶段寻找到合适的指标,让这个指标可以代表当前产品阶段的方向和目标,让这个指标不仅对业务经营团队,而且对产品的用户、对产品的价值都能有很好的…...

【操作系统】内存管理

虚拟内存 虚拟内存的目的是为了让物理内存扩充成更大的逻辑内存&#xff0c;从而让程序获得更多的可用内存。 为了更好的管理内存&#xff0c;操作系统将内存抽象成地址空间。每个程序拥有自己的地址空间&#xff0c;这个地址空间被分割成多个块&#xff0c;每一块称为一页。…...

家庭消耗品跟踪管理软件HomeLists

什么是 HomeLists &#xff1f; HomeLists 是一款自托管耗材统计软件&#xff0c;能通过提醒等帮助您跟踪家庭消耗品。 安装 在群晖上以 Docker 方式安装。 在注册表中搜索 homelists &#xff0c;选择第一个 aceberg/homelists&#xff0c;版本选择 latest。 本文写作时&…...

django模型简要(1)

1. AbstractUser(内置用户模型类)的使用 ### 需要在settings.py中添加如下&#xff1a; AUTH_USER_MODEL app.MyUser 说明&#xff1a;这是为了覆盖django默认的User model&#xff1b;app即模型所属app&#xff0c;MyUser即AbstractUser实现类。 2.on_delete选项 从django3.…...

【shell 编程大全】sed详解

sed详解1. 概述 今天单独拉出一章来讲述下sed命令。因为sed命令确实内容太多&#xff0c;不过也是比较灵活的&#xff0c;好了不废话了。我们开始吧 1.2 原理解析 shell脚本虽然功能很多&#xff0c;但是它最常用的功能还是处理文本文件&#xff0c;尤其是在正常的业务操作流程…...

关于sudo配置

前言这里做一个小补充&#xff0c;主要讲一下关于利用sudo对指令提权以及普通用户无法使用sudo指令的问题。在前面的文章【Linux】一文掌握Linux权限中&#xff0c;我们讲到了关于权限的一些问题。我们知道root身份下&#xff0c;一切畅通无阻&#xff0c;而权限只是用来限制我…...

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …...

eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)

说明&#xff1a; 想象一下&#xff0c;你正在用eNSP搭建一个虚拟的网络世界&#xff0c;里面有虚拟的路由器、交换机、电脑&#xff08;PC&#xff09;等等。这些设备都在你的电脑里面“运行”&#xff0c;它们之间可以互相通信&#xff0c;就像一个封闭的小王国。 但是&#…...

【kafka】Golang实现分布式Masscan任务调度系统

要求&#xff1a; 输出两个程序&#xff0c;一个命令行程序&#xff08;命令行参数用flag&#xff09;和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽&#xff0c;然后将消息推送到kafka里面。 服务端程序&#xff1a; 从kafka消费者接收…...

Day131 | 灵神 | 回溯算法 | 子集型 子集

Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 笔者写过很多次这道题了&#xff0c;不想写题解了&#xff0c;大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...

电脑插入多块移动硬盘后经常出现卡顿和蓝屏

当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时&#xff0c;可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案&#xff1a; 1. 检查电源供电问题 问题原因&#xff1a;多块移动硬盘同时运行可能导致USB接口供电不足&#x…...

Psychopy音频的使用

Psychopy音频的使用 本文主要解决以下问题&#xff1a; 指定音频引擎与设备&#xff1b;播放音频文件 本文所使用的环境&#xff1a; Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...

GC1808高性能24位立体声音频ADC芯片解析

1. 芯片概述 GC1808是一款24位立体声音频模数转换器&#xff08;ADC&#xff09;&#xff0c;支持8kHz~96kHz采样率&#xff0c;集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器&#xff0c;适用于高保真音频采集场景。 2. 核心特性 高精度&#xff1a;24位分辨率&#xff0c…...

ZYNQ学习记录FPGA(一)ZYNQ简介

一、知识准备 1.一些术语,缩写和概念&#xff1a; 1&#xff09;ZYNQ全称&#xff1a;ZYNQ7000 All Pgrammable SoC 2&#xff09;SoC:system on chips(片上系统)&#xff0c;对比集成电路的SoB&#xff08;system on board&#xff09; 3&#xff09;ARM&#xff1a;处理器…...

spring Security对RBAC及其ABAC的支持使用

RBAC (基于角色的访问控制) RBAC (Role-Based Access Control) 是 Spring Security 中最常用的权限模型&#xff0c;它将权限分配给角色&#xff0c;再将角色分配给用户。 RBAC 核心实现 1. 数据库设计 users roles permissions ------- ------…...

Linux 下 DMA 内存映射浅析

序 系统 I/O 设备驱动程序通常调用其特定子系统的接口为 DMA 分配内存&#xff0c;但最终会调到 DMA 子系统的dma_alloc_coherent()/dma_alloc_attrs() 等接口。 关于 dma_alloc_coherent 接口详细的代码讲解、调用流程&#xff0c;可以参考这篇文章&#xff0c;我觉得写的非常…...