转载请备注地址:https://blog.csdn.net/qq_34337272/article/details/79680693

系列文章传送门:

Java多线程学习(一)Java多线程入门

Java多线程学习(二)synchronized关键字(1)

Java多线程学习(二)synchronized关键字(2)

Java多线程学习(三)volatile关键字

Java多线程学习(四)等待/通知(wait/notify)机制

系列文章将被优先更新于微信公众号“Java面试通关手册”,欢迎广大Java程序员和爱好技术的人员关注。

本节思维导图:

思维导图源文件+思维导图软件关注微信公众号:“Java面试通关手册”回复关键字:“Java多线程”免费领取。

一 简介

先来看看维基百科对“volatile关键字”的定义:

在程序设计中,尤其是在C语言、C++、C#和Java语言中,使用volatile关键字声明的变量或对象通常具有与优化、多线程相关的特殊属性。通常,volatile关键字用来阻止(伪)编译器认为的无法“被代码本身”改变的代码(变量/对象)进行优化。如在C语言中,volatile关键字可以用来提醒编译器它后面所定义的变量随时有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。据

在C环境中,volatile关键字的真实定义和适用范围经常被误解。虽然C++、C#和Java都保留了C中的volatile关键字,但在这些编程语言中volatile的用法和语义却大相径庭。

Java中的“volatile关键字”关键字:

在 JDK1.2 之前,Java的内存模型实现总是从主存(即共享内存)读取变量,是不需要进行特别的注意的。而在当前的 Java 内存模型下,线程可以把变量保存本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数据的不一致



要解决这个问题,就需要把变量声明为 volatile,这就指示 JVM,这个变量是不稳定的,每次使用它都到主存中进行读取。

二 volatile关键字的可见性

volatile 修饰的成员变量在每次被线程访问时,都强迫从主存(共享内存)中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到主存(共享内存)。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值,这样也就保证了同步数据的可见性

RunThread.java

 private boolean isRunning = true;
int m;
public boolean isRunning() {
return isRunning;
}
public void setRunning(boolean isRunning) {
this.isRunning = isRunning;
}
@Override
public 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("线程被停止了!");
}
}

Run.java

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");
}
}

运行结果:

RunThread类中的isRunning变量没有加上volatile关键字时,运行以上代码会出现死循环,这是因为isRunning变量虽然被修改但是没有被写到主存中,这也就导致该线程在本地内存中的值一直为true,这样就导致了死循环的产生。

解决办法也很简单:isRunning变量前加上volatile关键字即可。



这样运行就不会出现死循环了。

加上volatile关键字后的运行结果:

你是不是以为到这就完了?

不存在的!!!(这里还有一点需要强调,下面的内容一定要看,不然你在用volatile关键字时会很迷糊,因为书籍几乎都没有提这个问题)

假如你把while循环代码里加上任意一个输出语句或者sleep方法你会发现死循环也会停止,不管isRunning变量是否被加上了上volatile关键字。

加上输出语句:

    while (isRunning == true) {
int a=2;
int b=3;
int c=a+b;
m=c;
System.out.println(m);
}

加上sleep方法:

        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 block
e.printStackTrace();
}
}

这是为什么呢?

因为:JVM会尽力保证内存的可见性,即便这个变量没有加同步关键字。换句话说,只要CPU有时间,JVM会尽力去保证变量值的更新。这种与volatile关键字的不同在于,volatile关键字会强制的保证线程的可见性。而不加这个关键字,JVM也会尽力去保证可见性,但是如果CPU一直有其他的事情在处理,它也没办法。最开始的代码,一直处于死循环中,CPU处于一直占用的状态,这个时候CPU没有时间,JVM也不能强制要求CPU分点时间去取最新的变量值。而加了输出或者sleep语句之后,CPU就有可能有时间去保证内存的可见性,于是while循环可以被终止。

三 volatile关键字能保证原子性吗?

《Java并发编程艺术》这本书上说保证但是在自增操作(非原子操作)上不保证,《Java多线程编程核心艺术》这本书说不保证。

我个人更倾向于这种说法:volatile无法保证对变量原子性的。我个人感觉《Java并发编程艺术》这本书上说volatile关键字保证原子性吗但是在自增操作(非原子操作)上不保证这种说法是有问题的。只是个人看法,希望不要被喷。可以看下面测试代码:

MyThread.java

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); }
@Override
public void run() {
addCount();
}
}

Run.java

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();
}
} }

运行结果:

上面的“count=i;”是一个原子操作,但是运行结果大部分都是正确结果99,但是也有部分不是99的结果。



解决办法

使用synchronized关键字加锁。(这只是一种方法,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);
}
@Override
public void run() {
addCount();
}
}

这样运行输出的count就都为99了,所以要保证数据的原子性还是要使用synchronized关键字。

四 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/project/java-concurrency/volatile1.html

如果你觉得博主的文章不错,欢迎转发点赞。你能从中学到知识就是我最大的幸运。

