硬件效率一致性处理
由于计算机存储设备与处理器的运算速度有几个数量级的差距,为了尽可能提升效率,计算机系统加入了一层读写速度尽可能接近处理器运算速度的高速缓存来作为内存与处理器之间的缓冲:将运算需要使用到的数据复制到缓存中,让运算能快速进行,当运算结束后再从缓存中同步回内存之中。
在多处理器系统中,每个处理器都有自己的高速缓存,而它们又共享同一个主内存。当多个处理器运算任务都涉及同一块主内存区域时,将可能导致各自的数据不一致。
除了增加高速缓存之外,为了使得处理器内部的运算单元尽量被充分利用,处理器可能会对输入代码进行乱序执行优化,处理器会在计算之后将乱序执行的结果重组,保证该结果与顺序执行的结果是一致的,但并不保证程序中各个语句计算的先后顺序与输入代码中的顺序一致,java虚拟机的即时编译器中也有类似的指令重排序优化。
java内存模型
java内存模型即在特定的操作协议下,对特定的内存或高速缓存进行读写访问的过程抽象。
java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。此处的变量与java编程中所说的变量的有所区别,包括了实例字段、静态字段和构成数组对象的元素,但不包括局部变量与方法参数,因为它是线程私有的,不会被共享。
java内存模型规定了所有变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接写主内存中的变量。
内存之间的8中基本操作
- lock ,作用与主内存,锁定
 - unlock, 作用域主内存,解锁
 - read,作用与主内存,把变量值从主内存传输到线程
 - load,作用与工作内存,把read读取的变量值放入工作内存的变量
 - use,作用于工作内存,把工作内存中一个变量值传递给执行引擎
 - assing(赋值),作用于工作内存,把从执行引擎接收到的值赋给工作内存的变量
 - store,作用于工作内存,把工作内存中的变量值传送到主内存,供随后write使用
 - wire,作用于主内存,把从store来的变量放到主内存中
 
上诉8中操作必须满足以下规则
- 不允许read和load,store和write操作之一单独出现
 - 不允许一个线程丢弃它的最近的assign操作,即变量在工作内存中改变了之后必须把该变化同步会主内存。
 - 一个变量只能在主内存中诞生,不允许直接在工作内存中load和assign一个变量,就是在实施use,store之前必须先执行assign、load
 - 一个变量同一时刻只允许一条线程对其lock,多lock操作可以被同一条线程重复多次执行,多次执行之后需要多次unlock才行。
 - 若对一个变量进行lock操作,那将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值
 - 没有lock就不能unlock
 - 执行unlock之前,必须先把此变量同步回主内存中
 
volatile变量
volatile关键字可以说是java虚拟机提供的最轻量级的同步机制
当定义一个变量为volatile之后,它将具备两种特性,第一是保证此变量对所有线程的可见性(当一条线程修改了这个变量的值,新值对于其他线程来说是可以立即得知的)。普通变量的值是不能做到的,需要借助主内存来同步。但是并不能说基于volatile变量的运算在并发下是安全的。因为java里面的运算并非是原子操作,这会导致volatile变量在并发下一样是不安全的。
比如对多个线程对同一个一个volatile修饰的静态值进行自增操作,它只能保证获取的时候是正确的,但是在执行自增操作的时候,可能此时的值已被其他线程修改了,就会导致修改值失败。
所以在不满足以下条件的时候,任然需要加锁
- 运算结果并不依赖变量得当前值,或者能够确保只有单一得线程修改变量得值
 - 变量不需要与其他得状态变量共同参与不变约束
 
使用volatile变量得第二个语义是禁止指令重排序优化。普通变量仅仅会保证在该方法得执行过程中所有依赖赋值结果得地方都能获取到正确得值,而不能保证变量赋值得顺序与程序代码中得执行顺序一致。指令重排序指得是CPU采用了允许将多条指令不按程序规定得顺序分开发送给各相应电路单元处理。但并不是说指令任意重排,cpu需要能正确处理指令依赖情况以保障程序能得出正确得执行结果。比如:指令1把A加1,指令2把A*2,指令3把B加1,那么指令3就能插入到指令1和指令2中间,不会影响运算结果。
volatile如何保证得线程间得可见性? volatile修饰得关键字会多一个指令,将ESP寄存器得值加0,它得作用是使得本CPU的cache写入了内存,通过这样一个操作使得其他cpu高速缓存中的数据失效,需要重新去store一下。这样就保证了修改对其他线程得立即可见。
java内存模型规则
java内存模型是围绕着并发过程中如何处理原子性、可见性、有序性这3个特征来建立得。
- 原子性。大致可以认为基本数据类型的访问读写是具备原子性得(long和double是非原子性协定)
 - 可见性。可见性是指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方式来实现可见性的,无论是普通变量还是volatile变量都是如此,普通变量与volatile变量的区别是:volatile的特殊规则保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。除了volatile外还有两个关键字能保证可见性,synchronized和final,同步块的可见性是由“对一个变量执行unlock操作之前,必须把此变量同步会主内存中”这条规则获取的。final变量一旦初始化完成,其他线程就能看见final字段的值
 - 有序性。一般来说,如果在本线程观察,所有操作都是有序的,如果在一个线程中观察另一个线程,所有操作都是无序的,前半句代表“线程内表现为串行的语义”,后半句指“指令重排序”现象和“工作内存与主内存同步延迟”现象。java提供了volatile和synchronized两个关键字来保证线程操作之间的有序性。
 
