当前位置: 首页 > 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;而权限只是用来限制我…...

EEGLAB处理运动想象脑电数据

最近在看论文时&#xff0c;经常看到作者处理数据的过程&#xff0c;之前都是一代而过&#xff0c;知道怎么处理就可以了&#xff0c;一直没有实践&#xff0c;最近需要一些特殊的数据&#xff0c;需要自己处理出来&#xff0c;这里尝试着自己用MATLAB处理数据&#xff0c;记录…...

span标签的使用场景

目录 前言 一、span标签是什么&#xff1f; 二、span常用 1.可以嵌套a标签。 2.直接使用 3.加样式使用 4.加按钮使用 5.加a标签的综合使用 6.跟table结合使用 总结 前言 本篇章主要记录一下开发日常中&#xff0c;所常遇见的使用span标签的场景。 一、span标签是什么…...

Kafka面试问题总结

kafka架构2.基础概念Producer&#xff08;生产者&#xff09; : 产生消息的一方。Consumer&#xff08;消费者&#xff09; : 消费消息的一方。Broker&#xff08;代理&#xff09; : 可以看作是一个独立的 Kafka 实例。多个 Kafka Broker 组成一个 Kafka Cluster。同时&#x…...

FPGA案例开发手册——基于全志T3+Logos FPGA核心板

前 言 本文档主要提供评估板FPGA端案例测试方法,适用的开发环境为Windows 7 64bit和Windows 10 64bit。 本文案例基于创龙科技的全志T3+Logos FPGA核心板,它是一款基于全志科技T3四核ARM Cortex-A7处理器 + 紫光同创Logos PGL25G/PGL50G FPGA设计的异构多核全国产工业核心板…...

或许你想要的画图工具在这里

之前文章发布后&#xff0c;有小伙伴问下面的画怎么画的&#xff08;偷偷告诉你&#xff0c;其实我是用铅笔水彩笔画的&#xff09;&#xff0c;哈哈&#xff0c;开玩笑了。其实这些图都是用Excalidraw 画出来的。 我们平常不管是工作中&#xff0c;还是在日常写文章&#x…...

2023年功能测试还值得入行吗?

前言 鉴于笔者从13年入行IT行业&#xff0c;经历了只有开发没有测试的阶段&#xff0c;经历了14年只要会基本的功能测试在一线就能薪资过万的阶段&#xff0c;经历了17年只要会一点自动化&#xff0c;会一点性能就能蒙骗过面试官的阶段&#xff0c;更经历了19年所有面试官对于…...

2022-2023山东大学机器学习期末回忆及复习建议

2023年第一次闭卷考试&#xff0c;让我们准备时都很无力&#xff0c;不知道试题究竟是什么难度&#xff0c;是否要掌握手推公式还有一些晦涩的知识点之类的&#xff0c;看到试题才发现其实闭卷也有好处&#xff0c;与往年题相比难度下降了不少。 一、名词解释 1、测试集 2、Boo…...

基于ssm框架实现家庭理财收支系统(源码+数据库+文档)

一、项目简介 本项目是一套基于ssm框架实现家庭理财收支系统&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的Java学习者。 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c…...

MyBatis - 09 - 自定义映射resultMap

文章目录1 准备工作1.1 建表1.2 创建实体类1.3 引出一个问题方案1方案2方案32.完整代码项目结构EmpMapper接口Emp类SqlSessionUtils工具类EmpMapper.xmljdbc.propertieslog4j.xmlmybatis-config.xmlResultMapTest完整代码在后面 1 准备工作 1.1 建表 t_emp 添加测试数据&…...

springBoot常见面试题(2023最新)

目录前言1.谈谈你对springBoot的理解2.为什么使用springBoot或springBoot的优点3. springBoot与springCloud 区别4.springBoot的核心配置文件有哪些&#xff0c;作用是什么5.springBoot配置文件有几种类型&#xff0c;区别是什么6.什么是热部署&#xff1f;springBoot怎么实现热…...

php网站开发示例/市场推广和销售的区别

非阻塞Connect对于select时应注意问题 http://www.cnitblog.com/zouzheng/archive/2010/11/25/71711.html 对于面向连接的socket类型(SOCK_STREAM,SOCK_SEQPACKET)在读写数据之前必须建立连接&#xff0c;首先服务器端socket必须在一个客户端知道的地址进行监听,也就是创建soc…...

可信赖的深圳网站建设/2021小学生新闻摘抄

/*** 选择排序的思想&#xff1a;* 每次从待排序列中找到最小的元素&#xff0c;* 然后将其放到待排的序列的最左边&#xff0c;直到所有元素有序** 选择排序改进了冒泡排序&#xff0c;将交换次数从O(N^2)减少到O(N)* 不过比较次数还是O(N)*/package al;public class SelectSo…...

建小网站多少钱/做seo推广一年大概的费用

国内it软件外包公司排行榜是怎么样的由于互联网技术的快速发展&#xff0c;特别是手机移动端的的普及&#xff0c;使得企业越来越需要开发自己自己的软件&#xff0c;但是软件开发人才缺口很大&#xff0c;企业没有这个技术实力去开发自己的软件&#xff0c;对于中小企业也不好…...

带做骑传奇私服网站/一键建站免费

前言 上篇文章给大家分享了前10个spark的企业面试题2020年最新Spark企业级面试题【上】&#xff0c;今天后续来了&#xff0c;来分享剩下的那个几个面试题。也祝大家找到自己喜欢的工作&#xff0c;一起加油&#xff0c;编写不易 请给老哥一个一键三连吧。 一、手写Spark-Wor…...

网站分类标准/百度关键词竞价

本文讲的是我的碎碎念&#xff1a;Docker入门指南&#xff0c;【编者的话】之前曾经翻译过很多Docker入门介绍的文章&#xff0c;之所以再翻译这篇&#xff0c;是因为Anders的角度很独特&#xff0c;思路也很调理。你也可以看下作者的演讲稿《Docker&#xff0c; DevOps的未来》…...

唐山网站建设拓/宁德市

1、您认为做好测试用例设计工作的关键是什么&#xff1f; 白盒测试用例设计的关键是以较少的用例覆盖尽可能多的内部程序逻辑结果 黑盒法用例设计的关键同样也是以较少的用例覆盖模块输出和输入接口。不可能做到完全测试&#xff0c;以最少的用例在合理的时间内发现最多的问题…...