Java多线程学习(三)volatile关键字
系列文章传送门:
Java多线程学习(一)Java多线程入门
Java多线程学习(二)synchronized关键字(1)
java多线程学习(二)synchronized关键字(2)
Java多线程学习(三)volatile关键字
Java多线程学习(四)等待/通知(wait/notify)机制
Java多线程学习(五)线程间通信知识点补充
Java多线程学习(六)Lock锁的使用
Java多线程学习(七)并发编程中一些问题
系列文章将被优先更新于微信公众号<font color="red">“Java面试通关手册”</font>,欢迎广大Java程序员和爱好技术的人员关注。
本节思维导图:
思维导图源文件+思维导图软件关注微信公众号:“Java面试通关手册”回复关键字:“Java多线程”免费领取。
一 简介
先来看看维基百科对“volatile关键字”的定义:
在程序设计中,尤其是在C语言、C++、C#和Java语言中,使用volatile关键字声明的变量或对象通常具有与优化、多线程相关的特殊属性。通常,volatile关键字用来阻止(伪)编译器认为的无法“被代码本身”改变的代码(变量/对象)进行优化。如在C语言中,<font color="red">volatile关键字可以用来提醒编译器它后面所定义的变量随时有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。据</font>
在C环境中,volatile关键字的真实定义和适用范围经常被误解。虽然C++、C#和Java都保留了C中的volatile关键字,但在这些编程语言中volatile的用法和语义却大相径庭。
Java中的“volatile关键字”关键字:
在 JDK1.2 之前,Java的内存模型实现总是从<font color="red">主存(即共享内存)读取变量</font>,是不需要进行特别的注意的。而在当前的 Java 内存模型下,线程可以把变量保存<font color="red">本地内存</font>(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成<font color="red">数据的不一致</font>。
要解决这个问题,就需要把变量声明为<font color="red"> volatile</font>,这就指示 JVM,这个变量是不稳定的,每次使用它都到主存中进行读取。
二 volatile关键字的可见性
volatile 修饰的成员变量在每次被线程访问时,都强迫从主存(共享内存)中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到主存(共享内存)。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值,这样也就保证了同步数据的可见性。
<font size="2">RunThread.java</font>
private boolean isRunning = true;int m;public boolean isRunning() {return isRunning;}public void setRunning(boolean isRunning) {this.isRunning = isRunning;}@Overridepublic void run() {System.out.println("进入run了");while (isRunning == true) {int a=2;int b=3;int c=a+b;m=c;}System.out.println(m);System.out.println("线程被停止了!");}
}
<font size="2">Run.java</font>
public class Run {public static void main(String[] args) throws InterruptedException {RunThread thread = new RunThread();thread.start();Thread.sleep(1000);thread.setRunning(false);System.out.println("已经赋值为false");}
}
<font size="2">运行结果:</font>
RunThread类中的isRunning变量没有加上<font color="red">volatile关键字</font>时,运行以上代码会出现<font color="red">死循环</font>,这是因为isRunning变量虽然被修改但是没有被写到<font color="red">主存</font>中,这也就导致该线程在<font color="red">本地内存</font>中的值一直为true,这样就导致了死循环的产生。
解决办法也很简单:isRunning变量前加上<font color="red">volatile关键字</font>即可。
这样运行就不会出现死循环了。
<font size="2">加上volatile关键字后的运行结果:</font>
<font color="red">你是不是以为到这就完了?</font>
不存在的!!!(这里还有一点需要强调,下面的内容一定要看,不然你在用volatile关键字时会很迷糊,因为书籍几乎都没有提这个问题)
假如你把while循环代码里加上任意一个输出语句或者sleep方法你会发现死循环也会停止,不管isRunning变量是否被加上了上volatile关键字。
<font size="2">加上输出语句:</font>
while (isRunning == true) {int a=2;int b=3;int c=a+b;m=c;System.out.println(m);}
<font size="2">加上sleep方法:</font>
while (isRunning == true) {int a=2;int b=3;int c=a+b;m=c;try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
<font color="red">这是为什么呢?</font>
<font color="red">因为:JVM会尽力保证内存的可见性,即便这个变量没有加同步关键字</font>。换句话说,只要CPU有时间,JVM会尽力去保证变量值的更新。这种与volatile关键字的不同在于,volatile关键字会强制的保证线程的可见性。而不加这个关键字,JVM也会尽力去保证可见性,但是如果CPU一直有其他的事情在处理,它也没办法。最开始的代码,一直处于死循环中,CPU处于一直占用的状态,这个时候CPU没有时间,JVM也不能强制要求CPU分点时间去取最新的变量值。而<font color="red">加了输出或者sleep语句之后,CPU就有可能有时间去保证内存的可见性,于是while循环可以被终止</font>。
三 volatile关键字能保证原子性吗?
《Java并发编程艺术》这本书上说保证但是在自增操作(非原子操作)上不保证,《Java多线程编程核心艺术》这本书说不保证。
我个人更倾向于这种说法:<font color="red">volatile无法保证对变量原子性的</font>。我个人感觉《Java并发编程艺术》这本书上说volatile关键字保证原子性吗但是在自增操作(非原子操作)上不保证这种说法是有问题的。只是个人看法,希望不要被喷。可以看下面测试代码:
<font size="2">MyThread.java</font>
public class MyThread extends Thread {volatile public static int count;private static void addCount() {for (int i = 0; i < 100; i++) {count=i;}System.out.println("count=" + count);}@Overridepublic void run() {addCount();}
}
<font size="2">Run.java</font>
public class Run {public static void main(String[] args) {MyThread[] mythreadArray = new MyThread[100];for (int i = 0; i < 100; i++) {mythreadArray[i] = new MyThread();}for (int i = 0; i < 100; i++) {mythreadArray[i].start();}}}
<font size="2">运行结果:</font>
上面的“count=i;”是一个原子操作,但是运行结果大部分都是正确结果99,但是也有部分不是99的结果。
<font color="red">解决办法:</font>
<font color="red">使用synchronized关键字加锁</font>。(这只是一种方法,Lock和AtomicInteger原子类都可以,因为之前学过synchronized关键字,所以我们使用synchronized关键字的方法)
修改MyThread.java如下:
public class MyThread extends Thread {public static int count;synchronized private static void addCount() {for (int i = 0; i < 100; i++) {count=i;}System.out.println("count=" + count);}@Overridepublic void run() {addCount();}
}
这样运行输出的count就都为99了,所以<font color="red">要保证数据的原子性还是要使用synchronized关键字</font>。
四 synchronized关键字和volatile关键字比较
- volatile关键字是线程同步的轻量级实现,所以volatile性能肯定比synchronized关键字要好。但是volatile关键字只能用于变量而synchronized关键字可以修饰方法以及代码块。synchronized关键字在JavaSE1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁以及其它各种优化之后执行效率有了显著提升,实际开发中使用synchronized关键字还是更多一些。
- 多线程访问volatile关键字不会发生阻塞,而synchronized关键字可能会发生阻塞
- volatile关键字能保证数据的可见性,但不能保证数据的原子性。synchronized关键字两者都能保证。
- volatile关键字用于解决变量在多个线程之间的可见性,而ynchronized关键字解决的是多个线程之间访问资源的同步性。
参考:
《Java多线程编程核心技术》
《Java并发编程的艺术》
极客学院Java并发编程wiki: http://wiki.jikexueyuan.com/p...
如果你觉得博主的文章不错,欢迎转发点赞。你能从中学到知识就是我最大的幸运。
欢迎关注我的微信公众号:“Java面试通关手册”(分享各种Java学习资源,面试题,以及企业级Java实战项目回复关键字免费领取)。另外我创建了一个Java学习交流群(群号:174594747),欢迎大家加入一起学习,这里更有面试,学习视频等资源的分享。
Java多线程学习(三)volatile关键字相关推荐
- Java多线程学习三十七:volatile 的作用是什么?与 synchronized 有什么异同
volatile 是什么 首先我们就来介绍一下 volatile,它是 Java 中的一个关键字,是一种同步机制.当某个变量是共享变量,且这个变量是被 volatile 修饰的,那么在修改了这个变量的 ...
- Java多线程学习三十六:主内存和工作内存的关系
CPU 有多级缓存,导致读的数据过期 由于 CPU 的处理速度很快,相比之下,内存的速度就显得很慢,所以为了提高 CPU 的整体运行效率,减少空闲时间,在 CPU 和内存之间会有 cache 层,也就 ...
- Java多线程学习三十八:你知道什么是 CAS 吗
CAS 简介 CAS 其实是我们面试中的常客,因为它是原子类的底层原理,同时也是乐观锁的原理,所以当你去面试的时候,经常会遇到这样的问题"你知道哪些类型的锁"?你可能会回答&quo ...
- Java多线程学习三十一:ThreadLocal 是用来解决共享资源的多线程访问的问题吗?
ThreadLocal 是不是用来解决共享资源的多线程访问的. 这是一个常见的面试问题,如果被问到了 ThreadLocal,则有可能在你介绍完它的作用.注意点等内容之后,再问你:ThreadLoca ...
- Java多线程学习三十:ThreadLocal 适合用在哪些实际生产的场景中
我们在学习一个工具之前,首先应该知道这个工具的作用,能带来哪些好处,而不是一上来就闷头进入工具的 API.用法等,否则就算我们把某个工具的用法学会了,也不知道应该在什么场景下使用.所以,我们先来看看究 ...
- Java多线程学习三十九:CAS 有什么缺点?
CAS 有哪几个主要的缺点. 首先,CAS 最大的缺点就是 ABA 问题. 决定 CAS 是否进行 swap 的判断标准是"当前的值和预期的值是否一致",如果一致,就认为在此期间这 ...
- Java多线程学习三十三:Future 的主要功能是什么?
Future 类 Future 的作用 Future 最主要的作用是,比如当做一定运算的时候,运算过程可能比较耗时,有时会去查数据库,或是繁重的计算,比如压缩.加密等,在这种情况下,如果我们一直在原地 ...
- Java多线程学习三:有哪几种实现生产者消费者模式的方法
我们先来看看什么是生产者消费者模式,生产者消费者模式是程序设计中非常常见的一种设计模式,被广泛运用在解耦.消息队列等场景.在现实世界中,我们把生产商品的一方称为生产者,把消费商品的一方称为消费者,有时 ...
- java多线程学习三
本章主要学习线程的静态方法 1.先忙先看一段代码: public class MyThread3 implements Runnable {static {System.out.println(&qu ...
- Java多线程学习三十五: CyclicBarrier 和 CountDownLatch 有什么不同
CyclicBarrier 和 CountDownLatch 有什么不同? CyclicBarrier作用 CyclicBarrier 和 CountDownLatch 确实有一定的相似性,它们都能阻 ...
最新文章
- 【python教程入门学习】PyCharm 如何使用
- 在你做回归测试的时候,突然有个着急的测试需求,你会怎么做?
- swift Array 数组
- gRPC——简介与Hello World
- 【XAudio2】4.库版本
- 【LOJ】#2184. 「SDOI2015」星际战争
- WildFly和Docker上的Java EE 7动手实验室
- 解决:Docker 启动的容器内部时间比服务器时间晚 8 小时,容器内部时间与宿主机时间不一致
- nQueen问题java实现
- oracle kill所有plsql developer进程
- Spring数据库事务典型错误用法剖析
- centos7 快速安装 mariadb(mysql)
- python获取网页数据判断并提交_python3爬虫无法通过网页内容判断存在与否?
- 代码读智识 笔墨知人心 1
- Ubuntu14.04下C++程序编辑、编译、运行入门篇
- java/php/net/python养花助手平台设计
- 拍卖系统业务演进过程(一)
- 掌门1对1《啊呜!卡通人》引强烈共鸣 智能科技赋能高效教学收获点赞
- 程序员 防脱发 秘方 脂溢性脱发 外用中药方3则
- 笔记本电脑win10系统,麦克风突然没声音了