Java高并发理论基础

并发级别

由于临界区的存在,多线程之间的并发必须受到控制。根据控制并发的策略,我们可以把并发的级别分为 阻塞、无饥饿、无障碍、无锁、无等待 几种。

阻塞

一个线程是阻塞的,那么在其他线程释放资源之前,当前线程无法继续执行。当我们使用sychronized关键字或者重入锁时,我们得到的就是阻塞的线程。

sychronized关键字和重入锁(ReentrantLock)都试图在执行后续代码之前,得到临界区的锁,如果得不到,线程就会被挂起等待,直到占有了所需资源为止。

无饥饿

如果线程之间是有优先级的,那么线程调度的时候总是倾向于满足高优先级的线程。即,对于同一个资源的分配,是不公平的。

对于非公平锁,系统允许高优先级的线程插队,这样有可能导致低优先级线程产生饥饿。

如果锁是公平的,按照先来后到的原则,那么饥饿就不会产生。所有线程都有机会执行。

无障碍

无障碍是一种最弱的非阻塞调度(乐观锁)。两个线程无障碍的执行,那么不会因为临界区的问题导致一方被挂起,对于无障碍的线程来说,双方共同修改临界区的资源,如果一方把资源修改坏,会立即对自己所做的修改进行回滚,确保数据安全。

阻塞的控制方式属于悲观策略,无障碍(非阻塞)的调度就是一种乐观策略。

一种可行的无障碍实现可以依赖一个“一致性标记”(版本号或者CAS机制都依赖于这种实现),线程在操作之前,先读取并保存这个标记,在操作完成后,再次读取,检查这个标记是否被更改过,如果两者一致,则说明资源访问没有冲突。如果不一致,则重试操作。

无锁

无锁的并行都是无障碍的。在无锁的情况下,所有的线程都能尝试对临界区进行访问,但不同的是,无锁的并发保证必然有一个线程能够在有限步内完成操作离开临界区。

在无锁的调用中,一个典型的特点是可能会 包含一个无穷循环。在这个循环中,线程会不断尝试修改共享变量。如果没有冲突,修改成功,程序退出,否则继续尝试修改。 但无论如何,无锁的并行总能保证有一个线程是可以胜出的。

如下:
在这里插入图片描述

无等待

无锁只要求有一个线程可以在有限步骤内完成操作,而无等待要求所有线程必须在有限步骤内完成。这样就不会引起饥饿问题。如果限制这个步骤的上限,还可以进一步分解为有界无等待和线程数无关的无等待几种。

一种典型的无等待结构是RCU(Read Copy Update).他的基本思想是,对数据的读不加控制,在写数据的时候,先取得原始数据的副本,接着只修改副本数据,修改完成后,在合适的时机写回数据。(COW 写时复制)

Java内存模型(JMM)

JMM的关键技术点都是围绕着多线程的原子性、可见性和有序性来建立的。

原子性

原子性指一个操作是不可中断的。即使在多个线程在一起执行的时候,一个操作一旦开始就不会被其他线程干扰。

可见性

可见性是指当一个线程修改了某一个共享变量的时候,其他线程是否能够立即知道这个修改。

在CPU1和CPU2上各运行一个线程,他们共享变量t,由于编译器优化或者硬件优化的缘故,在CPU1上的线程将变量t缓存到cache或者寄存器中,在这种情况下,CPU2上的线程对共享变量做了修改,CPU1上的线程无法意识到这种改动,依然会读取缓存值。因此,产生了可见性问题。

有序性

有序性问题的原因是程序在执行时,可能会进行指令重排,重排后的指令与原本的顺序未必一致。

串行执行的线程不存在指令重排问题,但是多线程间可能会存在,且是否发生指令重排不可预见。

为什么会发生指令重排

之所以发生指令重排,完全是出于性能考虑。一条指令执行是可以分为很多步骤,如下

  • 取址IF
  • 译码和取寄存器操作数ID
  • 执行或者有效地址计算EX
  • 存储器访问MEM
  • 写回WB

在CPU实际工作中,汇编指令也需要多个步骤依次执行。由于每个步骤都可能使用不同的硬件来完成,如取址时用到PC寄存器或者存储器。执行时用到ALU(算术逻辑单元)。为了提高性能,发明了流水线技术来执行指令。
在这里插入图片描述
可以看出,当第二条指令执行时,指令一并未执行完成,有了流水线,CPU才能真正高效的执行。但是,流水线总是害怕被中断。流水线满载时,性能确实相当不错,但是 一旦中断,所有的硬件设备都会进入一个停顿期,再次满载又需要几个周期。因此,性能损失就很大,所以,必须想办法尽量不让流水线中断。之所以需要指令重排,就是为了尽量减少中断流水线。

