java高并发的volatile与Java内存模型详解

public class Demo09 {
    public static boolean flag = true;
    public static class T1 extends Thread {
        public T1(String name) {
            super(name);
        }
        @Override
        public void run() {
            System.out.println("线程" + this.getName() + " in");
            while (flag) {
                ;
            }
            System.out.println("线程" + this.getName() + "停止了");
        }
    }
    public static void main(String[] args) throws InterruptedException {
        new T1("t1").start();
        //休眠1秒
        Thread.sleep(1000);
        //将flag置为false
        flag = false;
    }
}

运行上面代码,会发现程序无法终止。

线程t1的run()方法中有个循环,通过flag来控制循环是否结束,主线程中休眠了1秒,将flag置为false,按说此时线程t1会检测到flag为false,打印“线程t1停止了”,为何和我们期望的结果不一样呢?运行上面的代码我们可以判断,t1中看到的flag一直为true,主线程将flag置为false之后,t1线程中并没有看到,所以一直死循环。

那么t1中为什么看不到被主线程修改之后的flag?

要解释这个,我们需要先了解一下java内存模型(JMM),Java线程之间的通信由Java内存模型(本文简称为JMM)控制,JMM决定一个线程对共享变量的写入何时对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。Java内存模型的抽象示意图如下:

从上图中可以看出,线程A需要和线程B通信,必须要经历下面2个步骤:

1.首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去

2.然后,线程B到主内存中去读取线程A之前已更新过的共享变量

下面通过示意图来说明这两个步骤:

如上图所示,本地内存A和B有主内存中共享变量x的副本。假设初始时,这三个内存中的x值都为0。线程A在执行时,把更新后的x值(假设值为1)临时存放在自己的本地内存A中。当线程A和线程B需要通信时,线程A首先会把自己本地内存中修改后的x值刷新到主内存中,此时主内存中的x值变为了1。随后,线程B到主内存中去读取线程A更新后的x值,此时线程B的本地内存的x值也变为了1。从整体来看,这两个步骤实质上是线程A在向线程B发送消息,而且这个通信过程必须要经过主内存。JMM通过控制主内存与每个线程的本地内存之间的交互,来为java程序员提供内存可见性保证。

对JMM了解之后,我们再看看文章开头的问题,线程t1中为何看不到被主线程修改为false的flag的值,有两种可能:

1.主线程修改了flag之后,未将其刷新到主内存,所以t1看不到

2.主线程将flag刷新到了主内存,但是t1一直读取的是自己工作内存中flag的值,没有去主内存中获取flag最新的值

对于上面2种情况,有没有什么办法可以解决?

是否有这样的方法:线程中修改了工作内存中的副本之后,立即将其刷新到主内存;工作内存中每次读取共享变量时,都去主内存中重新读取,然后拷贝到工作内存。

java帮我们提供了这样的方法,使用volatile修饰共享变量,就可以达到上面的效果,被volatile修改的变量有以下特点:

1.线程中读取的时候,每次读取都会去主内存中读取共享变量最新的值,然后将其复制到工作内存

2.线程中修改了工作内存中变量的副本,修改之后会立即刷新到主内存

我们修改一下开头的示例代码:

public volatile static boolean flag = true;

使用volatile修饰flag变量,然后运行一下程序,输出:

线程t1 in
线程t1停止了

这下程序可以正常停止了。volatile解决了共享变量在多线程中可见性的问题,可见性是指一个线程对共享变量的修改,对于另一个线程来说是否是可以看到的。\

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注我们的更多内容!

(0)

