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并发编程实战>和一些优秀的博客,当然 ...
随机推荐
- java常见字符集
ASCII 名称由来 ASCII(American Standard Code for Information Interchange,美国信息互换标准编码)是基于罗马字母表的一套电脑编码系统.[1] ...
- 团队作业4——第一次项目冲刺(Alpha版本)2017.11.14
1.当天站立式会议照片 本次会议在5号公寓1楼召开,本次会议内容:①:熟悉每个人想做的模块.②:根据老师的要求将项目划分成一系列小任务.③:选择项目的开发模式:jsp+servlet+javabean ...
- Activity设置背景透明之开发坑
Activity设置背景透明的常规方法 方法一.在Manifest.xml中,直接在需要设置的Activity中添加主题样式: Android:theme="@android:style/T ...
- testng对执行失败的用例,再次执行
前段时间在网络上看到通过重写TestNG的接口,可以再次执行失败的测试用例,于是学习了,我之前的做法是当自己的脚本中碰到异常,就自动调用方法本身来达到再次执行用例的目的,这个过程中有设定重试的次数 对 ...
- vue-cli配置axios
1. npm install axios --save 2. npm install @type/axios --save-dev(使用ts编写的需要此声明文件,升级的axios好像不需要了,已经自带 ...
- poj 1185(状态压缩DP)
poj 1185(状态压缩DP) 题意:在一个N*M的矩阵中,‘H'表示不能放大炮,’P'表示可以放大炮,大炮能攻击到沿横向左右各两格,沿纵向上下各两格,现在要放尽可能多的大炮使得,大炮之间不能相互 ...
- lucene介绍
1.https://blog.csdn.net/shuaicihai/article/details/65111523 2.https://www.cnblogs.com/rodge-run/p/65 ...
- php 关于文件的一些封装好的函数
<?php //Bytes/Kb/MB/GB/TB/EB /** * 转换字节大小 * @param number $size * @return number */ function tran ...
- C#:文件/注册表/线程的操作
文件的操作:(file与fileinfo,前者是静态方法,执行安全检查,适合对一个的操作) 1.1.create: using System; using System.Collections.Gen ...
- hdu 5638 Toposort (拓扑排序+线段树)
Toposort Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 131072/131072 K (Java/Others)Total ...