Java并发编程原理与实战十二:深入理解volatile原理与使用
volatile:称之为轻量级锁,被volatile修饰的变量,在线程之间是可见的。
可见:一个线程修改了这个变量的值,在另一个线程中能够读取到这个修改后的值。
synchronized除了线程之间互斥之外,还有一个非常大的作用,就是保证可见性。以下demo即保证a值的可见性。
首先来看demo:

package com.roocon.thread.t7;
public class Demo {
private int a = 1;
public int getA() {
return a;
}
public void setA(int a) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.a = a;
}
public static void main(String[] args) {
Demo demo = new Demo();
new Thread(new Runnable() {
@Override
public void run() {
demo.setA(10);
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(demo.getA());
}
}).start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("最终结果:" + demo.getA());
}
}

运行结果:
1
最终结果:10
解释:线程1执行set操作,但是,线程2可能在线程1执行set操作成功之前就进行了get操作,这样,get得到的仍然是修改之前的a值
那么,如何保证线程1在执行set操作时,其他线程得到的a值是线程1修改后的值呢?采用同步锁可以实现效果。同步方法上锁的是同一个实例,因此,在执行set方法前,线程1获取了实例锁,那么,其他线程在执行get方法时,必须获得同一把实例锁才可以得到a的值,所以,必须等待线程1释放实例锁后,其他线程才可以继续执行同步的get方法。这样,就可以保证其他线程得到的a值一定是线程1修改后的值。

package com.roocon.thread.t7;
public class Demo {
private int a = 1;
public synchronized int getA() {
return a;
}
public synchronized void setA(int a) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.a = a;
}
public static void main(String[] args) {
Demo demo = new Demo();
new Thread(new Runnable() {
@Override
public void run() {
demo.setA(10);
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(demo.getA());
}
}).start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("最终结果:" + demo.getA());
}
}

运行结果:
10
最终结果:10
当然,要清楚的一点是,这里的输出统一仅仅是基于线程1的set操作是先于线程2的get操作执行的。但是,要知道,两个线程并发执行,不一定是set操作一定优先get操作执行的。
同样的,volatile也可以保证可见性。因为synchronized是重量级锁,所以,使用volatile会更好。

package com.roocon.thread.t7;
public class Demo {
private volatile int a = 1;
public int getA() {
return a;
}
public void setA(int a) {
this.a = a;
}
public static void main(String[] args) {
Demo demo = new Demo();
new Thread(new Runnable() {
@Override
public void run() {
demo.setA(10);
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(demo.getA());
}
}).start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("最终结果:" + demo.getA());
}
}

运行结果:
10
最终结果:10
再来看个demo理解volatile的可见性:

package com.roocon.thread.t7;
public class Demo2 {
public volatile boolean run = false;
public static void main(String[] args) {
Demo2 demo2 = new Demo2();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i< 5; i++) {
System.out.println("执行了第" + i +"次");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
demo2.run = true;
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while(!demo2.run){
}
System.out.println("线程2执行了");
}
}).start();
}
}

运行结果:
执行了第1次
执行了第2次
执行了第3次
执行了第4次
线程2执行了
这里需要注意的是,volatile只能保证线程的可见性,但是并不能保证原子性操作。如果volatile修饰的变量涉及到非原子操作,那么,我们需要使用synchronized来保证它的安全性。

package com.roocon.thread.t7;
public class Demo {
private volatile int a = 1;
public synchronized int getA() {
return a++;
}
public synchronized void setA(int a) {
this.a = a;
}
public static void main(String[] args) {
Demo demo = new Demo();
new Thread(new Runnable() {
@Override
public void run() {
demo.setA(10);
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(demo.getA());
}
}).start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("最终结果:" + demo.getA());
}
}

