多线程之volatile关键字

每个线程都运行在java栈内存中,每个线程都有自己的工作内存。线程的计算一般是通过工作内存进行交互的。如图所示:


从上图中我们可以看到,线程在初始化时从主内存中加载所需的变量值到工作内存中,然后在线程运行时,如果是读取,则直接从工作内存中读取,如果是写入则先写到工作内存中,之后再刷新到主内存中,这个可以看做是JVM的一个简单的内存模型,但是这样的结构在多线程的情况下有可能会出问题。比如:A线程修改变量的值,也刷新到主内存中了,但是此时B、C线程读取的还是本线程的工作内存,也就是它们读取的不是最新的值,此时就会出现不同线程持有的公共资源不同步的情况。

对于此类问题:我们可以使用synchronized关键字来同步代码块,也可以使用Lock锁来解决该问题,不过Java可以使用volatile关键字更简单的解决此类问题。比如在一个变量前加上volatile关键字。。可以确保每个线程对本地变量的访问和修改都是直接与主内存交互的,而不是与本线程的工作内存交互的。保证每个线程都能获得最“新鲜”的变量值。如下图所示:

但是此时我们需要注意的是:volatile变量只能保证线程取的是最新的值,但是并不能保证数据的同步性。两个线程同时修改一个volatile,有可能会产生脏数据。请看代码的例子:

package com.zkn.newlearn.thread;

/**
 * Created by wb-zhangkenan on 2016/11/4.
 */
public class ThreadTestVolatile01 {

    public static void main(String[] args){
        int value = 100;
        //控制循环次数
        int loops = 0;
        //获取线程组
        ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
        while(loops < 1000){
            //让多线程共享TestVolatile中count值
            TestVolatile testVolatile = new TestVolatile();
            for(int i=0;i<value;i++){
                new Thread(testVolatile).start();
            }
            do{
                try {
                    //让线程休眠15秒
                    Thread.sleep(15);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }while (threadGroup.activeCount() != 2);
            threadGroup.list();
            if(testVolatile.getCount() != value){
                System.out.println("循环到第"+loops+"遍时,出现线程不安全的情况!!");
                System.out.println("此时 count值为:"+testVolatile.getCount());
                System.exit(0);
            }
            loops++;
        }
    }
}

class TestVolatile implements Runnable{

    private volatile int count;

    @Override
    public void run() {
        for(int i=0;i<1000;i++){
            Math.hypot(Math.pow(92456789,i),Math.cos(i));
        }
        count++;
    }

