java高并发系列 - 第7天: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一直为ture,主线程将flag置为false之后,t1线程中没有看到,所以一直死循环。
那么t1中为什么看不到被主线程修改之后的flag?
要解释这个,我们需要先了解一下java内存模型(JMM),Java线程之间的通信由Java内存模型(本文简称为JMM)控制,JMM决定一个线程对共享变量的写入何时对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。Java内存模型的抽象示意图如下:

从上图中可以看出,线程A需要和线程B通信,必须要经历下面2个步骤:
- 首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去
- 然后,线程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的值,有两种可能:
- 主线程修改了flag之后,未将其刷新到主内存,所以t1看不到
- 主线程将flag刷新到了主内存,但是t1一直读取的是自己工作内存中flag的值,没有去主内存中获取flag最新的值
对于上面2种情况,有没有什么办法可以解决?
是否有这样的方法:线程中修改了工作内存中的副本之后,立即将其刷新到主内存;工作内存中每次读取共享变量时,都去主内存中重新读取,然后拷贝到工作内存。
java帮我们提供了这样的方法,使用volatile修饰共享变量,就可以达到上面的效果,被volatile修改的变量有以下特点:
- 线程中读取的时候,每次读取都会去主内存中读取共享变量最新的值,然后将其复制到工作内存
- 线程中修改了工作内存中变量的副本,修改之后会立即刷新到主内存
我们修改一下开头的示例代码:
public volatile static boolean flag = true;
使用volatile修饰flag变量,然后运行一下程序,输出:
线程t1 in
线程t1停止了
这下程序可以正常停止了。
volatile解决了共享变量在多线程中可见性的问题,可见性是指一个线程对共享变量的修改,对于另一个线程来说是否是可以看到的。
java高并发系列
- java高并发系列 - 第1天:必须知道的几个概念
- java高并发系列 - 第2天:并发级别
- java高并发系列 - 第3天:有关并行的两个重要定律
- java高并发系列 - 第4天:JMM相关的一些概念
- java高并发系列 - 第5天:深入理解进程和线程
- java高并发系列 - 第6天:线程的基本操作
- java高并发系列 - 第7天:volatile与Java内存模型
- java高并发系列 - 第8天:线程组
- java高并发系列 - 第9天:用户线程和守护线程
- java高并发系列 - 第10天:线程安全和synchronized关键字
- java高并发系列 - 第11天:线程中断的几种方式
- java高并发系列 - 第12天JUC:ReentrantLock重入锁
java高并发系列连载中,总计估计会有四五十篇文章,可以关注公众号:javacode2018,获取最新文章。

java高并发系列交流群

java高并发系列 - 第7天:volatile与Java内存模型的更多相关文章
- java高并发系列-第1天:必须知道的几个概念
java高并发系列-第1天:必须知道的几个概念 同步(Synchronous)和异步(Asynchronous) 同步和异步通常来形容一次方法调用,同步方法调用一旦开始,调用者必须等到方法调用返回后, ...
- java高并发系列 - 第6天:线程的基本操作
新建线程 新建线程很简单.只需要使用new关键字创建一个线程对象,然后调用它的start()启动线程即可. Thread thread1 = new Thread1(); t1.start(); 那么 ...
- java高并发系列 - 第12天JUC:ReentrantLock重入锁
java高并发系列 - 第12天JUC:ReentrantLock重入锁 本篇文章开始将juc中常用的一些类,估计会有十来篇. synchronized的局限性 synchronized是java内置 ...
- java高并发系列 - 第14天:JUC中的LockSupport工具类,必备技能
这是java高并发系列第14篇文章. 本文主要内容: 讲解3种让线程等待和唤醒的方法,每种方法配合具体的示例 介绍LockSupport主要用法 对比3种方式,了解他们之间的区别 LockSuppor ...
- java高并发系列 - 第15天:JUC中的Semaphore,最简单的限流工具类,必备技能
这是java高并发系列第15篇文章 Semaphore(信号量)为多线程协作提供了更为强大的控制方法,前面的文章中我们学了synchronized和重入锁ReentrantLock,这2种锁一次都只能 ...
- java高并发系列 - 第16天:JUC中等待多线程完成的工具类CountDownLatch,必备技能
这是java高并发系列第16篇文章. 本篇内容 介绍CountDownLatch及使用场景 提供几个示例介绍CountDownLatch的使用 手写一个并行处理任务的工具类 假如有这样一个需求,当我们 ...
- java高并发系列 - 第17天:JUC中的循环栅栏CyclicBarrier常见的6种使用场景及代码示例
这是java高并发系列第17篇. 本文主要内容: 介绍CyclicBarrier 6个示例介绍CyclicBarrier的使用 对比CyclicBarrier和CountDownLatch Cycli ...
- java高并发系列 - 第21天:java中的CAS操作,java并发的基石
这是java高并发系列第21篇文章. 本文主要内容 从网站计数器实现中一步步引出CAS操作 介绍java中的CAS及CAS可能存在的问题 悲观锁和乐观锁的一些介绍及数据库乐观锁的一个常见示例 使用ja ...
- java高并发系列 - 第22天:java中底层工具类Unsafe,高手必须要了解
这是java高并发系列第22篇文章,文章基于jdk1.8环境. 本文主要内容 基本介绍. 通过反射获取Unsafe实例 Unsafe中的CAS操作 Unsafe中原子操作相关方法介绍 Unsafe中线 ...
随机推荐
- 谷歌chrome浏览器被毒霸上网导航www.uu114.cn劫持 chrome://version命令行被篡改
问题描述 win10系统更新, 谷歌chrome浏览器打开后自动跳转到被劫持的网站.我的被hao123劫持, 瞬间对hao123的好感度下降浏览器输入chrome://version 可以看到“命令行 ...
- react后台项目开发(一)
1. 项目开发准备 描述项目 技术选型 api 接口(4部分:3请求{url,请求方式,请求参数格式}, 1响应数据格式)/ 接口文档/ 测试接口 2. 启动项目开发 使用react脚手架创建项目 开 ...
- C++ Debug 模式下程序崩溃: Expression: is_block_type_valid(header->block_use)
出现这样的错误,可能有很多种原因,而我出现崩溃的原因是由于代码中定义了vector容器, 未对它进行初始化操作导致的, 只要对它的大小进行初始化操作就行了 崩溃代码: vector<Rect& ...
- 软件测试价值提升之路- 第三章"拦截缺陷 "读书笔记
作为一个测试团队,基本的职责是:测试产品,发现缺陷,报告结果,使每个版本的测试水准稳步提升.这些价值是作为一个测试所必须具备的,发挥这些价值能够让测试获得研发团队的基本信任.这类价值分为3部分: 1) ...
- tensorflow中卷积、转置卷积具体实现方式
卷积和转置卷积,都涉及到padding, 那么添加padding 的具体方式,就会影响到计算结果,所以搞清除tensorflow中卷积和转置卷积的具体实现有助于模型的灵活部署应用. 一.卷积 举例说明 ...
- Python 从入门到进阶之路(五)
之前的文章我们简单介绍了一下 Python 的函数,本篇文章我们来看一下 Python 中的面向对象. Python从设计之初就已经是一门面向对象的语言,正因为如此,在Python中创建一个类和对象是 ...
- 前端深入之js篇丨Array数组操作从入门到成神Up Up Up,持续更新中
写在前面 随着前端深入的不断学习,发现数组这个数据结构在前端中有着相当大的存在感,由于我初学前端的时候并没有系统性的学习数组,所以我将通过这篇文章同你一起学习数组,希望我们能一起进步,学会熟练操作数组 ...
- 一起学Android之Handler
概述 在Android开发中,有主线程(UI线程)和工作线程(Worker线程)之分,两个线程是相互独立的,并不能相互访问(主线程主要负责UI的更新,不能进行耗时的操作,工作线程主要负责耗时的操作,但 ...
- linux远程登入/远程上传文件
一.远程登入 1.安装 Xshell5 2.查看是否具备连接 在linux 主机上输入 chkconfig --list | grep sshd #sshd 0:关闭 1:关闭 2:启用 3:启用 4 ...
- golang中,new和make的区别
在golang中,make和new都是分配内存的,但是它们之间还是有些区别的,只有理解了它们之间的不同,才能在合适的场合使用. 简单来说,new只是分配内存,不初始化内存: 而make即分配又初始化内 ...