java--线程-线程安全的原因

来源:互联网 发布:韩美林 知乎 编辑:程序博客网 时间:2024/06/07 09:35

多线程的特性

内存模型的共享性/可见性

这里写图片描述

如上图所示:

同一进程的多个线程共享同一片存储空间所有的变量都存放在主内存中,每个线程都有一个自己的工作内存(相当于CPU高级缓冲区)对于共享变量,线程每次读取的是工作内存中共享变量的副本,写入的时候也直接修改工作内存中副本的值,然后在某个时间点上再将工作内存与主内存中的值进行同步。这就导致,线程间共享同一份数据,但是数据却不能实时相同。

多线程的非原子性

多线程中,线程的操作不一定是原子性操作,原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会切换到另一个线程。比如,整数 i++ 的操作,其实需要分成三个步骤:(1)读取整数 i 的值;(2)对 i 进行加一操作;(3)将结果写回内存

JVM的重排序

  为了提高性能,编译器和处理器可能会对指令做重排序。

(1)编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。(2)指令级并行的重排序。现代处理器采用了指令级并行技术(Instruction-Level Parallelism, ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。

线程安全(并发问题)

由于多线程对共享变量的操作不是原子性实时的.造成数据安全问题就是线程安全(并发问题);

竞态条件 & 临界区

当两个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件。导致竞态条件发生的代码区称作临界区。上例中add()方法就是一个临界区,它会产生竞态条件。在临界区中使用适当的同步就可以避免竞态条件。

举例

想象下线程A和B同时执行同一个Counter对象的add()方法

public class Counter {    protected long count = 0;    public void add(long value){        this.count = this.count + value;      }}

非原子性

JVM并不是将这段代码视为单条指令来执行的,而是按照下面的顺序:从内存获取 this.count 的值放到寄存器将寄存器中的值增加value将寄存器中的值写回内存

共享和不可见性

线程都是把主内存中的数据值读取到自己的内存上进行操作.操作完了以后,随机同步到主内存

所以说可能的执行顺序是这样的

this.count = 0;  A:    读取 this.count 到一个寄存器 (值为0)  B:    读取 this.count 到一个寄存器 (值为0)  B:    将寄存器的值加2  B:    回写寄存器值(值为2)到内存. this.count 现在等于 2  A:    将寄存器的值加3  A:    回写寄存器值(值为3)到内存. this.count 现在等于 3

参考

内存模型
http://ifeve.com/java-memory-model-6/
http://www.cnblogs.com/paddix/p/5374810.html
http://ifeve.com/race-conditions-and-critical-sections/
http://www.jianshu.com/p/15f9f54f8e3f

原创粉丝点击