03java内存模型与线程安全

Java内存模型与线程安全

原子性

有序性

可见性

Happen-Before

线程安全的概念

经典的理论模型

Java内存模型,老的解释(只是理论上的模型,有点过时了,实际中会有行为以及物理条件等的制约):

Java内存模型,会有一个主存,每个工作线程都有一份自己的工作内存;数据在主存中会有一份,在java每个工作线程的工作内存中也各有一份;在主存和工作内存的数据,通过各种原子操作,load,store等进行同步。

并发操作的三大特性

附加参考:【Java】VarHandle解析 附加参考:Java 9 变量句柄-VarHandle 附加参考:Java魔法类:Unsafe应用解析

(下面这几段话,以及上面的附加参考,可以再之后有更深理解之后再返回来看。)

并发操作里面的三大特性:原子性、可见性、有序性。

volatile变量可以保证可见性、有序性(防止指令重拍),加锁或者原子操作、CAS等可以保证原子性,只有同时满足这三个特性才能够保证对于一个变量的并发操作是合乎预期的。

加锁的话需要对线程进行同步,而线程上下文切换之间带来的开销是很大的,所以这里不予考虑,考虑一下几种方式:

使用Atomic包下的原子类进行间接管理,但增加了开销,也可能导致额外的问题如ABA问题; 使用原子性的FieldUpdaters,利用反射机制,开销也会增大; 使用sun.misc.Unsafe提供的JVM内置函数,但直接操作JVM可能会损害安全性和可移植性; 针对以上的问题,VarHandle就是用来替代上述方式的一种方案,它提供了一系列标准的内存屏障操作,用于更细粒度的控制指令排序,在安全性、可用性、性能等方面都要优于现有的AIP,且基本上能够和任何类型的变量相关联。

原子性

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

i++是原子操作吗?不是。读,++行为,写。有这三个过程。

32位java虚拟机上读写64位long型数据,也不是原子操作。

有序性

在并发时,程序的执行可能就会出现乱序。

image-20230306232643320

程序执行时的顺序不一定按书写的顺序执行。比如以上代码writer方法中 a=1;flag=true;两句实际执行时就可能颠倒。

一条(hui )指令的执行分成很多步骤

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

流水线执行

image-20230307000105229
image-20230307001817446

为了使流水线尽量不等待(尽量少X,尽量少气泡),编译时可能会调整指令的执行顺序(指令重排)

image-20230307002117163
image-20230307002150169

指令重排的目的是使指令执行更加顺畅。

指令重排的一个原则是不能破坏串行语义的一致性。

可见性

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

成因比较复杂。更像是一个系统性的问题。可能由各个环节产生。

eg: 指令重排等 编译器优化(变量优化到不同的寄存器等) 硬件优化(比如写到硬件队列里,多次写同一个值可能认为没有意义被优化,如写吸收,批操作) Java虚拟机层面的可见性问题

image-20230307224558677

Happen-Before规则

Java内存模型中十分重要的规则(Happen-Before规则)

从Java语言规范中总结出来的:

image-20230307230837337

线程安全的概念

指某个函数、函数库在多线程环境中被调用时,能正确处理各个线程的局部变量,使程序能正确完成。

image-20230307231445511

评论

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注