一、Java内存模型
创建这样一个模型主要是为了定义程序中各个变量的访问规则。这也是Java跨平台性的一个重要组成部分。因为例如C/C++等语言都是直接使用物理硬件和操作系统的内存模型,会出现在这个平台能够正常访问数据,换一个之后就报错访问不到或者访问到错误数据。
图中的内存这些和前面的堆,栈,方法区,划分层次不同基本没有什么关系
特征
- 原子性
在一个操作中,CPU不可以在中途停止然后再调度,即不被中断操作,要么执行完成,要么不执行 - 可见性
当多线程访问同一个变量,一个线程修改了该变量的值,其他线程可立即看到修改的值 - 有序性
当多线程访问同一个变量,一个线程修改了该变量的值,其他线程可立即看到修改的值。
Java天然有序性:在本程序重观察,所有的操作都是有序的;在一个线程中观察另一个线程,所有的操作都是无序的。前半句指“线程内表现为串行语义”,后半句指“指令重排序”现象和“工作内存与主内存同步延迟”现象。
保证原子性的8钟操作
操作 | 作用范围 | 描述 |
---|---|---|
lock(锁定) | 主内存 | 把一个变量标记为一条线程独占状态 |
unlock(解锁) | 主内存 | 将一个处于锁定状态的变量释放出来,释放后的变量才能够被其他线程锁定 |
read(读取) | 工作内存 | 把变量值从主内存传送到线程的工作内存中,以便随后的load动作使用 |
load(载入) | 工作内存 | 它把read操作的值放入工作内存中的变量副本中 |
use(使用) | 工作内存 | 把工作内存中的值传递给执行引擎,每当虚拟机遇到一个需要使用这个变量的指令时候,将会执行这个动作 |
assig(赋值) | 工作内存 | 把从执行引擎获取的值赋值给工作内存中的变量,每当虚拟机遇到一个给变量赋值的指令时候,执行该操作 |
store(存储) | 工作内存 | 把工作内存中的一个变量传送给主内存中,以备随后的write操作使用 |
write(写) | 主内存 | 把store传送值放到主内存中的变量中 |
- read和load,store和write要按顺序执行,且不能单一出现
- use、store操作之前,必须先执行load、assign,即新变量只能在主内存中新生
- lock一个变量,将会清空工作内存中此变量的值,在需要他的时候重新执行load或者assig对其初始化值
二、volatile变量
最轻量的同步机制
两种特性:
- 被修饰的变量对所有线程有可见性。
在对volatile做修改时相当于对变量做了内存交互操作的store和write操作,由此对其他CPU立即可见
- 禁止指令重排序
通过内存屏障来实现,即重排序时不能把后面的指令重排序到内存屏障之前的位置。
指令重排序:为了尽可能减少内存操作速度远慢于CPU运行速度所带来的CPU空置的影响,虚拟机会按照自己的一些规则(这规则后面再叙述)将程序编写顺序打乱——即写在后面的代码在时间顺序上可能会先执行,而写在前面的代码会后执行——以尽可能充分地利用CPU
volatile修饰的变量只能保证可见性而不能保证原子性,任然需要通过加锁才能保证原子性,因此volatile变量只有在满足以下条件才使用
- 运算结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值。
- 变量不需要与其他的状态变量共同参与不变约束。
volatile变量读写性能消耗与普通变量相差无多,而且大多数情况下volatile变量的总开销要比锁低。
三、其他关键字的可见性
synchronized和final也能实现可见性,同步块的可见性是对一个变量执行unlock之前,必须把此变量同步回主内存中(store、write)。而被final修饰的字段在构造器中一旦初始化完成,并且构造器没有把this的引用传递出去,那其他线程中就能看见final字段的值
四、先行发生原则
总不可能所有的有序性都用关键子来规定。先行发生是判断数据是都存在竞争,线程是否安全的主要依据。比如操作A产生的影响(改变值,发送消息,调用等)能被操作B观察到,则A必然是要先于B发生的。
//A线程发生
i=1;
//B线程发生
j=i;
//C线程发生
i=2;
没有C时,J必然是1,有了C,C和B没有先行发生,那么执行的顺序时A->B->C还是A->C->B就不能确定了。
天然先行发生关系
- 程序次序规则
- 管程锁定规则
- volatile变量规则
- 线程启动规则
- 线程终止规则
- 线程中断规则
- 对象终结规则
- 传递性
“时间上的先发生”不一定代表“先行发生”
五、Java与线程
线程的实现
- 内核线程实现(1对1)
内核线程(KTL),操作系统内核支持的线程。程序一般不会直接使用内核线程,而是去使用内核现成的高级接口——轻量级进程(LWP),也就是我们通常意义上的线程
这样系统调用的代价相对较高,且每个轻量级进程都要内核线程支持,要消耗一定的内核资源,因此一个系统支持轻量级进程的数量是有限的。 - 用户线程实现(1对多)
只要不是内核线程的都是用户线程(UT)
因为没有内核线程的支援,所有的操作都要用户程序自己处理 - 用户线程加轻量级进程混合实现(多对多)
混合使用