私信我或关注公众号猿来如此呀,回复:学习,获取免费学习资源包

volatile关键字

关键字volatile的主要作用是使变量在多个线程间可见。

1 关键字volatile与死循环

如果不是在多继承的情况下,使用继承Thread类和实现Runnable接口在取得程序运行的结果上并没有多大的区别。如果一旦出现”多继承“的情况,则用实现Runable接口的方式来处理多线程的问题就是很有必要的。

public class PrintString implements Runnable{ private boolean isContinuePrint = true; @Override public void run() { while (isContinuePrint){ System.out.println("Thread: "+Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } public boolean isContinuePrint() { return isContinuePrint; } public void setContinuePrint(boolean continuePrint) { isContinuePrint = continuePrint; } public static void main(String[] args) throws InterruptedException { PrintString printString = new PrintString(); Thread thread = new Thread(printString,"Thread-A"); thread.start(); Thread.sleep(100); System.out.println("我要停止它!" + Thread.currentThread().getName()); printString.setContinuePrint(false); }}

运行结果:

Thread: Thread-A我要停止它!main

上面的代码运行起来没毛病,但是一旦运行在 -server服务器模式中64bit的JVM上时,会出现死循环。解决的办法是使用volatile关键字。

关键字volatile的作用是强制从公共堆栈中取得变量的值,而不是从线程私有数据栈中取得变量的值。

2 解决异步死循环

在研究volatile关键字之前先来做一个测试用例,代码如下:

public class PrintString implements Runnable{ private boolean isRunnning = true; @Override public void run() { System.out.println("Thread begin: "+Thread.currentThread().getName()); while (isRunnning == true){ } System.out.println("Thread end: "+Thread.currentThread().getName()); } public boolean isRunnning() { return isRunnning; } public void setRunnning(boolean runnning) { isRunnning = runnning; } public static void main(String[] args) throws InterruptedException { PrintString printString = new PrintString(); Thread thread = new Thread(printString,"Thread-A"); thread.start(); Thread.sleep(1000); printString.setRunnning(false); System.out.println("我要停止它!" + Thread.currentThread().getName()); }}

JVM有Client和Server两种模式,我们可以通过运行:java -version来查看jvm默认工作在什么模式。我们在IDE中把JVM设置为在Server服务器的环境中,具体操作只需配置运行参数为 -server。然后启动程序,打印结果:

Thread begin: Thread-A我要停止它!main

代码 System.out.println("Thread end: "+Thread.currentThread().getName());从未被执行。

是什么样的原因造成将JVM设置为-server就出现死循环呢?

在启动thread线程时,变量boolean isContinuePrint = true;存在于公共堆栈及线程的私有堆栈中。在JVM设置为-server模式时为了线程运行的效率,线程一直在私有堆栈中取得isRunning的值是true。而代码thread.setRunning(false);虽然被执行,更新的却是公共堆栈中的isRunning变量值false,所以一直就是死循环的状态。内存结构图:

这个问题其实就是私有堆栈中的值和公共堆栈中的值不同步造成的。解决这样的问题就要使用volatile关键字了,它主要的作用就是当线程访问isRunning这个变量时,强制性从公共堆栈中进行取值。

将代码更改如下:

volatile private boolean isRunnning = true;

再次运行:

Thread begin: Thread-A我要停止它!mainThread end: Thread-A

通过使用volatile关键字,强制的从公共内存中读取变量的值,内存结构如图所示:

使用volatile关键字增加了实例变量在多个线程之间的可见性。但volatile关键字最致命的缺点是不支持原子性。

下面将关键字synchronized和volatile进行一下比较:

  1. 关键字volatile是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好,并且volatile只能修饰于变量,而synchronized可以修饰方法,以及代码块。随着JDK新版本的发布,synchronized关键字在执行效率上得到很大提升,在开发中使用synchronized关键字的比率还是比较大的。
  2. 多线程访问volatile不会发生阻塞,而synchronized会出现阻塞。
  3. volatile能保证数据的可见性,但不能保证原子性;而synchronized可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公共内存中的数据做同步。
  4. 再次重申一下,关键字volatile解决的是变量在多个线程之间的可见性;而synchronized关键字解决的是多个线程之间访问资源的同步性。

线程安全包含原子性和可见性两个方面,Java的同步机制都是围绕这两个方面来确保线程安全的。

3 volatile非原子性的特征

关键字虽然增加了实例变量在多个线程之间的可见性,但它却不具备同步性,那么也就不具备原子性。

示例代码:

public class MyThread extends Thread { volatile private static int count; @Override public void run() { addCount(); } private void addCount() { for (int i = 0;i<100;i++){ count++; } System.out.println(count); } public static void main(String[] args) { MyThread[] myThreads = new MyThread[100]; for (int i=0;i<100;i++){ myThreads[i] = new MyThread(); } for (int i=0;i<100;i++){ myThreads[i].start(); } }}

运行结果:

...825383538153805378757675

在addCount方法上加入synchronized同步关键字与static关键字,达到同步的效果。

再次运行结果:

....960097009800990010000

关键字volatile提示线程每次从共享内存中读取变量,而不是从私有内存中读取,这样就保证了同步数据的可见性。但在这里需要注意的是:如果修改实例变量中的数据,比如i++,也就是比

i=i+1,则这样的操作其实并不是一个原子操作,也就是非线程安全。表达式i++的操作步骤分解为下面三步:

