《java虚拟机》----java内存模型与线程

 2019-12-10 11:20  阅读(839)
文章分类:Java Core

No1.

20191210001114\_1.png

20191210001114\_2.png

No2.

java内存模型规定了所有的变量都存储在主内存中(Main Memory)中

每条线程还有自己的工作内存(Working Memory)

线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量。

不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成

No3:

内存间交互操作:

1)lock(锁定):作用与主内存的变量,它把一个变量标识为一条线程独占的状态

2)unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定

3)read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用

4)load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中

5)use(使用):作用于工作内存的变量,它把工作内存中的一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作

6)assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作

7)store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用

8)write(写入):作用与主内存的变量,它把stroe操作从工作内存中得到的变量的值放入主内存的变量中

以上8种操作必须满足如下规则:

1)不允许read和load、store和write操作之一单独出现,即不允许一个变量从主内存读取了但工作内存不接受,或者从工作内存发起回写了但主内存不接受的情况出现

2)不允许一个线程无原因地(没有发生过任何assign操作)把数据从线程的工作内存同步回主内存中

3)一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量,换句话说,就是对一个变量实施use、store操作之前,必须先执行过了assign和load操作

4)一个变量在同一个时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁

5)如果对一个变量执行lock操作,那将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值

6)如果一个变量事先没有被lock操邹锁定,那就不允许对它执行unlock操作,也不允许去unlock一个被其他线程锁定住的变量

7)对一个变量执行unlock操作之前,必须先把此变量同步回主内存中(执行store、write操作)

No4:

关键字volatile可以说是java虚拟机提供的最轻量级的同步机制。

当一个变量定义为volatile之后,它将具备两种特性

1)保证此变量对所有线程的可见性,这里可见性是指一条线程修改了这个变量的值,新值对于其他线程来说是可以立即得知的

2)禁止指令重排序优化

No5:

原子性:

在java内存模型来直接保证的原子性变量操作包括read、load、assign、use、stroe、write,我们大致可以认为基本数据类型的访问读写是具备原子性的(例外就是long和double的非原子性协定)

可见性:

可见性是指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。

java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方式来实现可见性的。

除了volatile之外,java还有两个关键字能实现可见性,即synchronized和final。

有序性

如果在本线程内观察,所有的操作都是有序的,表现为线程内串行的语义;如果在一个线程中观察另一个线程,所有的线程都是无序的,表现为指令重排序现象和工作内存与主内存同步延迟现象。

volitile关键字本身就包含了禁止指令重排序的语义,而synchronized的规则“一个变量在同一个时刻只允许一条线程对其进行lock操作”决定了持有同一个锁的两个同步块只能串行地进入。

No6:

先行发生原则:指两项操作之间的偏序关系。如果操作A先于操作B发生,那么操作A产生的影响能被操作B观察到,影响包括修改了内存中共享变量的值、发送了消息、调用了方法等。

先行发生规则:

1)程序次序规则:在一个线程内,按照程序代码顺序,书写在前面的操作先行发生于书写在后面的操作。准确的说,应该是控制流顺序而不是程序代码顺序,因为要考虑分支、循环等结构

2)管程锁定规则:一个unlock操作先行发生于后面对同一个锁的lock操作,后面是指时间上的先后顺序

3)volatile变量规则:对一个volatile变量的写操作先行发生于后面对这个变量的读操作。后面也是时间上的顺序

4)线程启动规则:Thread对象的start()方法先行发生于此线程的每一个动作

5)线程终止规则:线程中的所有操作都先行发生于对此线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值等手段检测到线程已经终止执行

6)线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测到是否有中断发生

7)对象终结规则:一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始

8)传递性:如果操作A先行发生于操作B,操作B先行发生于操作C,那就可以得出操作A先行发生于操作C的结论

No7:

实现线程主要有3种方式:使用内核线程实现、使用用户线程实现和使用用户线程加轻量级进程混合实现

No8:

内核线程就是直接由操作系统内核(Kernel)支持的线程,这种线程由内核来完成线程切换,内核通过操作调度器(Scheduler)对线程进行调度,并负责将线程的任务映射到各个处理器上。每个内核线程可以视为内核的一个分身,这样操作系统就有能力同时处理多件事情,支持多线程的内核就叫做多线程内核。是1:1的关系

No9:

用户线程指完全建立在用户空间的线程库上,系统内核不能感知线程存在的实现。用户线程的建立、同步、销毁和调度完全在用户态中完成,不需要内核的帮助。是1:N的关系

No10:

内核线程和用户线程混合使用,推荐。是N:M的关系

No11:

系统为线程分配处理器使用权主要有两种调度方式:协同式线程调度和抢占式线程调度。java使用的是抢占式,可以通过分配优先级来控制。

No12:

线程状态:

1)新建(New):创建后尚未启动的线程

2)运行(Runable):包括Running和Ready,执行或等待

3)无期限等待(Waiting):需要被其他线程显式唤醒 导致原因 a)没有timeout参数的Object.wait() b) 没有timeout参数的Thread.join() c)LockSupport.park()

4)限期等待(Timed Waiting):在一定时间后会由系统自动唤醒 导致原因 a)Thread.sleep() b)有timeout参数的Object.wait() c)有timeout的Thread.join() d)LockSupport.parkNanos() e)LockSupport.parkUntil()

5)阻塞(Blocked):

6)结束(Terminated):已结束执行的线程

No13:

面试:volatile类型变量提供什么保证?

volatile变量提供顺序和可见性保证,例如,JVM或者JIT为了获得更好的性能会对语句重排序,但是volatile类型变量即使在没有同步块的情况下赋值也不会与其他语句重排序。volatile提供happens-before的保证,确保一个线程的修改能对其他线程是可见的。某些情况下,volatile还能提供原子性,如读64位数据类型,像long和double都不是原子的,但volatile类型的double和long就是原子的。

面试:线程间通信机制

1)全局变量

线程间内存共享,这是比较常用的通信方式和交互方式。注意:定义全局变量时最好使用volatile来定义,以防编译器对此变量进行优化

2)线程级共享变量:ThreadLocal

ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。提供三个核心方法:get()、set()、remove()

3)共享文件和数据库

不同线程共享一份文件和数据库

4)Handler消息机制

使用Handler在子线程中处理任务,得到结果通过Handler消息机制,在主线程中处理结果

5)线程同步

通过线程同步机制,可以使多个线程之间,同时对同一个内存地址进行写入和读取的操作

点赞(0)
版权归原创作者所有,任何形式转载请联系作者; Java 技术驿站 >> 《java虚拟机》----java内存模型与线程

相关推荐