1.概述
高效并发通过JAVA线程之间提高并发协调实现,在实现过程中需考虑硬件的效率和一致性,但在运算的过程中需要考虑处理器与内存的交互,所以基于高速缓存的存储交互解决的处理器与内存的方案,在对多处理器系统中,共享着同一内存,所以JAVA中也提出了JMM的概念,JMM可以理解为特定的操作协议中,对特定的内存或高速缓存进行读写访问的过程抽象,不同的物理机器拥有不同的内存模型,JVM也有自己的内存模型,在以高速缓存的前提下,处理器对输入代码进行乱序执行(Out-Of-Order Execution)优化,通过处理器计算之后的乱序结果重组,保证结果与顺序执行一致,JVM也有类似的"指令重排序"优化。
2.JMM
JMM被定义为用来屏蔽各种硬件和操作系统的内存访问差异,以实现JAVA各个平台达到一致的内存访问效果,在这之前主流程序语言直接使用物理硬件和操作系统的内存模型,这会导致处理器和内存存在着差异,针对这一情况,在JDK1.5(JSR-133),JMM开始被完善.
JMM的目标是定义程序中变量的访问规则,在虚拟机中将变量存储到内存和从内存中取出变量,包括实例字段,静态字段和构成数组,但不包括局部变量和方法参数,因为是线程私有的,不会被共享,JMM规定了所有变量都存储在”主内存“,每条线程都有自己的"工作内存",变量的所有操作都在工作内存中进行,而不是读写足内存的变量,不同的线程之间无法直接访问对方的工作内存中的变量
3.内存交互操作
主内存和工作内存之间有具体的交互协议,JMM定义了以下8中操作来完成,虚拟机实现时必须保证每一种操作都是原子的,不可再分的.
1.lock 作用于主内存的变量,把一个标量标识为一条线程独占的状态
2.unlock作用于主内存的变量,把一个处于锁定状态的变量释放出来,被释放的变量可以被其它线程锁定
3.read作用于主内存的变量,把一个变量的值从主内存传输到相册过的线程工作内存,以便随后的LOAD操作
4.load作用与工作内存的变量,把read操作从主内存得到的变量放入工作内存的变量副本
5.use 作用于工作内存变量,把工作内存的变量传递给执行引擎,每当虚拟机遇到一个需要使用的变量的值的字节码指令时会执行这个操作
6.assign 作用于工作内存的变量,把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节指令码指令时执行这个操作
7.store 作用与工作内存的变量,把工作内存中的一个变量值传递到主内存中,以便write操作
8.write 作用与工作内存的变量,把store操作从工作内存中得到的值放入主内存的变量
在一个变量从主内存复制到工作内存中,JMM规定RL操作和SW操作必须按顺序执行,但没有保证连续执行,但在基本操作中必须满足以下规则
1.不允许RL,SW操作之一单独出现,不允许一个变量从主内存读取了但工作内存不接受
2.不允许一个线程丢弃他最近的assign操作,变量在工作内存中改变了之后必须把该变化同步回主内存
3.不允许一个线程无原因把数据从线程的工作内存同步回主内存中.
4.一个新的变量只能在主内存中诞生,不允许在工作内存直接使用使用一个未被初始化的变量.
5.一个变量在同一时刻只允许一条线程执行lock操作。
6.对一个变量执行lock操作,将会清空工作内存中的变量的值
7.一个变量事先没有被lock锁定,那就不允许对他执行unlock操作
8.对一个变量执行unlock操作之前,必须先把此变量同步到主内存中
Volatile变量的特殊规则
当一个变量被定义为volatile时,具备两个特性
1.保证变量对所有线程的可见性,指一条线程修改了这个变量的值,新的值被其它线程可以得知,普通变量的值在线层之间传递需要足内存来完成,
由于volatile变量只能保持可见性,在不符合两条规则的运算场景中,仍然需要通过加锁来保证(synchronized或java.util.concurrent)原子性
1.运算结果并不依赖变量的当前值,或者只有单一的线程修改变量的值
2.变量不需要与其他的状态变量共同参与不变约束
longdouble变量规则
JMM定义了允许虚拟机可以选择不保证64位LOAD,STORE,READ,WRITE的原子性,如果多个线程共享一个VOLATILE的long或者double变量,并且同时对他们进行读取的修改操作,那么某个线程会获得一个非原值,而不是其它线程修改的变量
原子性可见性和有序性
JMM围绕着并发过程中的原子性可见性和有序性来建立
原子性(Actonicity) JMM来保证原子性变量操作,基本类型的访问读写具有原子性
可见性(Visibility)可见性只当一个线程修改了共享变量的值,其它线程能够立即得到资格修改,java中通过synchronized 和final来实现可见性,同步块的可见性是由一个“变量执行unlock操作之前”,必须先把此变量同步回主内存中这条规则获取,final关键字的可见性是指,在构造器一旦初始化完成,并且构造器没有把"this"的引用传递过去,那在其它线程中看到final字段的值.
有序性(Ordering)指的是如果在本线程内观察,所有的操作都是有序的,如果在一个线程观察另一个线程,所有操作都是无序的,前半句是指"线程内表现为串行的语义",后半句指的是指令重排序.
JAVA提供volatile和synchronized来确保线程操作之间的有序性,volatile包含了禁止指令重排序的语义,而synchronized是由"一个变量在同一时刻只允许对一个线程进行lock操作"
先行发生原则(happens-before)
JAVA总有一个happens-before原则,用于判断是否存在竞争,线程是否安全的依据,H-B原则在JMM定义为两项操作的偏序关系,在JMM中存在着天然的H-B原则
1.程序次序规则:在一个线程内,按照程序代码顺序,书写在前面的操作先行发生于书写在后面的操作.
2.管程锁定原则:一个unlock操作先行于发生于后面同一个锁的lock操作
3.volatile变量规则:对一个volatile变量的写操作相信发生于后面对这个变量的读操作.
4.线程启动规则:Thread对象的start()方法先行发生于线程的每一个操作
5.线程终止规则:通过Thread().join()方法结束,Thread.isAlive()的返回值手段检测线程中止执行
6.中断规则:一个对象的初始化完成先行发生于他的finalize()方法开始
7.传递性:A-B B-C ==A-C
JAVA线程
用户实现线程主要有3个方式:内核线程实现,内核线程和用户线程加轻量级进程混合实现
1.内核线程
KLT有操作系统内核(kernel)实现,由内核完成线程转换,内核通过操纵调度器对线程进行调度,并负责将线程的任务映射到各个处理器上,每个内核内核线程可以视为一个内核分身,但程序一般不会直接使用内核线程,而是使用内核线程的一种高级接口--轻量级进程(LWP),只有支持内核线程,才能有轻量级线程,以下是关系图
有内核线程的支持,每一个轻量级进程都会成为一个独立的调度单元,即使有一个轻量级进程堵塞,也不会影响工作
2.用户线程
一个线程只要不是内核线程,就可以认为是用户线程,轻量级线程可以认为是用户线程,用户线程的优势在于不出要系统内核支援,劣势也在于没有系统内核的支援,所有线程操作都需要用户自己处理,这对程序有不利的因素,存在着1:N的关系所以不推荐使用.
3.使用用户线程加轻量级进程混合实现
混合实现情况下,即存在用户线程也存在轻量级进程,在创建过程中通过内核的线程调度以及处理器映射,对用户线程通过轻量级进程完成,降低了进程阻塞的风险。存在着N:M的关系
线程调度
线程调度值系统为线程分配处理器使用权的过程,分为协同式系统调度(Cooperative Threads-Scheduling)和抢占式线程调度(Preemptive Threads-Scheduling)
1.协同式系统调度执行时间由线程本身来控制,好处在于实现简单,而且线程是要把事情刚完才会线程切换,切换操作时线程可知的,所以不会担心进程阻塞问题,线程控制时间不可控制,甚至一个线程编写有问题,一直不告知系统进行线程切换,那么线程会阻塞在这里.。
2.抢占式调度线程由系统分配执行时间,线程的切换不由系统本身。
线程切换
1.新建
2.运行
3.无限期等待
3.1没有设置TimeOut参数的Object.wait()方法
3.2没有设置TimeOut参数的Thread.join()方法
4.限期等待(TIME WAITING)
4.1Thread.sleep()方法
4.2Object.Wait()
4.3Thread.join()
4.4LockSupport.ParkNanos()
4.5LockSupport.parkUtil()
5.阻塞
阻塞状态在等待着获取到一个排它锁,等待状态则在等待一段时间或者唤醒动作的发生,在程序等待进入同步区域时,线程将进入这个状态
6.结束
终止线程的线程状态