万字详解JVM,让你一文吃透( 七 )


主内存与工作内存所以的变量都存储在主内存,每条线程还有自己的工作内存,保存了被该线程使用到的变量的主内存副本拷贝 。线程对变量的所有操作(读取、赋值)都必须在工作内存中进行,不能直接读写主内存的变量 。不同的线程之间也无法直接访问对方工作内存的变量,线程间变量值的传递需要通过主内存 。

万字详解JVM,让你一文吃透

文章插图
内存间的交互操作一个变量如何从主内存拷贝到工作内存、如何从工作内存同步回主内存,Java内存模型定义了8种操作:
万字详解JVM,让你一文吃透

文章插图
原子性、可见性、有序性
  • 原子性:对基本数据类型的访问和读写是具备原子性的 。对于更大范围的原子性保证,可以使用字节码指令monitorenter和monitorexit来隐式使用lock和unlock操作 。这两个字节码指令反映到Java代码中就是同步块——synchronized关键字 。因此synchronized块之间的操作也具有原子性 。
  • 可见性:当一个线程修改了共享变量的值,其他线程能够立即得知这个修改 。Java内存模型是通过在变量修改后将新值同步回主内存,在变量读取之前从主内存刷新变量值来实现可见性的 。volatile的特殊规则保证了新值能够立即同步到主内存,每次使用前立即从主内存刷新 。synchronized和final也能实现可见性 。final修饰的字段在构造器中一旦被初始化完成,并且构造器没有把this的引用传递出去 , 那么其他线程中就能看见final字段的值 。
  • 有序性:Java程序的有序性可以总结为一句话,如果在本线程内观察,所有的操作都是有序的(线程内表现为串行的语义);如果在一个线程中观察另一个线程 , 所有的操作都是无序的(指令重排序和工作内存与主内存同步延迟线性) 。
volatile什么是volatile?关键字volatile是Java虚拟机提供的最轻量级的同步机制 。当一个变量被定义成volatile之后,具备两种特性:
  1. 保证此变量对所有线程的可见性 。当一条线程修改了这个变量的值,新值对于其他线程是可以立即得知的 。而普通变量做不到这一点 。
  2. 禁止指令重排序优化 。普通变量仅仅能保证在该方法执行过程中,得到正确结果 , 但是不保证程序代码的执行顺序 。
为什么基于volatile变量的运算在并发下不一定是安全的?volatile变量在各个线程的工作内存,不存在一致性问题(各个线程的工作内存中volatile变量,每次使用前都要刷新到主内存) 。但是Java里面的运算并非原子操作,导致volatile变量的运算在并发下一样是不安全的 。
为什么使用volatile?在某些情况下,volatile同步机制的性能要优于锁(synchronized关键字),但是由于虚拟机对锁实行的许多消除和优化,所以并不是很快 。
volatile变量读操作的性能消耗与普通变量几乎没有差别,但是写操作则可能慢一些 , 因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行 。
并发与线程并发与线程的关系?并发不一定要依赖多线程,PHP中有多进程并发 。但是Java里面的并发是多线程的 。
什么是线程?线程是比进程更轻量级的调度执行单位 。线程可以把一个进程的资源分配和执行调度分开,各个线程既可以共享进程资源(内存地址、文件I/O),又可以独立调度(线程是CPU调度的最基本单位) 。
实现线程有哪些方式?
  • 使用内核线程实现
  • 使用用户线程实现
  • 使用用户线程+轻量级进程混合实现
Java线程的实现操作系统支持怎样的线程模型,在很大程度上就决定了Java虚拟机的线程是怎样映射的 。
Java线程调度什么是线程调度?线程调度是系统为线程分配处理器使用权的过程 。
线程调度有哪些方法?
  • 协同式线程调度:实现简单 , 没有线程同步的问题 。但是线程执行时间不可控,容易系统崩溃 。
  • 抢占式线程调度:每个线程由系统来分配执行时间,不会有线程导致整个进程阻塞的问题 。
虽然Java线程调度是系统自动完成的,但是我们可以建议系统给某些线程多分配点时间——设置线程优先级 。Java语言有10个级别的线程优先级 , 优先级越高的线程,越容易被系统选择执行 。
但是并不能完全依靠线程优先级 。因为Java的线程是被映射到系统的原生线程上,所以线程调度最终还是由操作系统说了算 。如Windows中只有7种优先级,所以Java不得不出现几个优先级相同的情况 。同时优先级可能会被系统自行改变 。Windows系统中存在一个“优先级推进器”,当系统发现一个线程执行特别勤奋,可能会越过线程优先级为它分配执行时间 。

推荐阅读