写在前面:与之前主流程序语言(c/c++等)直接使用物理硬件和操作系统的内存模型不同,java虚拟机为了屏蔽各种硬件和操作系统的内存访问差异定义了一种java内存模型。其主要定义程序中各个变量的访问规则(在虚拟机中将变量存储到内存和从内存中取出变量的底层细节)。
线程、主内存、工作内存之间的交互关系
1.java内存模型结构:
-所有的变量都存储在主内存中。
-每条线程还有自己的工作内存。
-工作内存中保存了从主内存中拷贝的该线程所要使用到的变量
-每条线程对变量的操作必须在自己的工作内存中,不能直接操作主内存,也不能操作其他线程的工作内存,线程间需要通过主内存传递。
2.内存间交互操作:
关于主内存与工作内存之间的具体交互协议,即一个变量如何从主内存拷贝到工作内存、如何从工作内存同步到主内存之间的实现细节,Java内存模型定义了以下八种操作来完成:
lock (锁定):作用于主内存的变量,把一个变量标识为一条线程独占状态。
unlock(解锁):作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
read (读取):作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
load (载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
use (使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
store (存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。
write (写入):作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。
内存间交互流程图
相关规则:
不允许read和load、store和write操作之一单独出现
不允许一个线程丢弃它的最近assign的操作,即变量在工作内存中改变了之后必须同步到主内存中。
不允许一个线程无原因地(没有发生过任何assign操作)把数据从工作内存同步回主内存中。
一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量。即就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作。
一个变量在同一时刻只允许一条线程对其进行lock操作,lock和unlock必须成对出现
如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前需要重新执行load或assign操作初始化变量的值
如果一个变量事先没有被lock操作锁定,则不允许对它执行unlock操作;也不允许去unlock一个被其他线程锁定的变量。
对一个变量执行unlock操作之前,必须先把此变量同步到主内存中(执行store和write操作)。
3.volatile变量的特殊规则
1.两种特性:
-保证该变量对所有线程的可见性。
-禁止指令重排序优化 。
2.使得java内存模型具有:有序性、可见性、原子性
4.线程的实现
1.一对一(1:1)的内核级线程模型:
一对一模型中,每个用户线程都对应各自的内核调度实体。内核会对每个线程进行调度,可以调度到其他处理器上面。当然由内核来调度的结果就是:线程的每次操作会在用户态和内核态切换。另外,内核为每个线程都映射调度实体,如果系统出现大量线程,会对系统性能有影响。但该模型的实用性还是高于多对一的线程模型。
2.多对一(M:1)的用户级线程模型:
多对一线程模型中,线程的创建、调度、同步的所有细节全部由进程的用户空间线程库来处理。用户态线程的很多操作对内核来说都是透明的,因为不需要内核来接管,这意味不需要内核态和用户态频繁切换。线程的创建、调度、同步处理速度非常快。当然线程的一些其他操作还是要经过内核,如IO读写。这样导致了一个问题:当多线程并发执行时,如果其中一个线程执行IO操作时,内核接管这个操作,如果IO阻塞,用户态的其他线程都会被阻塞,因为这些线程都对应同一个内核调度实体。在多处理器机器上,内核不知道用户态有这些线程,无法把它们调度到其他处理器,也无法通过优先级来调度。这对线程的使用是没有意义的!
3.多对多(M:N)的两级线程模型:
多对多模型中,结合了1:1和M:1的优点,避免了它们的缺点。每个线程可以拥有多个调度实体,也可以多个线程对应一个调度实体。听起来好像非常完美,但线程的调度需要由内核态和用户态一起来实现。可想而知,多个对象操作一个东西时,肯定要一些其他的同步机制。用户态和内核态的分工合作导致实现该模型非常复杂。NPTL曾经也想使用该模型,但它太复杂,要对内核进行大范围改动,所以还是采用了一对一的模型.
5.java线程调度
1.协同式:
定义:当线程完成自己的事情后才会进行切换
优点:实现简单、无需同步
缺点:不稳定、容错率低,一旦有线程阻塞会导致系统崩溃
2.抢占式(java使用):
定义:根据优先级的高低,系统分配执行时间
优点:不会因为一个线程的问题导致系统崩溃
缺点:需要同步,复杂
6.java线程的状态
1.新建(New):创建后尚未启动的线程
2.运行(Rannable):包括了操作系统线程状态中Running(正在执行)和Ready(等待CPU分配执行时间)。
3.等待(waiting):
无限期等待:不会被CPU分配执行时间,直到被其他线程显示唤醒(Object.wait();Thread.join();LockSupport.park())
限期等待:不会被CPU分配执行时间,无须等待被其他线程显示唤醒,一定时间由系统自动唤醒。
(Thread.sleep();Object.wait(long timeout);Thread.join(long timeout);LockSupport.parkNanos();LockSupport.parkUntil())
4.阻塞(Blocked):在等待获取到一个排它锁。在程序等待进入同步区域的时候,线程将进入这种状态。
5.结束(Terminated):已终止线程的线程状态。线程已经结束执行。
Java学习交流QQ群:523047986 禁止闲聊,非喜勿进!