  1. 从内存中取i的值;
  2. 计算i的值;
  3. 将i值写入到内存中。

假如在第二步计算i值的时候,另外一个线程也修改i的值,那么这个时候就会脏数据。解决的方法其实就是使用synchronized关键字。所以说volatile关键字本身并不处理数据的原子性,而是强制对数据的读写及时影响到主内存中。

4 使用原子类进行i++操作

除了在i++操作时使用synchronized关键字实现同步外,还可以使用AtomicInteger原子类进行实现。

原子操作是不可分割的整体,没有其他线程能够中断或检查正在原子操作中的变量。它可以在没有锁的情况下做到线程安全。

示例代码:

public class MyThread extends Thread { private static AtomicInteger count = new AtomicInteger(0); @Override public void run() { addCount(); } private static void addCount() { for (int i = 0;i<100;i++){ System.out.println(count.incrementAndGet()); } } public static void main(String[] args) { MyThread[] myThreads = new MyThread[100]; for (int i=0;i<100;i++){ myThreads[i] = new MyThread(); } for (int i=0;i<100;i++){ myThreads[i].start(); } }}

打印结果:

....999699979998999910000

成功达到累加的效果。

5 原子类也不安全

原子类在具有有逻辑性的情况下输出结果也具有随机性。

示例代码:

public class MyThread extends Thread { private static AtomicInteger count = new AtomicInteger(0); public MyThread(String name) { super(name); } @Override public void run() { this.addCount(); } private void addCount() { System.out.println(Thread.currentThread().getName()+"加100之后:"+count.addAndGet(100)); count.addAndGet(1); } public static void main(String[] args) throws InterruptedException { MyThread[] myThreads = new MyThread[10]; for (int i = 0; i < 10; i++) { myThreads[i] = new MyThread("Thread-"+i); } for (int i = 0; i < 10; i++) { myThreads[i].start(); } Thread.sleep(2000); System.out.println(MyThread.count); }}

打印结果:

Thread-0加100之后:100Thread-2加100之后:201Thread-1加100之后:302Thread-5加100之后:602Thread-4加100之后:502Thread-3加100之后:402Thread-6加100之后:706Thread-7加100之后:807Thread-9加100之后:908Thread-8加100之后:10091010

可以看到,结果值正确但是打印顺序出错了,出现这样的原因是因为AtomicInteger的addAndGet()方法是原子的,但方法与方法之间的调用却不是原子的。也就是方法addCount的调用不是原子的。解决这样的问题必须要用同步。

6 synchronized代码块有volatile同步的功能

关键字synchronized可以使多个线程访问同一个资源具有同步性,而且它还具有将线程工作内存中的私有变量与公共内存中的变量同步的功能。

我们把前面讲到的异步死循环代码改造一下:

public class PrintString implements Runnable{ private boolean isRunnning = true; @Override public void run() { String lock = new String(); System.out.println("Thread begin: "+Thread.currentThread().getName()); while (isRunnning == true){ synchronized (lock){ //加与不加的效果就是是否死循环 } } System.out.println("Thread end: "+Thread.currentThread().getName()); } public boolean isRunnning() { return isRunnning; } public void setRunnning(boolean runnning) { isRunnning = runnning; } public static void main(String[] args) throws InterruptedException { PrintString printString = new PrintString(); Thread thread = new Thread(printString,"Thread-A"); thread.start(); Thread.sleep(1000); printString.setRunnning(false); System.out.println("我要停止它!" + Thread.currentThread().getName()); }}

打印结果:

Thread begin: Thread-A我要停止它!mainThread end: Thread-A

关键字synchronized可以保证在同一时刻,只有一个线程可以执行某一个方法或某一个代码块。它包含两个特征:互斥相和可见性。同步synchronized不仅可以解决一个线程看到对象处于不一致的状态,还可以保证进入同步方法或者同步代码块的每个线程,都看到由同一个锁保护之前所有的修改效果。

学习多线程并发。要着重“外修互斥,内修可见”,这是掌握多线程、学习多线程并发的重要技术点。

私信我或关注公众号猿来如此呀,回复:学习,获取免费学习资源包

来源网络,侵权删除

java多线程编程核心技术 pdf_Java多线程编程核心技术之volatile关键字相关推荐