示例如下:

执行操作

a = b+c;
d = e-f;

指令重排前执行过程
在这里插入图片描述
由于ADD、SUB操作都需要等待上一条指令的结果,因此,在这里插入了不少停顿。通过将LW Rf,f和LW Re,e移动到前面执行即可。因为先加载e和f对程序是没有影响的,既然在Add的时候要停顿一下,那么停顿的时间不如去做点有意义的事情,指令重排后如下:
在这里插入图片描述
由此可见,指令重排对于CPU处理性能是十分必要的。

哪些指令不能重排:Happen-Before规则

Java虚拟机和执行系统会对指令进行一定的重排,但是指令重排是有原则的,并非所有的指令都可以随便改变执行位置。以下罗列了一些基本原则:

  1. 程序顺序原则:一个线程内保证语义的串行性。
  2. volatile原则:volatile变量的写先于读发生,这保证了volatile变量的可见性(当一个线程修改volatile变量时,新的值会被立即刷新到主内存中,并且其他线程能够立即看到这个更新
  3. 锁规则:解锁必然发生在随后的加锁前。
  4. 传递性:A先于B,B先于C,那么A必然先于C。
  5. 线程的start()方法先于他的每一个动作。
  6. 线程的所有操作先于线程的终结(Thread.join())
  7. 线程的中断先于被中断线程的代码
  8. 对象的构造函数的执行、结束先于finalize()方法。

Unsafe类

Unsafe类中的重要方法

Unsafe类提供了硬件级别的原子性操作,Unsafe类中的方法都是native方法。核心方法如下

  • boolean compareAndSwapLong(Object var1, long offset, long expect, long update): 比较对象obj中 偏移量为offset的变量的值是否与expect相等,相等则使用update值更新,然后返回true,否则返回false。
  • void park(boolean isAbsolute,long time) 方法:阻塞当前线程,其中参数isAbsolute等于false且time等于0表示一直阻塞。time大于0表示等待指定的time后,阻塞线程会被唤醒。其他线程调用了当前阻塞线程的interrupt方法或者unpark方法,阻塞的线程都会返回。
  • void upark(Object thread)方法:唤醒调用park后阻塞的线程。

伪共享

什么是伪共享

为了解决计算机系统中主内存和CPU之间的运行速度差,会在CPU和主内存之间添加一级或者多级高速缓冲存储器。这个Cache一般是被集成到CPU内部的,所以也叫CPU cache。

下图展示了两级Cache结构:
在这里插入图片描述
在Cache内部,数据是按行存储的,其中每一行称为一个Cache行。Cache行是Cache和主内存进行数据交换的单位,Cache行大小一般为2的幂次数字节。
在这里插入图片描述
当CPU访问某个变量时,首先会去看CPU Cache内是否有该变量,如果有则直接从中获取,否则就去主内存里面获取该变量,然后把该变量所在内存区域的一个cache行大小的内存复制到cache中。由于存放到cache行的是内存块而不是单个变量,所以可能会把多个变量存放到一个cache行中。当多个线程同时修改一个缓存行里面的多个变量时,由于同时只能有一个线程操作缓存行,所以相比将每个变量都放入一个缓存行,性能会有所下降,这就是伪共享。

在这里插入图片描述
在该图中,变量x和y同时被放到了CPU一级和二级缓存,当线程1使用CPU1对变量x进行更新时,首先会修改CPU1的一级共享变量x所在的缓存行,这时候在缓存一致性协议下,CPU2中变量x对应的缓存行失效。那么线程2在写入变量x时就只能去二级缓存里找,这就破坏了一级缓存。而一级缓存比二级缓存更快,这也说明了多个线程不可能同时去修改自己所使用的CPU中相同缓存行里面的变量。更坏的情况是,如果CPU只有一级缓存,则会导致频繁的访问主内存。

为何会出现伪共享

伪共享的产生是因为多个变量被放入了一个缓存行中,并且多个线程同时去写入缓存行中不同的变量。为何多个变量会被放入一个缓存行呢?因为缓存与内存交换数据的单位就是缓存行,当CPU要访问的变量没有在缓存中找到时,根据程序运行的局部性原理,会把该变量所在内存中大小为缓存行的内存放入缓存行。

long a;
long b;
long c;
long d;

如上代码声明了4个long变量,假设缓存行的大小为32字节,那么当CPU访问变量a时,发现该变量没有在缓存中,就会去主内存把变量a以及内存地址附近的b、c、d放入缓存行。当创建数组时,数组里面的多个元素也会被放入同一个缓存行。

在正常情况下,但线程访问时将数组元素放入一个或者多个缓存行对代码执行是有利的,因为数组元素都在缓存中,代码执行会更快。

但是,在多线程情况下,一个缓存行中的多个变量修改就会竞争缓存行,从而降低程序性能。

如何避免伪共享

在JDK8之前一般是通过字节填充的方式来避免该问题,也就是创建一个变量时使用填充字段填充该变量所在的缓存行,如下:

public final static class FilledLong{public volatile long value = 0l;public long p1,p2,p3,p4,p5,p6;
}

假如缓存行为64字节,那么在FilledLong里面填充6个long类型变量,每个long类型占8字节,加上value的8字节,以及类对象的对象头占用8字节,所以一个FilledLong对象实际会占用64字节。这样就正好可以放入一个缓存行。

JDK8提供了sun.misc.Contended注解,用来解决伪代码共享问题。如下:

@sun.msic.Contended
public final static class FilledLong{public volatile long value = 0l;
}

@sun.msic.Contended默认情况下只用于Java核心类,如果用户类路径下的类使用这个注解,需要添加JVM参数 -XX:-RestrictContended.填充的宽度默认为128,要自定义宽度则可以设置-XX:ContendedPaddingWidth参数。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/3267282.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

Java面试八股之什么是声明式事务管理,spring怎么实现声明式事务管理?

什么是声明式事务管理,spring怎么实现声明式事务管理? 声明式事务管理是一种编程范式,它允许开发人员通过声明性的配置或注解,而不是硬编码事务处理逻辑,来指定哪些方法或类应该在其上下文中执行事务。这种方法将事务…

【Python机器学习】决策树的构造——递归构建决策树

我们可以采用递归的原则处理数据集,递归结束的条件是:程序遍历完所有划分数据集的属性,或者每个分支下的所有实例都具有相同的分类。如果所有实例具有相同的分类,则得到一个叶子节点或者终止块。任何到达叶子节点的数据必然属于叶…

Linux中的三类读写函数

文件IO和标准IO的区别 遵循标准: 文件IO遵循POSIX标准,主要在类UNIX环境下使用。标准IO遵循ANSI标准,具有更好的可移植性,可以在不同的操作系统上重新编译后运行。可移植性: 文件IO的可移植性相对较差,因为…

MAC、ARP、NAT

文章目录 MACMAC帧格式MAC地址 ARP协议ARP的数据包格式 NAT和代理IP地址的数量限制NAT代理服务器 MAC 数据在局域网中如何发送就是数据链路层解决的。 MAC帧格式 源地址和目的地址是指网卡的硬件地址(也叫MAC地址), 长度是48位,是在网卡出厂时固化的。 帧协议类型字段有三种值…

AI在招聘领域的应用:提高效率、降低成本与增强候选人体验

一、引言 在数字化浪潮席卷全球的今天,人工智能(AI)技术已经渗透到社会的各个领域,其中也包括人力资源管理中的招聘环节。AI技术的引入不仅为企业带来了前所未有的便利,更在提升招聘效率、降低成本以及增强候选人体验等…

Spring Boot入门指南:留言板

一.留言板 1.输⼊留⾔信息,点击提交.后端把数据存储起来. 2.⻚⾯展⽰输⼊的表⽩墙的信息 规范: 1.写一个类MessageInfo对象,添加构造方法 虽然有快捷键,但是还是不够偷懒 项目添加Lombok。 Lombok是⼀个Java⼯具库,通过添加注…

【年报文本分析】Python+Selium获取互动易平台投资者提问与上市公司回应文本数据

目录 序言excel文件准备全部代码 序言 互动易平台链接:https://irm.cninfo.com.cn/views/interactiveAnswer 需要提前下载好三个库,都可以用pip install轻松下载,稍微麻烦点儿的是需要去下载个对应版本的chromedriver.exe驱动,放…

Android 生成Excel并导出全流程

前言 最近接到需求,要在安卓上离线完成根据数据生成Excel文件,但搜到了都不是能立马使用 例如 // implementation org.apache.poi:poi:3.17 // implementation com.alibaba:easyexcel:4.0.1 这两最大的问题是专用于java的,如果And…

git sendemail使用

教程参考: git-send-email - 以电子邮件形式发送补丁集 1、安装git-email 2、配置 SMTP 服务器 git config --global sendemail.smtpserver smtp.163.com git config --global sendemail.smtpserverport 465 git config --global sendemail.smtpuser xxxxxx163.c…

恒创科技:如何排查和解决服务器高负载问题?

服务器负载过高是业务正常运行的主要威胁之一。高平均负载意味着系统或服务器超载,许多进程正在等待 CPU 时间。因此将有许多进程等待完成,这些进程将进入队列,导致服务器负载增加。 高负载的主要原因 1.服务器超载 2.垃圾邮件 3.硬盘性能 4.…

Navicat Premium Lite数据库管理软件

首先我们大家可以通过下面的地址下载我们的Navicat Premium Lite: https://www.navicat.com.cn/products/navicat-premium-lite 1、Navicat精简版 2、下载 3、运行界面 。。。

前端面试项目细节重难点分享(十三)

面试题提问:分享你最近做的这个项目,并讲讲该项目的重难点? 答:最近这个项目是一个二次迭代开发项目,迭代周期一年,在做这些任务需求时,确实有很多值得分享的印象深刻的点,我讲讲下面…

【Linux】基本指令1

文章目录 1. find2. which3. alias4.whereis5.grep6. sort 、uniq7. linux怎么编辑文件中的内容 1. find 1.1 find: 查找文件所在的位置。(当我们进行find搜索的时候,可能需要访问磁盘进而导致效率低下。) 1.2 find / file.txt 在Linux系统是…

Qt自定义MessageToast

效果&#xff1a; 文字长度自适应&#xff0c;自动居中到parent&#xff0c;会透明渐变消失。 CustomToast::MessageToast(QS("最多添加50张图片"),this);1. CustomToast.h #pragma once#include <QFrame>class CustomToast : public QFrame {Q_OBJECT pub…

MinIO存储桶通知 - Kafka小测

概述 公司的某个项目需要用上这玩意&#xff0c;所以在本地搭建测试环境&#xff0c;经过一番折腾&#xff0c;测试通过&#xff0c;博文记录&#xff0c;用以备忘 MinIO安装 该节不做说明&#xff0c;网络有很多现成的帖子&#xff0c;自行搜索去 配置步骤 控制台添加事件…

ROS2入门到精通—— 2-11 ROS2实战:实现基于voronoi_planner的全局规划(一)!!!保姆级教程

实现基于voronoi_planner的全局规划将分为两篇博文进行讲解 本文参考该大佬代码: https://github.com/nkuwenjian/voronoi_planner.githttps://github.com/nkuwenjian/voronoi_layer.git将上面的ROS1代码移植到ROS2,移植不易,中间遇到很多坑 0 前言 针对一些狭窄区域,可能…

海山数据库(He3DB)性能优化方案解析

前端优化是一个永恒的话题&#xff0c;每个前端开发者都希望自己的页面能够快速加载&#xff0c;给用户良好的体验。但往往事与愿违。因此&#xff0c;本文从编码优化、构建优化、部署优化三方面入手进行web页面性能优化。 1. 编码优化 1.1. Css优化 1.1.1. 合理使用css选择…

谷粒商城实战笔记-60-商品服务-API-品牌管理-效果优化与快速显示开关

文章目录 一&#xff0c;显示状态列改为switch开关二&#xff0c;监听状态改变 首先&#xff0c;把ESLint语法检查关掉&#xff0c;因为这个语法检查过于严格&#xff0c;在控制台输出很多错误信息&#xff0c;干扰开发。 在build目录下下webpack.base.conf.js中&#xff0c;把…

python基础巩固

基本数据类型 可以用isinstance来判断 a111 isinstance(a,int) True数值运算&#xff1a; >>> 2 / 4 # 除法&#xff0c;得到一个浮点数 0.5 >>> 2 // 4 # 除法&#xff0c;得到一个整数 0 >>> 17 % 3 # 取余 2Python 字符串不能被改变。向一个…

【LLM】-08-搭建问答系统-语言模型,提问范式与 Token

目录 1、语言模型 1.1、训练过程&#xff1a; 1..2、大型语言模型分类&#xff1a; 1.3、指令微调模型训练过程&#xff1a; 2、Tokens 3、Helper function辅助函数 (提问范式) 4、计算token数量 1、语言模型 大语言模型&#xff08;LLM&#xff09;是通过预测下一个词…