您的位置:首页 > 编程语言 > Java开发

Java高并发程序-Chapter1 并行世界 (第三讲)Java 内存模型 JMM

2018-03-27 08:19 288 查看


1. 原子性 (Atomicity)

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

i++是原子操作吗? 
不是 (读取  运算 和 赋值 三个操作)
 long 赋值 (8个字节 64位)

如果我们使用long型的话,对于32位系统来long型数据的读写不是原子性的(因为long有64位)

也就是说,如果两个线程同时对long进行写入的话(或者读取),对线程之间的结果是有干扰的package com.john.learn.high.concurent.ch01.jmm.atomicity;

public class MultiThreadLong {

public static long t = 0;

public static class ChangeT implements Runnable {

public ChangeT(long to) {
this.to = to;
}

public void run() {

while (true) {
t = to;
Thread.yield();
}
}

private long to;

}

public static class ReadT implements Runnable {

public void run() {

while (true) {

long temp = MultiThreadLong.t;

if (temp != 111l && temp != 333l && temp != -999l && temp != -444l) {

System.out.println(temp);
}

Thread.yield();
}
}

private long to;

}

public static void main(String[] args) {

/**
* 32为虚机
*/
new Thread(new ChangeT(111l)).start();
new Thread(new ChangeT(333l)).start();
new Thread(new ChangeT(-999l)).start();
new Thread(new ChangeT(-444l)).start();

new Thread(new ReadT()).start();
}
}

001000000000000000000000000000000000010
999=11111111111111111111111111111111111111111000001100
333=000000000000000000000000000000000000000000000010100110
44411111111111111111111111111111111111111111111001000100
+4294966852=000000000001111111111111111111111000
4294967185=111111111111111111111111111100000000000000000000000011

上面显示了这几个相关数字的补码形式,也就是在计算机内的真实存储内容不这个奇怪的4294966852,

其实是111或者333的前32位,与-444的后32位夹杂后42967185只99444的前32位与111夹杂后的数字

2. 可见性(Visibility)

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

显然,对于串行程序来说,可见性问题是不存在的。因为你在任何一个操作步骤中修改了某个变量,那么在后续的步骤中,读取这个变量的值,一定是修改后的新值
如果一个线程修改了某一个全局变量,那么其他线程未必可以马上知道这个改动。
图1.14展示了发生可见性问题的一种可能。
如果在CPU1和CPU2上各运行了一个线程,它们共享变量T,由于编译器优化或者硬件优化的缘故,在CPU上的线程将变量T进行了优化,将其缓存在Cache中或者寄存器里。
这种情况下,如果在CPU2上的某个线程修改了变量T的实际值,那么CPU1上的线程可能并无法意识到这个改动,依然会读取Cache中或者寄存器里的数据。
因此,就产生了可见性问题。外在表现为:变量T的值被修改,但是CPU1上的线程依然会读到一个旧值。



可见性问题也是并行程序开发中需要重点关注的问题之一。

可见性问题是一个综合性问题。
除了上述提到的缓存优化或者硬件优化(有些内存读写可能不会立即触发,而会先进入一个硬件队列等待)会导致可见性问题外,
指令重排(这个问题将在下一节中更详细讨论)以及编辑器的优化,都有可能导致一个线程的修改不会立即被其他线程察觉。

3. 有序性(Ordering)

在并发时,程序的执行可能就会出现乱序 
假设线程A首先执行 writer方法,接着线程B执行 reader方法,如果发生指令重排,那
么线程B在代码第10行时,不一定能看到a已经被赋值为1了。如图1.15所示,显示了两个
线程的调用关系 





注意:指令重排可以保证串行语义一致,但是没有义务保证多线程间的语义也一致

一条指令的执行是可以分为很多步骤的 
– 取指 IF 
– 译码和取寄存器操作数 ID 
– 执行或者有效地址计算 EX 
– 存储器访问 MEM 

– 写回 WB 
流水线的工作原理





水线满载时,性能确实相当不错,但是一旦中断,所有的硬件设备都会进入个停顿期,再次满载又需要几个周期,因此,性能损失会比较大。所以,我们必须要想办法尽量不让流水线中断!

那么答案就来了,之所以需要做指令重排,就是为了尽量少的中断流水线

图1.17展示了A=B+C这个操作的执行过程。写在左边的指
就是汇编指令。LW表示load,其中LWR1,B,表示把B的值加载到Rl寄存器中。ADD指

就是加法,把R1、R2的值相加,并存放到R3中。SW表示 store, 存储,就是将R3寄存器









Happen-Before规则
程序顺序原则:一个线程内保证语义的串行性 
volatile规则:volatile变量的写,先发生于读,这保证了volatile变量的可见性 
锁规则:解锁(unlock)必然发生在随后的加锁(lock)前 
传递性:A先于B,B先于C,那么A必然先于C 
线程的start()方法先于它的每一个动作 
线程的所有操作先于线程的终结(Thread.join()) 
线程的中断(interrupt())先于被中断线程的代码 
对象的构造函数执行结束先于finalize()方法
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  并发编程