多任务处理,在计算机硬件上的问题
1. 缓存一致性
计算机中,CPU的运行速度和内存的读取速度,有几个数量级的差异; 为了解决这个问题, 现代计算机在CPU和内存之间,都加入了和CPU运算速度匹配的高速缓存Cache; 运算时, 先将数据读入cache, CPU从缓存中读取数据, 运算结束后, CPU将结果写入缓存, 然后再由缓存把数据写回内存; 基于高速缓存的方案解决了CPU与内存之间的数速度矛盾, 但是同时引进了一个新问题: 内存一致性; 如果多个CPU单元同时处理同一块内存区域的数据, 可能各个CPU的缓存数据不一样, 那这个时候把数据写回内存, 要怎么处理?
2. 指令重排优化
CPU为了尽可能的提升效率,会对输入代码进行乱序执行优化,然后将结果重组,保证结果和顺序执行是一样的, 但是并不保证代码中各个语句执行顺序和输入的代码一致, 因此代码的顺序并不能保证执行的顺序; Java即时编译中,也有 指令重排优化, 因此, 如果一个计算任务依赖另外一个计算任务的中间结果时, 需要去避免重排造成的问题;
Java内存模型(Java Memory Model, JMM)
1. 主内存和工作内存
Java内存模型规定了, 代码运行的所有变量都存储在主内存中 , 同时, 每条线程还有自己的工作内存, 线程的工作内存中包含了被该线程使用的线程私有变量 和主内存副本 ,线程对变量的所有操作(读取,赋值等)都是在工作线程进行, 不能直接读写主内存中的变量; 主内存包含对象实例数据,静态字段数据等, 线程私有变量主要是局部变量和方法参数;
2. 内存间交互操作
主内存和工作内存之间的相互操作, 即变量如何从主内存中读入到工作内存, 代码执行引擎如何使用, 如何从工作内存写回主内存; Java内存模型定义了8种操作来实现, 并且虚拟机实现时, 这8种操作必须都是原子的,不可再分;
- lock: 用于主内存,把一个变量标记为某条线程独占状态
- unlock: 用于主内存, 把处于lock的变量释放出来, 释放出来后变量才可以被其他线程lock
- read: 用于主内存, 把变量从主内存传输到工作内存, 方便后续的load使用
- load: 用于工作内存, 把从主内存中read的变量放入工作内存的变量副本中
- use: 用于工作内存, 把工作内存的变量传递给执行引擎去使用; 虚拟机需要使用变量时,执行此操作
- assign: 用于工作内存, 把从执行引擎收到的值, 赋值给工作内存中的变量; 虚拟机遇到变量赋值语句时,执行此操作
- store: 用于工作内存, 把工作内存中的变量传回到主内存, 以便后续的write操作
- write: 用于主内存, 把store操作的变量写入到主内存
内存模型与线程安全
并发过程中实现线程安全, 通常是要保证代码块是原子的,有序执行的, 同时共享变量的修改对线程都是可见的; 而Java内存模型也是围绕着并发过程中, 如何处理原子性, 可见性, 有序性这3个特征来建立的;
原子性(Atomicity)
Java内存模型中, read,load,use,assign,store,write都是原子性操作, 因此, 对于基本数据类型的读写访问,都可以认为是具有原子性的, (内存模型允许对long,double这类64位的数据读写操作, 划分成两次32位的操作来进行,因此对64位数据的操作可能不具有原子性, 但是目前的虚拟机实现,都是把这类64位数据作为原子性操作);
如果需要大范围的保证原子性, 内存模型中的lock和unlock可以实现; 虽说虚拟机对内存的lock和unlock并没开放给用户, 但提供了更高层次的指令, 反应到Java代码中就是同步块--synchronize关键字, 和java.util.concurrent.locks.Lock
的操作, 因此在synchronize代码块及Lock.lock和unlock之间的代码块也具有原子性
可见性(Visibility)
可见性是指当一个线程修改了共享变量的值, 其他线程能够立即得知这个修改
volatile修饰的变量,每次修改后,都会立即同步到主内存,每次使用时,都会去从主内存刷新,因此, volatile修饰的变量在多线程时保证了可见性;
除volatile之外, 同步块和final关键字也能实现可见性; 同步块的可见性, 是由Java内存模型中,对变量执行unlock操作,必须将变量同步回主内存这一规则保证的; final的可见性是指: final修饰的字段,一旦初始化完成,在其他线程就都能看见final字段的值,并且不可修改;
有序性(Ordering)
Java的volatile关键字和代码的同步块可以保证有序性;
volatile关键字本身包含了禁止指令重排的语意;
同步块的有序性, 是由Java内存模型中,一个变量在同一时刻,只允许一条线程对其进行lock操作的规则保证的, 这一规则决定了持有同一个锁的多个同步块只能串行进入;
网友评论