Android开发Android技术知识Android开发经验谈

要点提炼| 理解JVM之内存模型&线程

2018-07-18  本文已影响81人  厘米姑娘

本篇将介绍虚拟机如何实现多线程、多线程之间由于共享和竞争数据而导致的一系列问题及解决方案。


1.概述

a.多任务处理的必要性:

b.硬件的效率与一致性

为了更好的理解Java内存模型,先理解物理计算机中的并发问题,两者有很高的可比性。

为了平衡计算机的存储设备与处理器的运算速度之间几个数量级的差距,引入一层高速缓存(Cache)来作为内存与处理器之间的缓冲:

但是基于高速缓存的存储交互在多处理器系统中会带来缓存一致性(Cache Coherence)的问题。这是因为每个处理器都有自己的高速缓存,而它们又共享同一主内存(Main Memory),当多个处理器的运算任务都涉及同一块主内存区域时,就可能导致各自的缓存数据不一致。解决办法就是需要各个处理器访问缓存时都遵循一些协议,在读写时要根据协议来进行操作。如下图。

因此,这里所说的内存模型可以理解为:在特定的操作协议下,对特定的内存或高速缓存进行读写访问的过程抽象。


2.Java内存模型(Java Memory Model,JMM)

a.目的:屏蔽掉各种硬件和操作系统的内存访问差异,实现Java程序在各种平台下都能达到一致的内存访问效果。

b.方法:通过定义程序中各个变量访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。

注意:这里的变量与Java中说的变量不同,而指的是实例字段、静态字段和构成数组对象的元素,但不包括局部变量与方法参数,因为后者是线程私有的,不会被共享,自然就不会存在竞争问题。

c.结构:模型结构如图,和上张图进行类比。

注意:这里的主内存、工作内存与要点提炼| 理解JVM之内存管理说的Java内存区域中的Java堆、栈、方法区等并不是同一个层次的内存划分。

注意

  • 线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。
  • 不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递必须通过主内存来完成。

结论:注意是顺序非连续

  • 如果要把变量从主内存复制到工作内存,那就要顺序地执行readload
  • 如果要把变量从工作内存同步回主内存,就要顺序地执行storewrite

d.确保并发操作安全的原则

①在Java内存模型中规定了执行上述8种基本操作时需要满足如下规则:

可见这么多规则非常繁琐,实践也麻烦,下面再介绍一个等效判断原则--先行发生原则。

先行发生原则:是Java内存模型中定义的两项操作之间的偏序关系。下面例举一些“天然的”先行发生关系,无须任何同步器协助就已经存在,可以在编码中直接使用。

e.Java内存模型保证并发过程的原子性、可见性和有序性的措施:


3.Java与线程

a.线程实现的三种方式

①使用内核线程(Kernel-Level Thread,KLT)

②使用用户线程(User Thread,UT)

③使用用户线程加轻量级进程混合

那么Java线程的实现是选择哪一种呢?答案是不确定的。操作系统支持怎样的线程模型,在很大程度上决定了Java虚拟机的线程是怎样映射的。线程模型只对线程的并发规模和操作成本产生影响,而对Java程序的编码和运行过程来说,这些差异都是透明的。

b.Java线程调度的两种方式

线程调度:指系统为线程分配处理器使用权的过程。

协同式线程调度(Cooperative Threads-Scheduling)

抢占式线程调度(Preemptive Threads-Scheduling)

但是线程优先级并不是太靠谱,一方面因为Java的线程是通过映射到系统的原生线程上来实现的,所以线程调度最终还是取决于操作系统,在一些平台上不同的优先级实际会变得相同;另一方面优先级可能会被系统自行改变。

c.线程的五种状态

在任意一个时间点,一个线程只能有且只有其中的一种状态:

注意区别

  • 阻塞状态:在等待获取到一个排他锁,在另外一个线程放弃这个锁的时候发生;
  • 等待状态:在等待一段时间或者唤醒动作的发生,在程序等待进入同步区域的时候发生。

下图是线程状态之间的转换:

上一篇 下一篇

猜你喜欢

热点阅读