您的位置:首页 > 编程语言 > Java开发

JAVA内存模型与线程

2016-11-27 22:10 169 查看
 Java内存的主要目的是定义程序中各个变量的访问规则,即虚拟机将变量存储到内存和从内存读取变量这样的细节.这里的变量指实例字段、静态字段和构成数组对象的元素,不包括局部变量和方法参数(线程私有,不涉及共享)

 Java内存模型规定所有的变量都储存在主内存中(可以对应堆中的对象实例);每条线程还有自己的工作内存,线程的工作内存中保存了该线程使用到的变量的主内存的副本拷贝,线程对变量的所有操作(读取、赋值等)都必须在主内存中进行.线程间变量值得传递均需要通过主内存来完成



内存交互操作(我觉得了解一下就可以了,为了方便大家了解,我把这些都列出来了)

一个变量如何从主内存拷贝到工作内存,如何从工作内存同步回主内存之类的实现细节,Java内存模型中定义了以下8种操作来完成.这8种操作都是原子性的、不可再分的(对double和Long类型除外).

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

unlock(解锁):作用于主内存变量,它把一个处理锁定的状态的变量释放出来,释放后的变量才可以被其它线程锁定,unlock之前必须将变量值同步回主内存。

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

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

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

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

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

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

如果要把一个变量从主内存复制到工作内存,那就要顺序地执行read和load操作;如果要把变量从工作内存同步回主内存,就要顺序地执行store和write操作.Java内存模型只是要求上述两个操作必须按顺序执行,而没有保证必须是连续执行.也就是说read与load之间、store与write之间是可以插入其它指令的,如对主在内中的变量a,b进行访问时,一种可能出现的顺序是read a、readb、loadb、load a。除此之外,Java内存模型还规定了执行上述八种基础操作时必须满足如下规则:

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

不允许一个线程丢弃它的最近的assign操作,即变量在工作内存中改变(为工作内存变量赋值)了之后必须把该变化同步回主内存。

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

如果一个变量事先没有被load操作锁定,则不允许对它执行unlock操作:也不允许去unlock一个被其它线程锁定的变量。

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

volatile关键字

volatile的两个特性:

保证变量对所有线程的可见性,即当一个线程修改了此变量,其他线程能立即得知

禁止指令重排序优化

volatile变量在各个线程的工作内存中不存在一致性问题(在各个线程的工作内存中volatile变量也可以存在不一致的情况,但由于每次使用之前都要先刷新,执行引擎看不到不致的情况,因此可以认为不存在一致性问题),但是Java里的运算并非原子操作,导致volatile变量的运算在并发下一样是不安全的.

volatile关键字试用的场合:

运算结果不依赖变量的当前值,或者能确保只有单一的线程改变变量的值

变量不需要与其它的状态变量共同参与不变约束

原子性、可见性、有序性

原子性(Atomicity)

由Java内存模型来直接保证的原子性变量操作包括read、load、use、assign、store和write六个,大致可以认为基础数据类型的访问和读写是具备原子性的。如果应用场景需要一个更大范围的原子性保证,Java内存模型还提供了lock和unlock操作来满足这种需求,尽管虚拟机未把lock与unlock操作直接开放给用户使用,但是却提供了更高层次的字节码指令monitorenter和monitorexit来隐匿地使用这两个操作,这两个字节码指令反映到Java代码中就是同步块—synchronized关键字,因此在synchronized块之间的操作也具备原子性

可见性(Visibility)

可见性就是指当一个线程修改了线程共享变量的值,其它线程能够立即得知这个修改。Java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方法来实现可见性的,无论是普通变量还是volatile变量都是如此,普通变量与volatile变量的区别是volatile的特殊规则保证了新值能立即同步到主内存,以及每使用前立即从内存刷新。因为我们可以说volatile保证了线程操作时变量的可见性,而普通变量则不能保证这一点

除了volatile之外,Java还有两个关键字能实现可见性,它们是synchronized与final.同步块的可见性是由“对一个变量执行unlock操作之前,必须先把此变量同步回主内存中(执行store和write操作)”这条规则获得的,而final关键字的可见性是指:被final修饰的字段是构造器一旦初始化完成,并且构造器没有把“this”引用传递出去,那么在其它线程中就能看见final字段的值。

有序性(Ordering)

Java内存模型中的程序天然有序性可以总结为一句话:如果在本线程内观察,所有操作都是有序的;如果在一个线程中观察另一个线程,所有操作都是无序的。前半句是指“线程内表现为串行语义”,后半句是指“指令重排序”现象和“工作内存主主内存同步延迟”现象。

先行发生原则

先行发生原则是指Java内存模型中定义的两项操作之间的依序关系,如果说操作A先行发生于操作B,其实就是说发生操作B之前,操作A产生的影响能被操作B观察到,“影响”包含了修改了内存中共享变量的值、发送了消息、调用了方法等

下面是Java内存模型下一些”天然的“先行发生关系,这些先行发生关系无须任何同步器协助就已经存在,可以在编码中直接使用。如果两个操作之间的关系不在此列,并且无法从下列规则推导出来的话,它们就没有顺序性保障,虚拟机可以对它们进行随意地重排序(也就是说容易出现并发问题)

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

管程锁定规则(Monitor Lock Rule):一个unlock操作先行发生于后面对同一个锁的lock操作。这里必须强调的是同一个锁,而”后面“是指时间上的先后顺序。

volatile变量规则(Volatile Variable Rule):对一个volatile变量的写操作先行发生于后面对这个变量的读取操作,这里的”后面“同样指时间上的先后顺序。

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

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

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

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

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

时间上先发生的线程的操作不一定会被后发生的线程的感知到;先行发生的操作也不一定在时间上先发生

线程的五种状态:

新建(New):创建后尚未启动的线程处于这种状态

运行(running):Runable包括操作系统线程状态中的Running和Ready,也就是出于此种状态的线程可能正在运行,也可能正在等待着CPU为它分配执行时间

无限期等待(Waiting):处于该状态的线程不会被分配CPU执行时间,他们要等待被其他线程显式的唤醒。以下方法会让线程陷入无限期的等待状态:

没有设置Timeout的Object.wait()方法

没有设置Timeout的Thread.join()方法

LockSupport.park()方法

期限等待(Timed Waiting):处于这种状态的线程也不会被分配CPU执行时间,不过无须等待其他线程显式的唤醒,在一定时间之后他们会由系统自动唤醒。以下方法会让线程进入无线等待状态:

Thread.sleep()方法

设置了Timeout的Object.wait()方法。

设置了Timeout的Thread.join()方法。

LockSupport.parkNanos()方法。

LockSupport.Until()方法。

阻塞(Blocked):线程被阻塞了,“阻塞状态”与“等待状态”的区别是:“阻塞状态”在等待着获取到一个排他锁,这个事件将在另外一个线程放弃这个锁的时候发生;而“等待状态”则是在等待一段时间,或者唤醒动作发生。在程序等待进入同步区域的时候,线程进入这种状态

结束(Terminated):已经终止的线程状态,线程已经结束执行。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: