Java并发之volatile关键字

引言
说到多线程,我觉得我们最重要的是要理解一个临界区概念。

举个例子,一个班上1个女孩子(临界区),49个男孩子(线程),男孩子的目标就是这一个女孩子,就是会有竞争关系(线程安全问题)。推广到实际场景,例如对一个数相加或者相减等等情形,因为操作对象就只有一个,在多线程环境下,就会产生线程安全问题。理解临界区概念,我们对多线程问题可以有一个好意识。

Jav内存模型(JMM)

谈到多线程就应该了解一下Java内存模型(JMM)的抽象示意图.下图:

线程A和线程B执行的是时候,会去读取共享变量(临界区),然后各自拷贝一份回到自己的本地内存,执行后续操作。
JMM模型是一种规范,就像Java的接口一样。JMM会涉及到三个问题:原子性,可见性,有序性。
所谓原子性。就是说一个线程的执行会不会被其他线程影响的。他是不可中断的。举个例子:

int i=1
这个语句在Jmm中就是原子性的。无论是一个线程执行还是多个线程执行这个语句,读出来的i就是等于1。那什么是非原子性呢,按道理如果Java的代码都是原子性,应该就不会有线程问题了啊。其实JMM这是规定某些语句是原子性罢了。举个非原子性例子:

i ++;
这个操作就不是原子性的了。因为他就是包含了三个操作:第一读取i的值,第二将i加上1,第三将结果赋值回来给i,更新i的值。
所谓可见性。可见性表示如果一个值在线程A修改了,线程B就会马上知道这个结果。
所谓有序性。所谓有序性值的是语意的有序性。就是说代码顺序可能会发生变化。因为有一个指令重排机制。所谓指令重排,他会改变代码执行顺序,为了让cpu执行效率更高。为了防止重排序出错,JMM有个happen-before规则,这个规则限制了那些语句执行在前,那些语句执行在后。
Happen-before:
程序顺序原则:一个线程内保证语义的串行性
volatile原则:volatile变量的写发生在读之前
锁规则:先加锁再解锁
传递性:a先于b,b先于c,则a必定先于c
线程的start方法先于他的每一个操作
线程所有的操作先于线程的终结
对象的构造函数执行、结束先于finalize()方法。

volatile
进入正题,volatile可以保证变量(临界区)的可见性以及有序性,但是不能保证原子性。举个例子:

public class VolatileTest implements Runnable{
    private static VolatileTest volatileTest = new VolatileTest();
    private  static volatile int i= 0;
    public static void main(String[] args) throws InterruptedException {
        for (int j = 0; j < 20; j++) {
            Thread a = new Thread(new VolatileTest());
            Thread b = new Thread(new VolatileTest());
            a.start();b.start();
            a.join();b.join();
            System.out.print(i+"&&");
        }

    }

    @Override
    public void run() {
        for (int j = 0; j < 1000; j++) {
            i++;
        }
    }

}

// 输出结果
// 2000&&4000&&5852&&7852&&9852&&11852&&13655&&15655&&17655&&19655&&21306
//&&22566&&24566&&26189&&28189&&30189&&32189&&34189&&36189&&38089&&

有结果看到有问题,虽然i已经添加了volatile关键字,说明volatile关键字不能保证i++的原子性。

那什么场景适合使用volatile关键字

1,轻量级的“读-写锁”策略

private volatile int value;
public int getValue(){ return value;}
public synchronized void doubleValue(){ value = value*value; }