运行结果:
10
最终结果:11
volatile:
在多处理器的系统上,它会执行一下步骤:
1.将当前处理器缓存行的内容写回到系统内存
2.这个写回到内存的操作会使得在其他CPU里缓存了该内存地址的数据失效
3.其他CPU缓存数据失效,则会重新去内存中读取值,也就是被修改的数据
这里要理解的一个概念是缓存行,缓存行是CPU缓存的一个基本单位。
硬盘---内存--CPU缓存,内存的读取速度比硬盘快,CPU缓存的读取速度比内存更高效。
volatile和synchronized的比较:
synchronized是完全可以替换volatile的,只是volatile相对synchronized是轻量级锁。
volatile是不可以完全替换synchronized的,因为volatile只能保证可见性,并不能保证操作的原子性。所以,很多情况下,两者会相互结合使用。
参考资料:
《java并发编程与实战》龙果学院
Java并发编程原理与实战十二:深入理解volatile原理与使用的更多相关文章
- Java并发编程中的设计模式解析(二)一个单例的七种写法
Java单例模式是最常见的设计模式之一,广泛应用于各种框架.中间件和应用开发中.单例模式实现起来比较简单,基本是每个Java工程师都能信手拈来的,本文将结合多线程.类的加载等知识,系统地介绍一下单例模 ...
- 【java并发编程艺术学习】(二)第一章 java并发编程的挑战
章节介绍 主要介绍并发编程时间中可能遇到的问题,以及如何解决. 主要问题 1.上下文切换问题 时间片是cpu分配给每个线程的时间,时间片非常短. cpu通过时间片分配算法来循环执行任务,当前任务执行一 ...
- Java并发编程笔记之基础总结(二)
一.线程中断 Java 中线程中断是一种线程间协作模式,通过设置线程的中断标志并不能直接终止该线程的执行,而是需要被中断的线程根据中断状态自行处理. 1.void interrupt() 方法:中断线 ...
- 读《Java并发编程的艺术》(二)
上篇博客开始,我们接触了一些有关Java多线程的基本概念.这篇博客开始,我们就正式的进入了Java多线程的实战演练了.实战演练不仅仅是贴代码,也会涉及到相关概念和术语的讲解. 线程的状态 程的状态分为 ...
- Java并发编程的艺术 记录(二)
volatile的应用 volatile的定义如下:Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致地更新,线程应该确保通过排他锁单独获得这个变量.Java语言提供了volatil ...
- Java并发编程的艺术笔记(二)——wait/notify机制
一.概述 一个线程修改了一个对象的值,另一个线程感知到变化从而做出相应的操作.前者是生产者,后者是消费者. 等待/通知机制,是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B调用 ...
- Java并发编程与技术内幕:线程池深入理解
摘要: 本文主要讲了Java当中的线程池的使用方法.注意事项及其实现源码实现原理,并辅以实例加以说明,对加深Java线程池的理解有很大的帮助. 首先,讲讲什么是线程池?照笔者的简单理解,其实就是一组线 ...
- 转:Java并发编程与技术内幕:线程池深入理解
版权声明:本文为博主林炳文Evankaka原创文章,转载请注明出处http://blog.csdn.net/evankaka 目录(?)[+] ); } catch (InterruptedExcep ...
- Java并发编程面试题 Top 50 整理版
本文在 Java线程面试题 Top 50的基础上,对部分答案进行进行了整理和补充,问题答案主要来自<Java编程思想(第四版)>,<Java并发编程实战>和一些优秀的博客,当然 ...
随机推荐
- 旧文备份:CANopen协议中SDO服务
SDO是服务数据对象接口(Service Data Obiect)的缩写,顾名思义提供服务数据的访问接口,服务数据就是一些实时性要求不高的数据,一般是指节点配置参数,因此,SDO一般用来配置和获得节点 ...
- python基础(二)条件判断、循环、格式化输出
继续上一篇,今天主要总结一下条件判断.循环.格式化输出 一.条件判断 python中条件判断使用if else来判断,多分支的话使用if elif ... else,也就是如果怎么怎么样就怎么怎么样, ...
- scrapy 直接在编辑器运行
# *_*coding:utf-8 *_* from scrapy.cmdline import execute execute("scrapy crawl spbeen --nolog&q ...
- Json的JsonValueProcessor方法
将对象转换成字符串,是非常常用的功能,尤其在WEB应用中,使用 JSON lib 能够便捷地完成这项工作. JSON lib能够将Java对象转成json格式的字符串,也可以将Java对象转换成xml ...
- JS 随机整数
<script> function GetRandomNum(Min,Max){ var Range = Max - Min; var Rand = Math.random() ...
- swusec的构想,顺便送开学福利——校园网一号多登录演示
前言: 我不是什么大牛,我只想通过我的努力,打造swu网络安全爱好者的圈子.期待你加入. swusec是什么? swusec (SouthWestUniversity SecurityTeam),西南 ...
- DAY1-Flask项目
1.pipenv:与virtualenv类似的第三方的Python运行虚拟环境 给每个项目安装pipenv环境:pipenv install 启动:pipenv shell 使用pipenv安装Fla ...
- BZOJ5006 THUWC2017随机二分图(概率期望+状压dp)
下称0类为单边,1类为互生边,2类为互斥边.对于一种匹配方案,考虑其出现的概率*2n后对答案的贡献,初始为1,如果有互斥边显然变为0,否则每有一对互生边其贡献*2.于是有一个显然的dp,即设f[S1] ...
- 【刷题】洛谷 P1402 酒店之王
题目描述 XX酒店的老板想成为酒店之王,本着这种希望,第一步要将酒店变得人性化.由于很多来住店的旅客有自己喜好的房间色调.阳光等,也有自己所爱的菜,但是该酒店只有p间房间,一天只有固定的q道不同的菜. ...
- 【刷题】BZOJ 5418 [Noi2018]屠龙勇士
www.lydsy.com/JudgeOnline/upload/noi2018day2.pdf Solution 将攻击的式子列出来,\(atk \times x-p \times y=a_i\) ...