线程间通信线程间的通信一般有两种方式进行,一是通过消息传递,二是共享内存。java 线程间的通信采用的是共享内存方式,jmm 为共享变量提供了线程间的保障。如果两个线程都对一个共享变量进行操作,共享变量初始值为 1,每个线程都变量进行加 1,预期共享变量的值为 3。在 jmm 规范下会有一系列的操作。我们直接来看下图:
在多线程的情况下,对主内存中的共享变量进行操作可能发生线程安全问题,比如:线程 1 和线程 2 同时对同一个共享变量进行操作,执行+1操作,线程 1 、线程2 读取的共享变量是否是彼此修改前还是修改后的值呢,这个是无法确定的,这种情况和cpu的高速缓存与内存之间的问题非常相似
如何实现主内存与工作内存的变量同步,为了更好的控制主内存和本地内存的交互,java 内存模型定义了八种操作来实现:
lock:锁定。作用于主内存的变量,把一个变量标识为一条线程独占状态。unlock:解锁。作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。read:读取。作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用load:载入。作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。工作内存即本地内存。use:使用。作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。assign:赋值。作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。store:存储。作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。write:写入。作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。重温java 并发三大特性原子性原子性:即一个或者多个操作作为一个整体,要么全部执行,要么都不执行,并且操作在执行过程中不会被线程调度机制打断;而且这种操作一旦开始,就一直运行到结束,中间不会有任何上下文切换(context switch) 比如:
int i = 0; //语句1,原子性i++; //语句2,非原子性语句1大家一幕了然,语句2却许多人容易犯迷糊,i++ 其实可以分为3步:
i 被从局部变量表(内存)取出,压入操作栈(寄存器),操作栈中自增使用栈顶值更新局部变量表(寄存器更新写入内存)执行上述3个步骤的时候是可以进行线程切换的,或者说是可以被另其他线程的 这3 步打断的,因此语句2不是一个原子性操作
在 java 中,可以借助synchronized 、各种 lock 以及各种原子类实现原子性。synchronized 和各种lock是通过保证任一时刻只有一个线程访问该代码块,因此可以保证其原子性。各种原子类是利用cas (compare and swap)操作(可能也会用到 volatile或者final关键字)来保证原子操作。
可见性可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看到修改的值。我们来看一个例子:
public class visibilitytest { private boolean flag = true; public void change() { flag = false; system.out.println(thread.currentthread().getname() + ,已修改flag=false); } public void load() { system.out.println(thread.currentthread().getname() + ,开始执行.....); int i = 0; while (flag) { i++; } system.out.println(thread.currentthread().getname() + ,结束循环); } public static void main(string[] args) throws interruptedexception { visibilitytest test = new visibilitytest(); // 线程threada模拟数据加载场景 thread threada = new thread(() -> test.load(), threada); threada.start(); // 让threada执行一会儿 thread.sleep(1000); // 线程threadb 修改 共享变量flag thread threadb = new thread(() -> test.change(), threadb); threadb.start(); }}threada 负责循环,threadb负责修改 共享变量flag,如果flag=false时,threada 会结束循环,但是上面的例子会死循环。原因是threada无法立即读取到共享变量flag修改后的值。我们只需 private volatile boolean flag = true;加上volatile关键字threada就可以立即退出循环了。
java中的volatile关键字提供了一个功能,那就是被其修饰的变量在被修改后可以立即同步到主内存,被其修饰的变量在每次是用之前都从主内存刷新。
因此,可以使用volatile来保证多线程操作时变量的可见性。除了volatile,java中的synchronized和final两个关键字 以及各种 lock也可以实现可见性。
有序性有序性:即程序执行的顺序按照代码的先后顺序执行。
int i = 0;int j = 0;i = 10; //语句1j = 1; //语句2但由于指令重排序问题,代码的执行顺序未必就是编写代码时候的顺序。语句可能的执行顺序如下:
语句1 语句2语句2 语句1指令重排对于非原子性的操作,在不影响最终结果的情况下,其拆分成的原子操作可能会被重新排列执行顺序。 指令重排不会影响单线程的执行结果,但是会影响多线程并发执行的结果正确性 。在java 中,可以通过volatile关键字来禁止指令进行重排序优化,详情可见:https://mp.weixin.qq.com/s/tyicfvmeedwa-2hd9n9xjq。也可以使用synchronized关键字保证同一时刻只允许一条线程访问程序块。
参考资料:
《java并发编程实战》
https://www.cnblogs.com/czwbig/p/11127124.html
https://www.cnblogs.com/jelly12345/p/14609657.html
https://www.cnblogs.com/bailiyi/p/11967396.html
开关电源电感值的确定
小米MIX3怎么样 3299元值不值买
购买破壁机掌握这3点,双十一轻松下单不纠结
好视角智能机柜锁总览
分线盒的作用和位置
Java运行时内存区域与硬件内存的关系2
常州电网基建共44项目,承诺实现全市配电自动化全覆盖
Camon16可能会首先在印度的商店首次正式亮相
中国移动香港宣布,正式实现商用5G独立组网
微软Windows 10 21H2更新改进将在2021年下半年开始向消费者推出
思尔芯助力中微电自研高性能安全GPU芯片开发
传感器脉冲频率计数远程采集WiFi模块 支持NPN和PNP
智能手环的优势_智能手环的市场分析_戴智能手环的危害
解决电池组件中过量电流“泄漏”问题
IGBT高压大功率驱动和保护电路的应用研究
人工智能未来悬而未决的五大问题浅析
意法半导体和格芯将在法国新建12英寸晶圆厂
可信计算+区块链会有怎样的效果
诺基亚6之后, 诺基亚3也来了, 还有兴趣吗
2018全球十大品牌揭晓,Amazon、微软、脸书均榜上有名