  1. java编程入门pdf_Java 8编程入门官方教程(第6版) [(美)Schildt H.] 中文完整pdf扫描版[233MB]...

    Java8编程入门官方教程(第6版)针对新版JavaSE8对内容进行了全面更新.在畅销书作者Herbert Schildt(施密特)的帮助下,可以即刻开始学习Java程序设计的基础知识.<Jav ...

  2. java web 编程技术 pdf_Java WEB编程技术.pdf

    您所在位置:网站首页 > 海量文档 &nbsp>&nbsp计算机&nbsp>&nbspJava Java WEB编程技术.pdf289页 本文档一共被 ...

  3. 一文读懂Java内存模型(JMM)及volatile关键字

    点赞再看,养成习惯,公众号搜一搜[一角钱技术]关注更多原创技术文章. 本文 GitHub org_hejianhui/JavaStudy 已收录,有我的系列文章. 前言 并发编程从操作系统底层工作的整 ...

  4. 全面理解Java内存模型(JMM)及volatile关键字

    [版权申明]未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) http://blog.csdn.net/javazejian/article/details/72772461 出自[zejian ...

  5. 如何理解 JAVA 中的 volatile 关键字

    如何理解 JAVA 中的 volatile 关键字 最近在重新梳理多线程,同步相关的知识点.关于 volatile 关键字阅读了好多博客文章,发现质量高适合小白的不多,最终找到一篇英文的非常通俗易懂. ...

  6. volite java_如何理解 JAVA volatile 关键字

    最近在重新梳理多线程,同步相关的知识点.关于 volatile 关键字阅读了好多博客文章,发现质量高适合小白的不多,最终找到一篇英文的非常通俗易懂.所以学习过程中顺手翻译下来,一方面巩固知识,一方面希 ...

  7. java多线程核心技术_Java多线程编程核心技术(笔记)

    方法内部的私有变量,不存在线程安全问题,永远都是线程安全的,这是方法内部的变量是私有的特性造成的. 实例变量非线程安全,多个线程访问同一个对象的实例变量,则有可能出现非线程安全问题. synchron ...

  8. Java的并发编程中的多线程问题到底是怎么回事儿?

    转载自   Java的并发编程中的多线程问题到底是怎么回事儿? 在我之前的一篇<再有人问你Java内存模型是什么,就把这篇文章发给他.>文章中,介绍了Java内存模型,通过这篇文章,大家应 ...

  9. java web接收tcp_Java多线程实现TCP网络Socket编程(C/S通信)

    开篇必知必会 在前一篇<基于TCP协议网络socket编程(java实现C/S通信)>,实际存在一个问题,如果服务器端在建立连接后发送多条信息给客户端,客户端是无法全部接收的,原因在于客户 ...

最新文章

  1. GaitSet:通过交叉视角步态识别
  2. IT职场常见疾病之“颈椎病”
  3. qtdesigner 组件全吗_百度营销工具有哪些?带你了解百度全链路营销
  4. login控件authenticate_Login.Authenticate 事件 (System.Web.UI.WebControls) | Microsoft Docs
  5. 微软所谓的无人工介入的自动的机器翻译系统
  6. 数据eda_关于分类和有序数据的EDA
  7. LED显示驱动(三):显示驱动底层学习小结
  8. 使用码云或GitHub搭建简单的个人网站
  9. wps linux 哪个版本好用吗,WPS Linux版与国产统一操作系统UOS完成适配:体验追上Wintel...
  10. golang java 对比_golang编程语言和java的性能对比
  11. Oracle linux7.2安装11g RAC
  12. Python网络爬虫——爬取和分析NBA球员排名及各项数据
  13. 入门Python数据挖掘与机器学习(附代码、实例)
  14. 搞笑而富有哲理,看完后一个字——————“爽”
  15. 网页中常用的js特效
  16. LIBSVM实验-在matlab中使用heart_scale数据集
  17. 基于JAVA医院信息管理系统计算机毕业设计源码+系统+数据库+lw文档+部署
  18. Unity Asset Store越来越多
  19. 笔记:JavaScript编译与执行
  20. 排列组合(按键和LED)

热门文章

  1. 使用SAP Analytics Cloud显示新冠肺炎病毒感染人数的实时信息
  2. 如何自行给指定的SAP OData服务添加自定义日志记录功能
  3. SAP BOPF draft table automatic deletion
  4. why COMM_PRFREEATTR could not appear in F4 help
  5. SAP OData Total = 80是这样计算出来的
  6. UI5 registerModulePath
  7. Cloud for Customer的第一个显示的work center是怎么加载和渲染的
  8. WebClient UI view controller所有可用的属性列表
  9. SAP Kyma SSL证书请求文件(CSR)生成逻辑
  10. CL_ABAP_COMPILER - get ID - double click on local variable