2.单例模式(双检查锁机制

private volatile static Singleton instace;
public static Singleton getInstance(){  // 没有使用同步方法,而是同步方法块
    //第一次null检查 ,利用volatile的线程间可见性,不需要加锁,性能提高
    if(instance == null){
        synchronized(Singleton.class) {    //锁住类对象,阻塞其他线程
            //第二次null检查,以保证不会创建重复的实例
            if(instance == null){
                instance = new Singleton(); // 禁止重排序
            }
        }
    }
    return instance;

参考
《现代操作系统(第三版)中文版》
《实战Java高并发程序设计》
《Java并发编程的艺术》

原文地址:https://blog.51cto.com/14640947/2458345

时间: 2024-10-10 08:30:47

Java并发之volatile关键字的相关文章

java并发之volatile关键字(TIJ21-21.3.3 21.3.4)

** 1 简介    volatile是java中的一个保留关键字,它在英语中的含义是易变的,不稳定的.volatile像final.static等其他修饰符 一样,可以修饰class中的域,而不能修饰方法中的局部变量.当修饰class中的域时,volatile可以修饰primative类型或者任意对 象.下面这个例子展示了这一点: public class TIJ_volatile {    private volatile int i;    private volatile String

java点滴之volatile关键字特性

一 问题引入 JVM把内存分为两层,一层为大的主存,另外一个是工作内存(属于每个线程自己专属的),正常情况下,线程在用到某个变量的值时,都是先取到工作内存中进行处理,然后再写回主存,这样就会带来不同线程变量值不同步的问题. volatile字面意思是易挥发,不稳定,比如100个线程同时访问修改的一个字段值,那么这种值的特性明显和JVM中高速缓存机制不相符,这种值是不适合放在各线程自己的寄存器中的,那会导致100个线程中持有的副本都不一样. 二volatile修饰符作用 当一个变量被声明成 vol

理解java中的volatile关键字

Java语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量.这两种机制的提出都是为了 实现代码线程的安全性.Java 语言中的 volatile 变量可以被看作是一种 "程度较轻的 synchronized":与 synchronized 块相比,volatile 变量所需的编码较少,并且运行时开销也较少,但是它所能实现的功能也仅是 synchronized 的一部分. volatile 写和读的内存语义: 线程 A 写一个 volatile 变量,实质上是线程 A

Java中的volatile关键字

一.计算机内存模型的相关概念 计算机在执行程序时,每条指令都是在CPU中执行的,而执行指令过程中,可能会涉及到数据的读取和写入.由于程序运行过程中的临时数据是存放在主存(物理内存)当中的,由于CPU执行速度很快,而从内存读取数据和向内存写入数据的过程跟CPU执行指令的速度比起来要慢得多,因此如果任何时刻对数据的操作都要通过和内存的交互来进行,会大大降低指令执行的速度,因此自CPU里面就有了高速缓存. 也就是,当程序在运行过程中,会将运算需要的数据从主存复制一份到CPU高速缓存中,那么CPU进行计

java并发系列(六)-----Java并发:volatile关键字解析

在 Java 并发编程中,要想使并发程序能够正确地执行,必须要保证三条原则,即:原子性.可见性和有序性.只要有一条原则没有被保证,就有可能会导致程序运行不正确.volatile关键字 被用来保证可见性,即保证共享变量的内存可见性以解决缓存一致性问题.一旦一个共享变量被 volatile关键字 修饰,那么就具备了两层语义:内存可见性和禁止进行指令重排序.在多线程环境下,volatile关键字 主要用于及时感知共享变量的修改,并使得其他线程可以立即得到变量的最新值,例如,用于 修饰状态标记量 和 D

Java并发之synchronized关键字深度解析(二)

前言 本文继续[Java并发之synchronized关键字深度解析(一)]一文而来,着重介绍synchronized几种锁的特性. 一.对象头结构及锁状态标识 synchronized关键字是如何实现的给对象加锁?首先我们要了解一下java中对象的组成.java中的对象由3部分组成,第一部分是对象头,第二部分是实例数据,第三部分是对齐填充. 对齐填充:jvm规定对象的起始内存地址必须是8字节的整数倍,如果不够的话就用占位符来填充,此部分占位符就是对齐填充: 实例数据:实例数据是对象存储的真正有

Java并发编程 Volatile关键字解析

volatile关键字的两层语义 一旦一个共享变量(类的成员变量.类的静态成员变量)被volatile修饰之后,那么就具备了两层语义: 1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的. 2)禁止进行指令重排序. 根据volatile的语义,我们可以看到,volatile主要针对的是并发三要素(原子性,可见性和有序性)中的后两者有实际优化作用. 可见性: 线程本身并不直接与主内存进行数据的交互,而是通过线程的工作内存来完成相应的操作.

Java中的volatile关键字为什么不是不具有原子性

Java中long赋值不是原子操作,因为先写32位,再写后32位,分两步操作,而AtomicLong赋值是原子操作,为什么?为什么volatile能替代简单的锁,却不能保证原子性?这里面涉及volatile,是java中的一个我觉得这个词在Java规范中从未被解释清楚的神奇关键词,在Sun的JDK官方文档是这样形容volatile的: The Java programming language provides a second mechanism, volatile fields, that

详解java中的volatile关键字

一.为什么要有volatile关键字 估计很多java初学者都被volatile这个关键字迷惑过.虽然网上有很多讨论volatile的文章,但它们有的过于讲述底层原理,而没有说明其应用场景,让初学者看后还是一头雾水:有的过于使用类比讲解,造成了一定的错误,这样的文章更害人.下面,小弟试着分析下volatile关键字的作用及用法,希望能给大家带来一定的启发.文中错误之处,请各位大神指正. 我们知道,在多线程编程中,多个线程在访问共享变量时,必须进行必要的同步,否则很可能产生错误.synchroni