.Java 并发编程的两个关键问题程序员
并发是让多个线程同时执行,若线程之间是独立的,那并发实现起来很简单,各自执行各自的就行;但每每多条线程之间须要共享数据,此时在并发编程过程当中就不可避免要考虑两个问题:通讯 与 同步。
通讯
通讯是指消息在两条线程之间传递。
既然要传递消息,那接收线程 和 发送线程之间必需要有个前后关系,此时就须要用到同步。通讯和同步是相辅相成的。
同步
同步是指,控制多条线程之间的执行次序。编程
2. 通讯的方式2.1 通讯方式的种类多线程
线程之间的通讯一共有两种方式:共享内存 和 消息传递。
共享内存
共享内存指的是多条线程共享同一片内存,发送者将消息写入内存,接收者从内存中读取消息,从而实现了消息的传递。
但这种方式有个弊端,即须要程序员来控制线程的同步,即线程的执行次序。
这种方式并无真正地实现消息传递,只是从结果上来看就像是将消息从一条线程传递到了另外一条线程。
消息传递
顾名思义,消息传递指的是发送线程直接将消息传递给接收线程。
因为执行次序由并发机制完成,所以不须要程序员添加额外的同步机制,但须要声明消息发送和接收的代码。
综上所述:对于共享内存的通讯方式,须要进行显示的同步,隐式的通讯;
而对于消息传递的通讯方式,须要隐式的同步,显示的通讯。并发
2.2 Java使用的通讯方式函数
Java使用共享内存的方式实现多线程之间的消息传递。所以,程序员须要写额外的代码用于线程之间的同步。
PS:其实共享内存的方式从实现过程来看,跟消息传递一点关系都没有:一条线程将消息存入共享内存,另外一条线程从共享内存中读这条消息。
但从结果来看,整个过程就好像是一条消息被从线程A传递到了线程B。
这种方式之因此能实现消息传递,依托于两点:操作系统
3. Java多线程的内存模型(简化版)线程
全部线程都共享一片内存,用于存储共享变量;
此外,每条线程都有各自的存储空间,存储各自的局部变量、方法参数、异常对象。对象
4. volatile是什么?排序
Java采用共享内存的方式实现消息传递,而共享内存须要依托于同步。Java提供了synchronized、volatile关键字实现同步。此外volatile关键字还拥有一些额外的功能。内存
5. volatile的使用
在成员变量前加上该关键字便可。
publicvolatilebooleanflag;
6. volatile的特性
6.1 重排序
重排序是计算机为了提升程序执行效率而对代码的执行顺序进行调整。你觉得代码是一行行顺序执行的,但实际并不是如此.
若两行指令之间没有依赖关系,那么计算机能够对他们的顺序进行重排序,但若两行之间的某个变量被volatile修饰后,重排序规则会发生变化。
在如下状况下,即便两行代码之间没有依赖关系,也不会发生重排序:
volatile读
volatile写
6.2 可见性
什么是内存可见性?
“内存可见性”指的是一条线程修改完一个共享变量后,另外一个线程若访问这个变量将会访问到修改后的值。即:一条线程对共享变量的修改,对其余线程当即可见。
但若是未对共享变量采用同步机制,那么共享变量的修改不会对其余线程当即可见。
为何会出现内存不可见的状况?
经过上文可知,在Java中每条线程都有各自独立的存储空间,此外还有一个全部线程共享的内存空间。
当开启线程时,系统会将共享内存中的全部共享变量拷贝一份到线程专属的存储空间中。接下来该线程在结束前的全部操做都是基于本身的存储空间进行的。所以,若一条线程改变了一个共享变量,仅仅改变的是这条线程专属存储空间中的变量值;此时若其余线程访问这个变量,访问的仍然是先前从共享存储空间读出来的值。
然而咱们但愿一条线程将某个共享变量修改后,其余线程能当即访问到这个最新的值,而不是失效值。
这时就须要同步机制来解决这个问题。
如何确保共享变量的可见性?
要确保全部共享变量对全部线程是可见的,就须要给全部共享变量使用同步。在Java中你能够选择将共享变量用同步代码块包裹或用volatile修饰共享变量。
为何volatile能保证共享变量的内存可见性?
volatile修饰了一个成员变量后,这个变量的读写就会比普通变量多一些步骤。
经过对volatile变量读写的限制,就能保证线程每次读到的都是最新的值,从而确保了该变量的内存可见性。
volatile变量赠送的附加功能
进行volatile写操做时,不只会将volatile变量写入共享内存,系统还会将当前线程专属空间中的全部共享变量写入共享内存。
进行volatile读操做时,系统也会一次性将共享内存中全部共享变量读入线程专属空间。
这就意味着,若是普通变量在volatile写操做以前被修改,那么在volatile读操做以后就能正确读到他们。
可是,在volatile写操做以后被修改的普通变量 和 在volatile读操做以前被访问的普通变量 都不具备内存可见性。
6.3 原子性
什么是原子性?
原子性指的是一组操做必须一块儿完成,中途不能被中断。
volatile能确保long、double读写的原子性
在Java中的全部类型中,有long、double类型比较特殊,他们占据8字节(64比特),其他类型都小于64比特。在32位操做系统中,CPU一次只能读取/写入32位的数据,所以对于64位的long、double变量的读写会进行两步。在多线程中,若一条线程只写入了long型变量的前32位,紧接着另外一条线程读取了这个只有“一半”的变量,从而就读到了一个错误的数据。
为了不这种状况,须要在用volatile修饰long、double型变量。
在内存可见性与原子性上,volatile就至关因而同步的setter和getter函数。但并不具备volatile的重排序规则,同步块只确保同步块内部的指令不发生重排序,并不确保同步块之外的指令的重排序。
PS1:Java中的byte居然是字节,bit才是比特(位)。
PS2:char和short-2字节、int和float-4字节、long和double-8字节、byte-1字节
QA:在同步块中调用wait函数是否会破坏原子性?