欢迎关注我的微信公众号:“Java面试通关手册”(分享各种Java学习资源,面试题,以及企业级Java实战项目回复关键字免费领取)。另外我创建了一个Java学习交流群(群号:174594747),欢迎大家加入一起学习,这里更有面试,学习视频等资源的分享。

Java多线程学习(三)volatile关键字的更多相关文章

  1. JAVA多线程学习- 三:volatile关键字

    Java的volatile关键字在JDK源码中经常出现,但是对它的认识只是停留在共享变量上,今天来谈谈volatile关键字. volatile,从字面上说是易变的.不稳定的,事实上,也确实如此,这个 ...

  2. java多线程4:volatile关键字

    上文说到了 synchronized,那么就不得不说下 volatile关键字了,它们两者经常协同处理多线程的安全问题. volatile保证可见性 那么volatile的作用是什么呢? 在jvm运行 ...

  3. java线程学习之volatile关键字

    volatile变量的主要作用:是使变量在多个线程间可见. 在java中每一个线程都会有一块工作内存区,其中存放着所有线程共享的主内存的变量值的拷贝.当线程执行时,它在自己的工作内存区操作这些变量,为 ...

  4. java 多线程11:volatile关键字

    直接先举一个例子普通的线程实例变量的非可见性: public class MyThread28 extends Thread { private boolean isRunning = true; p ...

  5. java多线程-慎重使用volatile关键字

    Java语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量.这两种机制的提出都是为了实现代码线程的安全性.其中 Volatile 变量的同步性较差(但有时它更简单并且开销更低),而 ...

  6. Java多线程学习(三)---线程的生命周期

    线程生命周期 摘要: 当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态.在线程的生命周期中,它要经过新建(New).就绪(Runnable).运行(Running).阻塞 ...

  7. java多线程学习三

    本章主要学习线程的静态方法 1.先忙先看一段代码: public class MyThread3 implements Runnable { static { System.out.println(& ...

  8. Java多线程学习(二)synchronized关键字(2)

    转载请备注地址:https://blog.csdn.net/qq_34337272/article/details/79670775 系列文章传送门: Java多线程学习(一)Java多线程入门 Ja ...

  9. Java多线程学习(二)synchronized关键字(1)

    转载请备注地址: https://blog.csdn.net/qq_34337272/article/details/79655194 Java多线程学习(二)将分为两篇文章介绍synchronize ...

随机推荐

  1. AppScan工作原理&操作教程

    一.AppScan的工作原理 对一个综合性的大型网站来说,可能存在成千上万的页面.以登录界面为例,至少要输入用户名和密码,即该页面存在两个字段,当提交了用户名和密码等登录信息,网站需要检查是否正确,这 ...

  2. Redis 学习之集群

    该文使用centos6.5 64位  redis3.2.8 一.  redis-cluster架构图 集群通信:所有redis节点之间通过PING-PONG机制彼此互联,内部使用二进制鞋子优化传输速度 ...

  3. arp hook

    最近疯狂的研究Linux的种种功能,也颇有心得,这里讲述一下Linux下的Net的Hook,使用net的Hook可以实现很多很多非常底层的功能,比如过滤报文,做防火墙,做代理等等. Now,Let's ...

  4. 从一个简单的main方法执行谈谈JVM工作机制

    本来JVM的工作原理浅到可以泛泛而谈,但如果真的想把JVM工作机制弄清楚,实在是很难,涉及到的知识领域太多.所以,本文通过简单的mian方法执行,浅谈JVM工作原理,看看JVM里面都发生了什么. 先上 ...

  5. winform中文本框添加拖拽功能

    对一个文本框添加拖拽功能: private void txtFolder_DragEnter(object sender, DragEventArgs e) { if (e.Data.GetDataP ...

  6. CCS3 动画-鼠标放上去放大背景图片

    ---〉 效果如上,一个简单的过渡放大效果, <!DOCTYPE HTML> <html> <body> <style> #test{ width:30 ...

  7. 域对象 pageContext request session servletContext

    pageContext 当前页面之内有效 request   当前的请求内有效 session 当前的会话内有效 servletContext 当前这次服务器生命周期内有效

  8. 未找到与约束ContractName Microsoft.VisualStudio.Text.ITextDocumentFactoryService...匹配的导出

    安装微软的windows补丁 KB2781514 ,补丁主要解决“在 .NET Framework 4.5 更新之后,Visual Studio 用户可能无法打开或创建 C++ 或 JavaScrip ...

  9. A表数据插入到B表(表结构不一致)

    D_A  有E\F\H 3字段 D_B 有 A\B\C\D\E\ID 字段 将 D_B 个别字段插入到D_A  表 INSERT INTO  D_A(E,F,H) select B,A,ID from ...

  10. [GDOI2014]拯救莫莉斯 状压DP

    题面: 莫莉斯·乔是圣域里一个叱咤风云的人物,他凭借着自身超强的经济头脑,牢牢控制了圣域的石油市场. 圣域的地图可以看成是一个n*m的矩阵.每个整数坐标点(x , y)表示一座城市( 1\le x\l ...