    public int getCount() {

        return count;
    }
}

这段代码的逻辑是这样的:

启动1000个线程,修改共享资源count的值。

休眠15毫秒,让活动线程数变为1(这里有一个Monitor Ctrl Break线程)。

判断实际值和理想值是否一致,如果不一致则此时出现脏数据

我们先来看看自加的操作:count++这句话可以分为两部分,先取出count的值,再执行加1的操作。所以在某两个紧邻的时间片段内可能会出现下面的事情:

1、第一个时间片段:

A线程获得执行机会,因为有关键字volatile修饰,所以它从主内存中获得count的最新值98,记下来的事情又分为两种类型:

如果是单CPU,此时调度器暂停A线程,让出执行几乎给B线程,于是B线程也获得了count的最新值98。

如果是多CPU,此时线程A继续执行,而线程B也同时获得count的最新值98。

2、第二个时间片段:

如果是单CPU,B线程执行完加1动作(原子操作),count的值为99:。由于是volatile类型的变量,所以直接写入主内存,然后A线程继续执行,计算的结果也是99:,重新写入主内存中。

如果是多CPU,A线程执行完加1动作后修改主内存的变量count为99:,线程B执行完毕后也修改住内存中的变量为99:

当这两个时间片执行完毕后,原本期望的结果为100,单运行后的值却为99:,这表示出现了线程不安全的情况,这也证明了,volatile关键字并不能保证线程安全,它只是能保证当线程需要该变量的值时能够获取到最新的值,不能保证多个线程修改的安全性。
参考自:编写高质量代码 改善Java程序的151个建议。

时间: 2024-09-16 07:12:34

多线程之volatile关键字的相关文章

学习Java多线程之volatile域_java

前言 有时仅仅为了读写一个或者两个实例域就使用同步的话,显得开销过大,volatile关键字为实例域的同步访问提供了免锁的机制.如果声明一个域为volatile,那么编译器和虚拟机就知道该域是可能被另一个线程并发更新的.再讲到volatile关键字之前我们需要了解一下内存模型的相关概念以及并发编程中的三个特性:原子性,可见性和有序性. 1. java内存模型与原子性,可见性和有序性 Java内存模型规定所有的变量都是存在主存当中,每个线程都有自己的工作内存.线程对变量的所有操作都必须在工作内存中

Java并发编程之volatile关键字解析

原文出处海子 一内存模型的相关概念 二并发编程中的三个概念 三Java内存模型 四深入剖析volatile关键字 五使用volatile关键字的场景 volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以重获生机. volatile关键字虽然从字面上理解起来比较简单,但是要用好不是一件容易的事情.由于volatile关键字是与Java的内存模型有关的,

Java多线程之synchronized关键字详解

多线程的同步机制对资源进行加锁,使得在同一个时间,只有一个线程可以进行操作,同步用以解决多个线程同时访问时可能出现的问题. 同步机制可以使用synchronized关键字实现. 当synchronized关键字修饰一个方法的时候,该方法叫做同步方法. 当synchronized方法执行完或发生异常时,会自动释放锁. 下面通过一个例子来对synchronized关键字的用法进行解析. 1,是否使用synchronized关键字的不同 public class ThreadTest  {      

多线程之:volatile变量的一次写操作的过程

一:对volatile修饰的变量进行一次写操作的完整过程   在 java 垃圾回收整理一文中,描述了jvm运行时刻内存的分配.其中有一个内存区域是jvm虚拟机栈,每一个线程运行时都有一个线程栈,线程栈保存了线程运行时候变量值信息.当线程访问某一个对象时候值的时候,首先通过对象的引用找到对应在堆内存的变量的值,然后把堆内存变量的具体值load到线程本地内存中,建立一个变量副本,之后线程就不再和对象在堆内存变量值有任何关系,而是直接修改副本变量的值,在修改完之后的某一个时刻(线程退出之前),自动把

Java多线程初学者指南(6):慎重使用volatile关键字

volatile关键字相信了解Java多线程的读者都很清楚它的作用.volatile关键字用于声明简单类型变量,如int.float.boolean等数据类型.如果这些简单数据类型声明为volatile,对它们的操作就会变成原子级别的.但这有一定的限制.例如,下面的例子中的n就不是原子级别的: package mythread; public class JoinThread extends Thread { public static volatile int n = 0; public vo

volatile关键字的说明以及测试

volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统.硬件或者其它线程等.遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问. 使用该关键字的例子如下: int volatile nVint; 当要求使用volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据.而且读取的数据立刻被保存. 例如: volatile int i=10; in

Java线程:volatile关键字

谈及到volatile关键字,不得不提的一篇文章是:<Java 理论与实践: 正确使用 Volatile 变量>,这篇文章对volatile关键字的用法做了相当精辟的阐述. 之所以要单独提出volatile这个不常用的关键字原因是这个关键字在高性能的多线程程序中也有很重要的用途,只是这个关键字用不好会出很多问题. 首先考虑一个问题,为什么变量需要volatile来修饰呢? 要搞清楚这个问题,首先应该明白计算机内部都做什么了.比如做了一个i++操作,计算机内部做了三次处理:读取-修改-写入. 同

Java并发编程:从根源上解析volatile关键字的实现

Java并发编程:volatile关键字解析 1.解析概览 内存模型的相关概念 并发编程中的三个概念 Java内存模型 深入剖析volatile关键字 使用volatile关键字的场景 2.内存模型的相关概念 缓存一致性问题.通常称这种被多个线程访问的变量为共享变量. 也就是说,如果一个变量在多个CPU中都存在缓存(一般在多线程编程时才会出现),那么就可能存在缓存不一致的问题. 为了解决缓存不一致性问题,通常来说有以下2种解决方法: 通过在总线加LOCK#锁的方式 通过缓存一致性协议 这2种方式

转 Java并发编程:volatile关键字解析

volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java  5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以重获生机. volatile关键字虽然从字面上理解起来比较简单,但是要用好不是一件容易的事情.由于volatile关键字是与Java的内存模型有关的,因此在讲述volatile关键之前,我们先来了解一下与内存模型相关的概念和知识,然后分析了volatile关键字的实现原理,最后给出了几个使用vol