1.Java内存模型
Java内存模型用来屏蔽各种硬件和操作系统的内存访问差异,以实现在各种平台下都能达到一致的并发效果。
主内存与工作内存
Java内存模型的主要目标是/定义程序中各个变量的/访问规则,即在虚拟机中/将变量存储到内存和从内存中取出变量这样的底层细节。
Java内存模型规定:
所有的变量都存储在主内存中;
每个线程有自己的工作内存,线程的工作内存保存被线程使用到变量的主内存副本拷贝;
线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存的变量;
不同线程之间不能直接访问对方工作内存中的变量,线程间变量值的传递通过主内存来完成。
内存间交互操作
Java内存模型定义了八种操作:
lock(锁定):作用于主内存的变量,它把一个变量标识为一个线程独占的状态;
unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定;
read(读取):作用于主内存的变量,它把一个变量的值从主内存传送到线程中的工作内存,以便随后的load动作使用;
load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中;
use(使用):作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎;
assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存中的变量;
store(存储):作用于工作内存的变量,它把工作内存中的一个变量的值传送到主内存中,以便随后的write操作;
write(写入):作用于主内存的变量,它把store操作从工作内存中得到的变量的值写入主内存的变量中。
操作时规则
不允许read和load、store和write操作之一单独出现;
说明:如果要把一个变量从主内存复制到工作内存,那要顺序执行read和load操作;如果要把变量从工作内存同步回主内存,就要顺序执行store和write操作。Java内存模型要求上述连个操作必须按顺序执行,但不必须连续执行。
不允许一个线程丢弃它的最近的assign操作,变量在工作内存中改变了之后必须把变化同步回主内存;
不允许一个线程无原因地把数据从线程的工作内存同步回主内存;
一个新变量之能在主内存诞生,不允许在工作内存直接使用一个为被初始化(load或assign)的变量,即在对一个变量实施use和store操作之前,必须先执行assign和load操作;
一个变量在同一个时刻只允许一条线程对其进行lock操作,但lock操作可以被同一线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才被解锁;
如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值;
如果一个变量事先没有被lock操作锁定,则不允许对他执行unlock操作;也不允许去unlock一个被其他线程锁定住的变量;
对一个变量执行unlock操作之前,必须先把此变量同步回主内存。
volatile
关键字volatile是JVM提供最轻量级的同步机制。
当一个变量被定义成volatile后,它具有两个特性:
第一,volatile变量对所有线程是立即可见的,对于volatile变量所有的写操作都能立刻反应到其他线程之中;但volatile变量只能保证可见性,不能保证在并发下是安全的。
第二,禁止指令重排序优化。
Java模型特性
原子性:J read、load、assign、use、store和write保证原子性变量操作;lock和unlock通过synchronized语法来加锁,在synchronized块之间的操作也具有原子性;
可见性:volatile、synchronized和final实现变量修改值在其他线程可见;synchronized可见性是由“对一个变量执行unlock操作之前,必须先把此变量同步回主内存中“这条规则获得的;final可见性是”被final修饰的字段在构造器中一旦被初始化完成,并且构造器没有把this的引用传递出去,那么在其他线程就能看到final字段的值。“
有序性:Java语言提供volatiel和synchronized两个关键字来保证线程之间的有序性;volatile本身禁止指令重排序;synchronized是由“一个变量在同一个时刻只允许一条线程对其进行lock操作“这条规则获得的。
2.Java与线程
线程是比进程更轻量级的调度执行单位,线程可以把一个进程的资源分配和执行调度分开,各个线程既可以共享进度资源,由可以独立调度。
Java线程实现
Java在Windows与Linux上都是使用一对一的县城模型来实现,一个Java线程就映射到一条轻量级进程之中。
Java线程调度
线程调度指系统为线程分配处理器使用权的过程,主要调度方式分为:协同式和抢占式。Java使用的线程调度方式是抢占式调度。
协同式,线程的执行时间由线程本身来控制,线程执行完之后主动通知系统切换到另一个线程,它的坏处是线程执行时间不可控制。如果一个线程迟迟不让出CPU执行时间会导致整个系统崩溃。
抢占式,每个线程由系统来分配执行时间,线程的切换由操作系统决定。
状态装换
Java定义了5种线程状态,在任意一个时间点,一个县城智能有且只有有其中的一种状态,即:
新建(New):创建后尚未启动的线程;
运行(Runable):包括了操作系统线程状态中的Running和Ready,处于此状态的线程可能正在执行,也可能正在等待CPU为它分配执行时间;
无限期等待(Waiting):该状态的线程不会被分配CPU执行时间,他们要等待其他线程显式唤醒。以下方式可以让线程陷入Waiting状态:
1.没有设置Timeout参数的Object.wat()方法;
2.没有设置Timeout参数的Thread.join()方法;
3.LockSupport.park()方法。
限制等待(Timed Waiting):该状态的线程也不会被分配CPU执行时间,不过无须等待其他线程显示唤醒,在一定时间之后它们会由系统自动唤醒。以下方式可以让线程陷入Timed Waiting状态:
1.Thread.sleep()方法;
2.设置了Timeout参数的Ojbect.wait()方法;
3.设置了Timeout参数的Thread.join()方法;
4.LockSupport.parkNanos()方法;
5.LockSupport.parkUntil()方法。
阻塞(Blocked):线程被阻塞。在程序等待进入同步区域时,线程进入这种状态。
结束(Terminated):终止线程的状态。