本文意在分析java多线程编程中的volatile作用,除了介绍volatile基本信息与使用,还会从多个层面分析volatile的原理,分别从JMM实现规范与内存语义等多个层面解析volatile,做到知其然更知其所以然,才能在多线程编程中更合理的使用volatile关键字;本文也会对和volatile类似的synchronize关键字从多个层面做分析,主要是讲解jdk1.6之后对synchronize关键字的优化。
多线程编程真的比单线程要快么?
java支持多线程目的是为了让程序异步执行,从而更快的对结果进行处理响应,在大多数情况下,多线程代码比在单线程代码环境中执行的要快,但世事无绝对,多线程代码并不是绝对比单线程快(上下文切换导致性能开销),如何让自己写的多线程代码执行效率高,这就是多线程编程所要面临的挑战。
1.并发编程的挑战
如上文所言,并发编程的目的是为了让程序运行的更快。但是,并不是启动更多的线程就能让程序最大限度地并发执行,在进行并发编程时,如果希望通过多线程执行任务让程序运行得更快,会面临非常多的挑战,比如上下文切换的问题,死锁的问题,以及受限于硬件和软件的资源限制问题,下面会介绍这些在并发编程中要面临的问题。
1.1上下文切换
cpu分配时间片执行多线程程序示意图即使是单核处理器也支持多线程执行代码,CPU通过给每个线程分配时间片来实现此机制。时间片是CPU分配给各个线程的执行时间,因为时间片非常短,所以CPU要通过不停的切换线程执行(这让程序员产生一种幻觉:多个线程是同时在执行的)。在上图中,线程程序1切换到下一线程前,会保存当前任务的状态,以便下一次循环到自己时才能继续恢复执行任务。所以任务从保存到再加载的过程就是一次上下文切换。
这就像我们同时阅读两本书,在阅读英文书时发现某个单词不认识,去字典查,但在查字典前需要我们先记住当前英文书阅读到了第几页等信息,才能查字典,这样我们才能在查完字典之后继续恢复阅读英文书。这样的切换会影响我们的读书效率,同理上下文切换也会影响多线程的执行速度。
1.2 volatile 简介
在多线程编程中有两个重要的角色,volatile和synchronize,其中volatile相当于轻量级的synchronize,volatile在多线程并发编程中保证了共享变量的“可见行”,可见行即当一个线程修改这个共享变量时,另一个线程能够读到这个修改的值。volatile使用恰当能够,会比synchronize使用执行成本更低(即使synchronize在1.6之后做了优化),主要原因是volatile不会引起线程上下文的切换和调度。
1.3 volatile 定义与实现原理
volatile定义如下:
java允许线程访问共享变量,为了确保共享变量能够被准确且一致的更新,线程应该用排他锁获得这个变量,java提供了volatile,在某些情况下使用volatile比使用锁更加方便轻量,如果一个共享变量被声明为volatile,java内存模型确保所有线程看到这个变量的值是一致的。
volatile 实现原理
在使用volatile变量进行写操作时,CPU会做如下事情:
java代码如下:
volatile Boolean flag = true;
转换为汇编语言大致如下:
image.png
lock指令会在多核处理器下引发两件事情:
1. 将当前处理器缓存行的数据写回到内存中
2. 这个写回内存的操作会使其他CPU里缓存了该内存地址的数据无效
多个线程内存与主内存的关系:
image.png从上图可知,线程A若要与线程B进行通信的话,在java中需要如下两步:
- 线程A将本地内存A中更新过的共享变量刷到主内存中;
- 线程B去主内存中读取线程A已经更新到主内存中的共享变量值。
此过程示意图如下:
image.png
以上就是volatile实现原理的大体流程,但如果想更深层次的了解volatile的原理实现,就不得不从volatile的内存语义,与JSR-qee内存模型两个层面开始说起,在从这两个层面开始谈volatile内存语义之前,需要先了解几个概念与现象
1.4 重排序
java程序在执行时,为了提高性能,编译器和处理器常常会对指令进行重排序,重排序分3种类型:
- 编译器优化的重排序,编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序
- 指令级并行的重排序。
-
内存系统的重排序,由于处理器使用缓存和读/写缓冲区,,使得加载和存储操作看上去是在乱序执行
image.png
上图的1属于编译器重排序,2,3属于处理器重排序。这些重排序会导致多线程程序出现内存可见行问题
举例如下:
//线程A
a=1; //A1
x=b; //A2
//线程B
b=2;
y=a;
假设处理器A和处理器B按程序的顺序并行执行内存访问,最终可能的得到x=y=0;的结果。出现此结果的原因如下:
image.png
虽然处理器执行内存操作的顺序是A1->A2,但内存实际操作的顺序却是A2->A1,此时,处理器A的内存操作顺序被重排序了
image.png
网友评论