多线程之二:java内存模型和线程安全

 2019-12-10 16:13  阅读(1033)
文章分类:Java Core

整理自炼数成金

源码连接:

1.线程安全是什么:

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

2.内存模型

– 每一个线程有一个工作内存和主存独立

– 工作内存存放主存中变量的值的拷贝

20191210001667\_1.png

当数据从主内存复制到工作存储时,必须出现两个动作:第一,由主内存执行的读(read)操作;第二,由工作内存执行的相应的load操作;当数据从工作内存拷贝到主内存时,也出现两个操作:第一个,由工作内存执行的存储(store)操作;第二,由主内存执行的相应的写(write)操作。

对于普通变量,一个线程中更新的值,不能马上反应在其他变量中

如果需要在其他线程中立即可见,需要使用 volatile 关键字

3.由于内存模型而引出线程安全的一些特性:

– 原子性

原子性是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就 不会被其它线程干扰。而且CPU执行指令是一条一条的,所以在应用里同时对一个对象进行读写,都不会影响一致性。

– 可见性

一个线程修改了变量,其他线程可以立即知道。

– 保证可见性的方法

–volatile

– synchronized (unlock之前,写变量值回主存)

– final(一旦初始化完成,其他线程就可见)

– 有序性

在本线程内,操作都是有序的

在线程外观察,操作都是无序的。(指令重排 或 主内存同步延时)

指令重排

– 指令重排是JVM对字节码执行的一个优化

– 一条指令的执行是可以分为很多步骤的

– 取指 IF

– 译码和取寄存器操作数 ID

– 执行或者有效地址计算 EX

– 存储器访问 MEM

– 写回 WB

该语句需要9步才可以执行完,

20191210001667\_2.png

我们看下以下语句的指令正常的执行步骤

a=b+c

d=e-f

从该图可以看出,这2条语句需要执行14步

20191210001667\_3.png

我们再看下通过指令重排的执行步骤

从该图可以看出,重排后只需要12步就可以执行完

20191210001667\_4.png

– 指令重排的好处

可以使流水线更加顺畅

减轻处理压力,加快处理时间

– 线程内串行语义

• 写后读 a = 1;b = a; 写一个变量之后,再读这个位置。

• 写后写 a = 1;a = 2; 写一个变量之后,再写这个变量。

• 读后写 a = b;b = 1; 读一个变量之后,再写这个变量。

• 以上语句不可重排

• 编译器不考虑多线程间的语义

• 可重排: a=1;b=2;

–破坏线程间的有序性

[java]
view plain
copy

  1. class OrderExample {

  2. int a = 0;

  3. boolean flag = false;

  4. **public****void** writer() {

  5. a = 1;

  6. flag = true;

  7. }

  8. **public****void** reader() {

  9. if (flag) {

  10. int i = a +1;

  11. ……

  12. }

  13. }

  14. }

    线程A首先执行writer()方法

    线程B线程接着执行reader()方法

    线程B在int i=a+1 是不一定能看到a已经被赋值为1

    因为在writer中,两句话顺序可能打乱

–保证有序性的方法

[java]
view plain
copy

  1. class OrderExample {
  2. int a = 0;
  3. boolean flag = false;
  4. public**synchronized**void writer() {
  5. a = 1;
  6. flag = true;
  7. }
  8. public**synchronized**void reader() {
  9. if (flag) {
  10. int i = a +1;
  11. ……
  12. }
  13. }
  14. }

同步后,即使做了writer重排,因为互斥的缘故,reader 线程看writer线程也是顺序执行的。

–指令重排的基本原则

– 程序顺序原则:一个线程内保证语义的串行性

– volatile规则:volatile变量的写,先发生于读

– 锁规则:解锁(unlock)必然发生在随后的加锁(lock)前

– 传递性:A先于B,B先于C 那么A必然先于C

– 线程的start方法先于它的每一个动作

– 线程的所有操作先于线程的终结(Thread.join())

– 线程的中断(interrupt())先于被中断线程的代码

对象的构造函数执行结束先于finalize()方法

点赞(0)
版权归原创作者所有,任何形式转载请联系作者; Java 技术驿站 >> 多线程之二:java内存模型和线程安全

相关推荐