相关推荐

  • Java 内存模型(JMM)

    目录 四.Happens-Before 规则 Java 内存模型 一.什么是 Java 内存模型 Java 内存模型定义如下: 内存模型限制的是共享变量,也就是存储在堆内存中的变量,在 Java 语言中,所有的实例变量.静态变量和数组元素都存储在堆内存之中.而方法参数.异常处理参数这些局部变量存储在方法栈帧之中,因此不会在线程之间共享,不会受到内存模型影响,也不存在内存可见性问题. 通常,在线程之间的通讯方式有共享内存和消息传递两种,很明显,Java 采用的是第一种即共享的内存模型,在共享的内存

  • Java中多线程与并发_volatile关键字的深入理解

    一.volatile关键字 volatile是JVM提供的一种轻量级的同步机制,特性: 1.保证内存可见性 2.不保证原子性 3.防止指令重排序 二.JMM(Java Memory Model) Java内存模型中规定了所有的变量都存储在主内存中(如虚拟机物理内存中的一部分),每条线程还有自己的工作内存(如CPU中的高速缓存),线程的工作内存中保存了该线程使用到的变量到主内存的副本拷贝,线程对变量的所有操作(读取.赋值)都必须在工作内存中进行,而不能直接读写主内存中的变量.不同线程之间无法直接访

  • 详解Java的内存模型

    JVM的内存模型 Java "一次运行,到处编译" 的真面目 说JVM内存模型之前,先聊一个老生常谈的问题,为什么Java可以 "一次编译,到处运行",这个话题最直接的答案就是,因为Java有JVM啊,解释这个答案之前,我想先回顾一下一个语言被编译的过程: 一般编程语言的编译过程大抵就是,编译--连接--执行,这里的编译就是,把我们写的源代码,根据语义语法进行翻译,形成目标代码,即汇编码.再由汇编程序翻译成机器语言(可以理解为直接运行于硬件上的01语言):然后进行连

  • Java并发内存模型详情

    目录 1.Java内存模型 2.硬件内存架构 3.实际执行 3.1 共享对象可见性 3.2 竞争条件 Java是一门支持多线程执行的语言,要编写正确的并发程序,了解Java内存模型是重要前提.而了解硬件内存模型有助于理解程序的执行. 本文主要整理以下内容 Java内存模型 硬件内存架构 共享对象可见性 竞争条件 1.Java内存模型 Java内存模型最新修订是在Java5. JSR-176 罗列了 J2SE5.0 相关发布特性,包含其中的 JSR-133(JavaTM内存模型与线程规范),jav

  • 谈谈对Java中的volatile的理解

    前言 volatile相关的知识其实自己一直都是有掌握的,能大概讲出一些知识,例如:它可以保证可见性:禁止指令重排.这两个特性张口就来,但要再往深了问,具体是如何实现这两个特性的,以及在什么场景下使用volatile,为什么不直接用synchronized这种深入和扩展相关的问题,就回答的不好了.因为volatile是面试必问的知识,所以这次准备把这部分知识也给啃掉. 系统处理效率与Java内存模型 在计算机中,每条程序指令都是在CPU中执行的,而CPU执行指令的数据都是临时存储在内存中的,但是

  • Java中JMM与volatile关键字的学习

    目录 JMM volatile关键字 可见性与原子性测试 哪些地方用到过volatile? 单例模式的安全问题 你知道CAS吗? CAS底层原理 CAS缺点 ABA问题 总结 JMM JMM是指Java内存模型,不是Java内存布局,不是所谓的栈.堆.方法区. 每个Java线程都有自己的工作内存.操作数据,首先从主内存中读,得到一份拷贝,操作完毕后再写回到主内存. JMM可能带来可见性.原子性和有序性问题. 1.可见性:指当一个线程修改了某一个共享变量的值,其他线程是否能够立即知道这个修改.显然

  • java Volatile与Synchronized的区别

    引言 在研究并发程序时,我们可能都知道volatile和synchronized是用于多线程中,用于线程安全和变量可见性的,但是具体两者怎么使用,有何区别可能还是稀里糊涂一知半解,在此就自己简单的理解总结一下二者的区别,和大家一块儿学习!我们需要了解java中关键字volatile和synchronized关键字的使用以及lock类的用法. 首先,了解下java的内存模型: java的线程内存模型中定义了每个线程都有一份自己的共享变量副本(本地内存),里面存放自己私有的数据,其他线程不能直接访问

  • java高并发的volatile与Java内存模型详解

    public class Demo09 { public static boolean flag = true; public static class T1 extends Thread { public T1(String name) { super(name); } @Override public void run() { System.out.println("线程" + this.getName() + " in"); while (flag) { ;

  • java高并发的用户线程和守护线程详解

    目录 程序只有守护线程时,系统会自动退出 设置守护线程,需要在start()方法之前进行 线程daemon的默认值 总结 守护线程是一种特殊的线程,在后台默默地完成一些系统性的服务,比如垃圾回收线程.JIT线程都是守护线程.与之对应的是用户线程,用户线程可以理解为是系统的工作线程,它会完成这个程序需要完成的业务操作.如果用户线程全部结束了,意味着程序需要完成的业务操作已经结束了,系统可以退出了.所以当系统只剩下守护进程的时候,java虚拟机会自动退出. java线程分为用户线程和守护线程,线程的

  • Java 高并发六:JDK并发包2详解

    1. 线程池的基本使用 1.1.为什么需要线程池 平时的业务中,如果要使用多线程,那么我们会在业务开始前创建线程,业务结束后,销毁线程.但是对于业务来说,线程的创建和销毁是与业务本身无关的,只关心线程所执行的任务.因此希望把尽可能多的cpu用在执行任务上面,而不是用在与业务无关的线程创建和销毁上面.而线程池则解决了这个问题,线程池的作用就是将线程进行复用. 1.2.JDK为我们提供了哪些支持 JDK中的相关类图如上图所示. 其中要提到的几个特别的类. Callable类和Runable类相似,但

  • java 高并发中volatile的实现原理

    java 高并发中volatile的实现原理 摘要: 在多线程并发编程中synchronized和Volatile都扮演着重要的角色,Volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的"可见性".可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值.它在某些情况下比synchronized的开销更小 1. 定义: java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应该确保通过排他锁单独获得这个变量.

  • Java多线程高并发中的Fork/Join框架机制详解

    1.Fork/Join框架简介 Fork/Join 它可以将一个大的任务拆分成多个子任务进行并行处理,最后将子任务结果合并成最后的计算结果,并进行输出.Fork/Join 框架要完成两件事情: Fork:把一个复杂任务进行分拆,大事化小 :把一个复杂任务进行分拆,大事化小 Join:把分拆任务的结果进行合并 在 Java 的 Fork/Join 框架中,使用两个类完成上述操作: ForkJoinTask: 我们要使用 Fork/Join 框架,首先需要创建一个 ForkJoin 任务.该类提供了

  • Java中由substring方法引发的内存泄漏详解

    内存溢出(out of memory ) :通俗的说就是内存不够用了,比如在一个无限循环中不断创建一个大的对象,很快就会引发内存溢出. 内存泄漏(leak of memory) :是指为一个对象分配内存之后,在对象已经不在使用时未及时的释放,导致一直占据内存单元,使实际可用内存减少,就好像内存泄漏了一样. 由substring方法引发的内存泄漏 substring(int beginIndex, int endndex )是String类的一个方法,但是这个方法在JDK6和JDK7中的实现是完全

  • Golang 内存模型详解(一)

    开始之前 首先,这是一篇菜B写的文章,可能会有理解错误的地方,发现错误请斧正,谢谢. 为了治疗我的懒癌早期,我一次就不写得太多了,这个系列想写很久了,每次都是开了个头就没有再写.这次争取把写完,弄成一个系列. 此 nil 不等彼 nil 先声明,这个标题有标题党的嫌疑. Go 的类型系统是比较奇葩的,nil 的含义跟其它语言有些差别,这里举个例子(可以直接进入 http://play.golang.org/p/ezFhXX0dnB 运行查看结果): 复制代码 代码如下: package main

  • 易语言对象内存模型详解

    易语言对象的所有方法都是虚的.易语言的对象内存布局和COM几乎一致! 大家在学习的时候有任何问题,可以在下方的留言区讨论,感谢大家对我们的支持.

  • Java高并发BlockingQueue重要的实现类详解

    ArrayBlockingQueue 有界的阻塞队列,内部是一个数组,有边界的意思是:容量是有限的,必须进行初始化,指定它的容量大小,以先进先出的方式存储数据,最新插入的在对尾,最先移除的对象在头部. public class ArrayBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, java.io.Serializable { /** 队列元素 */ final Object

  • 详解Java高并发编程之AtomicReference

    目录 一.AtomicReference 基本使用 1.1.使用 synchronized 保证线程安全性 二.了解 AtomicReference 2.1.使用 AtomicReference 保证线程安全性 2.2.AtomicReference 源码解析 2.2.1.get and set 2.2.2.lazySet 方法 2.2.3.getAndSet 方法 2.2.4.compareAndSet 方法 2.2.5.weakCompareAndSet 方法 一.AtomicReferen

随机推荐