本篇文章介绍一些多线程的相关的深刻概念。理解后对于线程的安全性会有更深的理解。html
先说一个格言,摘自Java核心技术:
若是向一个变量写入值,而这个变量接下来可能会被另外一个线程读取;或者一个变量读值,而这个变量多是以前被另外一个线程写入的,此时必须同步。java
下面就是概念了。程序员
Monitor实际上是一种同步工具、同步机制,一般被描述成一个对象,主要特色是:编程
互斥
的执行。比如一个Monitor只有一个运行“许可”,任一个线程进入任何一个方法都须要得到这个“许可”,离开时把许可归还。在 Monitor Object 模式中,主要有四种类型参与者:数组
Java中,Object 类自己就是监视者对象,Java 对于 Monitor Object 模式作了内建的支持。缓存
Java的并发采用的是共享内存模型,线程间通讯是隐式的,同步是显示的;而咱们在Android中所常说的Handler通讯即采用的是消息传递模型,通讯是显示的,同步是隐式的。安全
并发编程模型的分类
并发编程中,须要处理两个问题:线程之间如何通讯、线程之间如何同步。bash
Java内存模型的抽象
Java堆内存在线程间共享,下文所说的共享变量即被存储在堆内存中变量:实例域、静态域和数组。局部变量、方法定义参数和异常处理参数不会在线程之间共享,不会有内存可见性问题,也不受内存模型影响。多线程
Java线程之间的通讯由Java内存模型(JMM,Java Memory Module)控制,JMM决定了一个线程对共享变量的写入什么时候对另外一个线程可见。
JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存中,每一个线程都有一个私有的本地内存,也叫工做内存,本地内存中存储了该线程以读/写共享变量的副本。(本地内存是JMM的一个抽象概念,并不真实存在,它涵盖了缓存、写缓冲区、寄存器以及其余的硬件和编译器优化。)
因此线程A和线程B要通讯步骤以下:并发
线程模型图
原子性指:一个操做(有可能包含有多个子操做)要么所有执行(生效),要么所有都不执行(都不生效)。
java.util.concurrent.atomic包中不少类使用了CAS指令来保证原子性,而再也不使用锁。如AtomicInterger
、AtomicBoolean
、AtomicLong
、AtomicReference
等。
原子性不保证顺序一致性,只保证操做是原子的。
可见性是指,当多个线程并发访问共享变量时,一个线程对共享变量的修改,其它线程可以当即看到。
happens-before规则对应于一个或多个编译器和处理器重排序规则,对于程序员来讲,该规则易懂,避免为了理解JMM提供的内存可见性保证而去学习复杂的重排序规则以及这些规则的具体实现。
使用happens-before的概念来阐述操做之间的内存可见性。
若是一个操做执行的结果须要对另外一个操做可见,那么这两个操做之间必需要存在happens-before关系。
这两个操做能够在一个线程内,也能够是不一样线程。
两个操做之间具备happens-before关系,并不意味着前一个操做必需要在后一个操做前执行;仅仅要求前一个操做的执行结果对后一个可见,且前一个操做按顺序排在第二个操做以前。
是现代处理器上提供的高效机器级别的原子指令,这些原子指令以原子方式对内存执行读-写-改操做,这是在多处理器中实现同步的关键。AtomicInterger
、AtomicBoolean
、AtomicLong
的实现都是基于CAS指令。
在计算机中,软件技术和硬件技术有一个共同的目标:在不改变程序执行结果的前提下,尽量的提升开发并行度。
as-if-serial语义
是指无论怎么重排序,单线程程序的执行结果不能被改变。编译器、runtime和处理器都必须遵照as-if-serial语义。
因此,编译器和处理器不会对存在数据依赖关系的操做作重排序,由于这种重排序会改变执行结果。
数据依赖性
重排序对多线程的影响
重排序破坏了多线程程序的语义。对于存在控制依赖的操做(if语句)进行重排序,由于单线程程序是按顺序来执行的,因此执行结果不会改变;而多线程程序中,重排序可能会改变运行结果。
对控制依赖if(flag){b = a*a}
的重排序以下,编译器和处理器会采用猜想执行来克服相关性来对并行度的影响,对先提取并计算a*a,而后把计算结果保存到名为重排序缓冲的硬件缓存中,接下来再判断flag是否为真。另外一个线程设置为true了,并设置a=1,然而取得的值可能为0,与预期不符。这就是影响的一个案例。
重排序的一个示例,摘自EffectiveJava:
while(!done) {
i++
}
//重排后。这种优化称做提示,是HopSpot Server VM的工做
if(!done){
while(true) {
i++;
}
}复制代码
若是一个多线程程序能正确同步,这个程序将是一个没有数据竞争的程序。JMM对正确同步的多线程程序的内存一致性作了以下保证:若是程序是正确同步的,程序的执行将具备顺序一致性,即程序的执行结果与该程序在顺序一致性内存模型中的执行结果相同。
首先要明确,线程的安全性须要三点保证:原子性、可见性,顺序性。只有知足了这三个条件时线程才是安全的。
synchronized、Lock彻底保证了这三点;volatile仅保证了可见性和顺序性(禁止指令重排),在某些状况下可使用volatile代替synchronized以提升性能。在这种状况下,volatile是轻量级的synchronized。
某些状况下是指:
假设对共享变量除了赋值之外并不完成其余操做,那么能够将这些共享变量声明为volatile。即共享变量自己的操做是原子性的、顺序性的,只缺可见性了,此时能够用volatile关键字。在使用时要仔细分析。
具体是指:
要记住,原子性指的是对共享变量的操做(包括其子操做,即多条语句)是一块的,要么执行,要么不执行。不是说用了AtomicInteger就是原子性的,而是对AtomicInteger这个共享变量的操做是否是多条语句,这些多条语句是否是原子性的。
经典示例1:单例模式
经典示例2:
boolean volatile isRunning = false;
public void start () {
new Thread( () -> {
while(isRunning) {
someOperation();
}
}).start();
}
public void stop () {
isRunning = false;//只有赋值操做,非多条语句
}复制代码
参考:
Java进阶(二)当咱们说线程安全时,到底在说什么
Java并发编程:volatile关键字解析
并发模型——共享内存模型(线程与锁)理论篇
《深刻理解Java内存模型》