Java并发编程系列:volatile关键字

1、原子性、可见性和有序性

Java内存模型主要是围绕着线程并发过程当中如何处理原子性、可见性和顺序性这三个特征来设计的。linux

一、原子性c++

原子性表示任意时刻只有一个线程能够执行某一段功能代码,以防止多个线程同时访问某些共享数据时,形成错误。windows

二、可见性数组

可见性是指一个线程修改了某个共享变量后,其余线程可以马上访问到被修改后的最新数据,也就是共享数据对其余线程都是可见的。
volatile修饰的变量在修改后会当即同步到主内存,在使用时会从新从主内存中读取。volatile变量是依赖主内存为中介来保证多线程下变量对其余线程的可见性。
synchronized关键字是经过在unlock以前必须把变量同步回主内存来实现可见性的。
final关键字则是由于变量在初始化后,值就不会更改,因此只要在初始化过程当中没有把this指针传递出去也能保证对其余线程的可见性。安全

三、有序性多线程

程序在运行时,指令的执行顺序并非严格按照从上到下顺序执行的,可能会进行指令重排。根据CPU流水线做业,通常来讲,简单的操做会先执行,复杂的操做后执行。
有序性从不一样的角度来看是不一样的。单纯从单线程来看都是有序的,但到了多线程就不同了。能够这么理解,若是在一个线程内部观察,全部操做都是有序的。可是若是在一个线程内观察另外一个线程,操做多是无序的。也就是CPU进行的指令重排序对单线程程序而言,不会有什么问题,可是对于多线程程序,就可能出现问题。而有序性就是防止指令重排序带来的问题。并发

2、Java内存模型

一、内存模型产生缘由性能

Java存在一个线程可见性的问题,是因为Java内存模型的缘由。
Java是跨平台语言,能够支持不一样的硬件平台,这是Java虚拟机的功劳。Java内存模型(Java Memory Model,JMM)是Java虚拟机规范定义的,用来屏蔽掉Java程序在各类不一样硬件和操做系统对内存访问的差别,这样就能够实现Java程序在各类不一样的硬件平台上都能达到内存访问的一致性。能够避免像c、c++等直接使用物理硬件和操做系统的内存模型在不一样操做系统和硬件平台下的不兼容。好比有些c/c++程序在windows平台运行正常,而在linux平台运行就会出问题。this

二、内存模型概念spa

虽然Java程序运行在Java虚拟机上面,内存等是虚拟机的一部分,但实际也是使用的物理机的,只不过是Java虚拟机屏蔽了底层硬件细节,统一作了处理。
Java内存模型的主要目标是定义程序中变量的访问规则,即在Java虚拟机中将变量存储到主内存或将变量从主内存中取出这样的底层细节。须要注意的是这里的变量跟Java程序中的变量不是彻底等同的。这里的变量是指实例字段,静态字段,构成数组对象的元素,可是不包括线程内部的局部变量和方法参数,由于这是线程私有的。

这里能够简单的认为,主内存是Java虚拟机内存区域中的堆,局部变量和方法参数是在虚拟机栈中定义的。可是在堆中的变量若是在多线程中使用,就涉及到了堆和不一样虚拟机栈中变量的值的一致性问题了。

Java内存模型概念:
主内存:Java虚拟机规定全部的变量都必须在主内存中产生,为了方便理解,能够认为是堆区。与前面说的物理机的主内存相比,物理机的主内存是整个机器的内存,而虚拟机的主内存是虚拟机内存中的一部分。
工做内存:Java虚拟机中每一个线程都有本身的工做内存,该内存是线程私有的,为了方便理解,能够认为是虚拟机栈。线程的工做内存保存了线程须要的变量在主内存中的副本。

Java虚拟机规定,线程对主内存共享变量的操做必须在线程的各自的工做内存中进行,不能直接读写主内存中的变量。不一样的线程之间也不能相互访问对方的工做内存。若是线程之间须要传递变量的值,必须经过主内存来做为中介进行传递。这里须要说明一下,Java内存模型是一个抽象概念,其实并不存在,它描述的是一种规范。相似下图

3、volatile和synchronized关键字

一、volatile

volatile关键字修饰的变量能够保证可见性和有序性。volatile类型的变量在修改后会当即同步到主内存,在使用的时候会从主内存中从新读取,是依赖主内存为中介来保证多线程下变量对其余线程的可见性的。而volatile禁止了指令重排序,从而保证指令的有序性。
线程访问volatile变量时,可以获取到最新值,但volatile变量并不能用于线程同步。

二、volatile和synchronized的区别

synchronized用于线程同步,可是和volatile不同,synchronized能够保证原子性、可见性和有序性。

4、volatile使用场景

一、状态标识

好比用一个变量做为状态标识来控制多个线程协同工做。每一个线程经过判断状态标识的值来肯定是否执行相关代码。那这个状态标识的变量就可使用volatile关键字修饰,以确保当某个线程更新了变量的值以后,其余的线程能够当即得到最新的值。 

二、一个线程写,多个线程读 

volatile很适用一个线程写,多个线程读的场合。好比,相似发布订阅的机制,当一个写线程更新volatile变量值的话,其余读线程能够当即获取到最新值。

三、volatile和synchronized实现“低开销读-写锁” 

以下显示的线程安全的计数器,使用 synchronized 确保增量操做是原子的,并使用 volatile 保证当前结果的可见性。若是更新不频繁的话,该方法可实现更好的性能,由于读路径的开销仅仅涉及 volatile 读操做,这一般要优于一个无竞争的锁获取的开销。

public class CheesyCounter {  

    private volatile int value;  
  
    //读操做,无需synchronized,提升性能  
    public int getValue() {   
        return value;   
    }   
  
    //写操做,必须synchronized。由于x++不是原子操做  
    public synchronized int increment() {  
        return value++;  
    }  
}

 

参考文章:

https://www.jianshu.com/p/5584600d2569https://www.jianshu.com/p/15106e9c4bf3

相关文章
相关标签/搜索