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

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

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

相关文章:

  • Spring事件机制
  • vue+canvas音频可视化
  • 俊昭stm32笔记
  • W30-python03-pytest+selenium+allure访问百度网站实例
  • 如何在 Debian 8 上安装和使用 PostgreSQL 9.4
  • 【微信小程序】微信小程序设置本地背景图片在真机无法显示的解决方案
  • Arthas在线诊断案例实战整理
  • 使用 XRDP 远程linux主机
  • 学习小型gpt源码(自用)
  • @Transactional使用的注意事项
  • 快手可灵视频生成大模型全方位测评
  • 【JavaScript】`Map` 数据结构
  • Ubuntu22.04使用NVM安装多版本Node.js和版本切换
  • 基于C51和OLED12864实现Goole小恐龙
  • 【Docker】CentOS7环境下的安装
  • 浏览器的最大并发数(http1.1)
  • Android 开发中px、dpi 和 dp三个单位的介绍
  • zookeeper开启SASL权限认证
  • mysql一个小问题引发的思考-mysql类型转换-查询缓存 及 MYSQL查询缓存以及自动选择不使用查询缓存的情况
  • css更改图片颜色
  • 通过POST请求往Elastic批量插入数据
  • JAW:一款针对客户端JavaScript的图形化安全分析框架
  • 错误解决 error CS0117: ‘Buffer‘ does not contain a definition for ‘BlockCopy‘
  • ICMPv6与DHCPv6之网络工程师软考中级
  • 【HTML — 构建网络】HTML 入门
  • javafx的ListView代入项目的使用
  • 基于ABAP OLE技术实现对服务器文件进行读写操作
  • 求教Postgresql在jdbc处理bit(1)字段的预处理解决方案
  • 微信小程序-自定义tabBar
  • vue3+element-plus 实现动态菜单和动态路由的渲染