先行发生原则
java内存模型定义的有序性若都用volatile或者synchronized来做会显得很繁琐。所以程序中定义了一部分不需要定义的规则。
- 程序次序规则:在一个线程内,控制流顺序书写在前面的先行发生于书写在后面的。
 - 管程锁定规则:一个unlock操作先行发生于后面对同一个锁的lock操作
 - volatile变量规则:对一个volatile变量的写操作先行发生于后面对这个变量的读操作
 - 线程启动规则:Thread对象的start()方法先行发生于此线程的每一个动作
 - 线程终止规则:线程中所有操作都先行发生于对此县城的终止检测
 - 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生。
 - 对象终结规则:一个对象的初始化完成先行于它的finalize()方法的开始
 - 传递性:A先行于B,B先行于C,那么A先行于C
 
线程模型
线程实现主要有三种方式:使用内核线程实现、使用用户线程实现和使用用户线程加轻量级进程混合实现
使用内核线程实现
内核线程就是直接由操作系统内核支持的线程,这种线程由内核来完成线程的切换,内核通过操纵调度器对线程进行调度,并负责将线程的任务映射到各个处理器上。
程序一般不会直接去使用内核线程,而是去使用内核线程的一种高级接口–轻量级进程,轻量级进程就是我们通常意义上的线程,每个轻量级进程都由一个内核线程支持。
局限:首先,由于是基于内核线程实现,所有各种线程操作,如创建、析构及同步都需要进行系统调用。系统调用需要在用户态和内核态中来回切换,其次,每个轻量级进程都需要一个内核线程的支持,因此,要消耗一定的内核资源,所以一个系统支持轻量级进程的数量是有限的
使用用户线程实现
从广义上讲,一个线程只要不是内核线程,就可以认为是用户线程。狭义上的用户线程是指完全建立在用户空间的线程库上,系统内核不能感知线程存在的实现。用户线程的建立、同步、销毁和调度都是在用户态中完成,不需要内核帮助。因此操作可以是非常快速且低小号的,也可以支持规模更大的线程数量。
使用用户线程的优点和缺点都是没有内核线程的支持。所以用户线程实现相对复杂,而且,由于操作系统只把处理器资源分配到进程,那么,进程调度如何处理阻塞等问题就会很困难,甚至不能完成。java,ruby以前使用过用户线程,后来放弃了。
使用用户线程加轻量级进程混合实现
在这种模式下,线程的创建、切换等操作还是完全建立在用户空间中。而操作系统提供的轻量级进程则作为用户线程和内核线程之间的桥梁,这样可以使用内核提供的线程调度功能及处理器映射,并且,用户线程的系统调用可通过轻量级线程来完成,大大降低了整个进程被完全阻塞的风险。
对于SunJDK来说,它的Windows版与Linux版都是使用一对一的线程模型实现的,一条java线程就映射到一条轻量级进程之中。因为Windows和Linux系统提供的线程模型就是一对一的。
线程调度
线程调度是指系统为线程分配处理器使用权的过程,主要调度方式有两种,分别是协同式线程调度和抢占式线程调度
协同式线程调度,线程的执行时间由线程本身控制,线程把自己的工作执行完后,交给另外一个线程。优点是没有什么线程同步的问题,缺点是时间不可控,一个进程不让出时间片就会导致整个系统崩溃
抢占式线程调度,由系统来分配执行时间,线程切换不由线程本身决定。java的线程调度就是抢占式线程调度,时间可控。
java线程调度的优先级设置其实不太靠谱,因为java线程是映射到系统原生线程执行的,这种情况下,可能系统的线程的优先级可能就不会和java的优先级所对应,可能会出现java线程多个优先级对应系统某一个系统的优先级。再者,例如在windows系统中,会为执行的特别“勤奋”的线程多分配时间片。