1. volatile是什么?

在谈及线程安全时,常会说到一个变量——volatile。在《Java并发编程实战》一书中是这么定义volatile的——“Java语言提供了一种稍弱的同步机制,即volatile变量,用来确保将变量的更新操作通知到其他线程”。这句话说明了两点:①volatile变量是一种同步机制;②volatile能够确保可见性。这两点和我们探讨“volatile变量是否能够保证线程安全性”息息相关。

2. volatile变量能确保线程安全性吗?为什么?

什么是同步机制?在并发程序设计中,各进程对公共变量的访问必须加以制约,这种制约称为同步。也就是说,同步机制即为对共享资源的一种制约。那么问题来了:volatile这种“稍弱的同步机制”是怎么制约各个进程对共享资源的访问的呢?答案就在“volatile能够确保可见性”中。

2.1 可见性

volatile能够保证字段的可见性:volatile变量,用来确保将变量的更新操作通知到其他线程。volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。

可见性和“线程如何对变量进行操作(取值、赋值等)”有关系:

我们要先明确一个定律:线程对变量的所有操作(取值、赋值等)都必须在工作内存(各线程独立拥有)中进行,而不能直接读写内存中的变量,各工作内存间也不能相互访问。对于volatile变量来说,由于它特殊的操作顺序性规定,看起来如同操作主内存一般,但实际上 volatile变量也是遵循这一定律的。

关于主存与工作内存之间具体的交互协议(即一个变量如何从主存拷贝到工作内存、如何从工作内存同步到主存等实现细节),Java内存模型中定义了以下八种操作来完成:

lock:(锁定),unlock(解锁),read(读取),load(载入),use(试用), assign(赋值),store(存储),write(写入)。

volatile 对这八种操作有着两个特殊的限定,正因为有这些限定才让volatile修饰的变量有可见性以及可以禁止指令重排序 :

① use动作之前必须要有read和load动作, 这三个动作必须是连续出现的。【表示:每次工作内存要使用volatile变量之前必须去主存中拿取最新的volatile变量】

② assign动作之后必须跟着store和write动作,这三个动作必须是连续出现的。【表示: 每次工作内存改变了volatile变量的值,就必须把该值写回到主存中】

有以上两条规则就能保证每个线程每次去拿volatile变量的时候,那个变量肯定是最新的, 其实也就相当于好多个线程用的是同一个内存,无工作内存和主存之分。而操作没有用volatile修饰的变量则不能保证每次都能获取到最新的变量值。

2.2 所以volatile究竟能否保证线程安全性?

答:在某些特定的情况下能。那到底什么是什么能,什么时候又不能了呢?我们继续往下看。

(1)volatile能保证线程安全的情况

要使 volatile 变量提供理想的线程安全性,必须同时满足两个条件:①对变量的写操作不依赖于当前值。②该变量没有包含在具有其他变量的不变式中。

实际上,这两个条件表明,可以被写入 volatile 变量的这些有效值独立于任何程序的状态,包括变量的当前状态。大多数编程情形都会与这两个条件的其中之一冲突(如:“若没有则添加”、“若相等则移出”的复合操作等复合操作都是与①或②相冲突的),使得 volatile 变量不能像 synchronized 那样普遍适用于实现线程安全。

(2)volatile不能保证线程安全的情况

除了(1)中提到的能够使volatile发挥保证线程安全性的情况,其他情况下volatile并不能保证线程安全问题,因为volatile并不能保证变量操作的原子性。

我们先以 i++( i++ 是非原子操作)为例:

private volatile int i = 0,两个线程同时执行 i++,

此时是两个线程同时从主内存中拿到 i 的最新值 0 ,并且同时对 i 进行 +1 操作并将和赋值回 i,最后同时将 +1 后的 i 值写回主内存中,最终 i == 1,很明显结果是错的。

下面我们再通过更详细的代码来验证“即使变量用了volatile来修饰,才进行非原子操作时依旧会出现线程安全问题”:

class Window implementsRunnable {private volatile int ticket = 100;public voidrun() {for(;;) {//通过下面的①②两个步骤我们可以发现:对一个共享资源可以多个线程同时进行修改,自然就会有线程安全问题。

if (ticket > 0) {try{

Thread.sleep(100);//①多个线程同时判断到“ticket>0”,然后挂起了

} catch(InterruptedException e) {

e.printStackTrace();

}//②多个线程同时醒来,同时进行“ticket--”操作:

System.out.println(Thread.currentThread().getName() + ":" + ticket--);

}else{break;

}

}

}

}

public classA03UseVolatileIsNotThreadSafe {public static voidmain(String[] args) {

Window w= newWindow();

Thread t1= newThread(w);

Thread t2= newThread(w);

Thread t3= newThread(w);

t1.setName("窗口1");

t2.setName("窗口2");

t3.setName("窗口3");

t1.start();

t2.start();

t3.start();

}

}

测试结果:(1)出现了大量的重复数字; (2)最后还输出了 “-1”;==》说明变量即使用volatile修饰了但依旧出现了线程安全问题。

代码解析:

出现问题(1)的原因:线程存在“先检查后执行”的竞态条件。可能有两个线程同时拥有CPU的执行权(机器是双核的),它们判断到做“if (ticket > 0)”,并同时做“ticket--”操作。

出现问题(2)的原因:

①当ticket==1时,两个或多个线程同时通过了“if (ticket > 0)”的判断,并进入了判断框中去执行代码;

②然后它们执行到“Thread.sleep(100);”就睡了;

③睡醒后总有一个线程会先抢到cup的执行权,然后执行“ticket--”操作,并将最新的ticket数值推送告知到每个线程;

④此时那些在判断框中的其他的线程并不会再次做“if (ticket > 0)”的判断,而是直接拿最新的ticket并做“ticket--”操作。

就算线程在“ticket--”之前每次都做“if (ticket > 0)”的判断,也依旧会有线程安全问题,因为又可能出现①那种同时通过判断的状态。

// volatile的典型用法:检查某个状态标记,以判断是否退出循环。

volatile booleanasleep;

……

while( !asleep){

countSomeSheep();

}

3. volatile的典型用法:检查某个状态标记,以判断是否退出循环。

volatile booleanasleep;

……while( !asleep){

countSomeSheep();

}

4.总结:因为volatile不能保证变量操作的原子性,所以试图通过volatile来保证线程安全性是不靠谱的。

volatile能保持线程安全吗_volatile是什么?volatile能保证线程安全性吗?如何正确使用volatile?...相关推荐

  1. 面试准备每日系列:计算机底层之并发编程(一)原子性、atomic、CAS、ABA、可见性、有序性、指令重排、volatile、内存屏障、缓存一致性、四核八线程

    文章目录 1. 什么是进程?什么是线程? 2. 线程切换 3. 四核八线程是什么意思 3.1 单核CPU设定多线程是否有意义 4. 并发编程的原子性 4.1 如何解决原子性问题 & atomi ...

  2. Java中保证线程安全的三板斧

    前言 现在,如果要使用 Java 实现一段线程安全的代码,大致有 synchronized . java.util.concurrent 包等手段.虽然大家都会用,但却不一定真正清楚其在 JVM 层面 ...

  3. Java线程池状态判断源码_深入浅出Java线程池:源码篇

    前言 在上一篇文章深入浅出Java线程池:理论篇中,已经介绍了什么是线程池以及基本的使用.(本来写作的思路是使用篇,但经网友建议后,感觉改为理论篇会更加合适).本文则深入线程池的源码,主要是介绍Thr ...

  4. 线程安全list_多线程开发之如何创建一个线程安全的类

    上一篇讨论了如何解决线程安全的问题,今天总结如何设计一个线程安全的类: 创建线程安全类的关注点 一个类要想线程安全,除了上一篇文章通过外部解决方式外,还可以通过合理的设计类的内部来解决,使类本身就线程 ...

  5. 多线程情况下如何保证线程安全

    一.线程安全等级 其实线程安全并不是一个"非黑即白"单项选择题.按照"线程安全"的安全程度由强到弱来排序,我们可以将java语言中各种操作共享的数据分为以下5类 ...

  6. 到底如何保证线程安全,总结得太好了。。

    一.线程安全等级 之前的博客中已有所提及"线程安全"问题,一般我们常说某某类是线程安全的,某某是非线程安全的.其实线程安全并不是一个"非黑即白"单项选择题. 按 ...

  7. volatile能保持线程安全吗_Java线程安全(volatile synchronized)

    总结 volatile不能保证线程安全而synchronized可以保证线程安全.volatile只能保证被其修饰变量的内存可见性,但如果对该变量执行的是非原子操作线程依旧是不安全的.而synchro ...

  8. Java并发,volatile+不可变容器对象能保证线程安全么?!

    <Java并发编程实战>第3章原文 <Java并发编程实战>中3.4.2 示例:使用Volatile类型来发布不可变对象 在前面的UnsafeCachingFactorizer ...

  9. java和线程相关的关键字有哪些_Java中有哪些机制来保证线程安全?synchronized关键字和volatile关键字...

    想要解决线程安全问题,首先要知道为什么会造成线程不安全? 在单线程中,我们从来没有提到个线程安全问题,线程安全问题是只出现在多线程中的一个问题.因为多线程情况下有共享数据,每个线程都共享这些数据并对这 ...

  10. volatile不能保证线程安全

    对于volatile这个关键字,相信很多朋友都听说过,甚至使用过,这个关键字虽然字面上理解起来比较简单,但是要用好起来却不是一件容易的事. 这篇文章将从多个方面来讲解volatile,让你对它更加理解 ...

最新文章

  1. JS事件:target与currentTarget区别
  2. 1.15 Java访问控制修饰符(public、 private、protected 和 friendly)
  3. pageadminCMS.Net Framework的安装教程
  4. 【渝粤题库】国家开放大学2021春2227物业设备设施管理题目
  5. C#开发 —— 基础知识
  6. android如何适配平板,适用于平板电脑、大屏设备和可折叠设备的自适应布局
  7. 用 普通 用户欺骗登陆 获取 管理员的最高权限~~~~
  8. 企业应用超级App来啦!
  9. java中的mapper是什么_Java使用ObjectMapper的简单示例
  10. python报表自动化系列 - 为pandas.DataFrame制作自然数索引(更改索引为从1开始的自然数)
  11. C语言的数据类型→字符型数据
  12. java怎样学_告诉你java如何学
  13. 你的灯还亮着吗阅读笔记之二
  14. 一行python代码查找中文同义词(synonyms)
  15. 医院职工离职申请证明模板,共计10篇
  16. Vue下载文件不成功及下载文件名称问题
  17. SQLServer 查询建表语句
  18. html 字怎么居中怎么写,html文字居中代码怎么写
  19. 原生js倒计时插件封装
  20. 总有些中文情歌,让我莫明的感动了

热门文章

  1. 内网ip和外网ip区别
  2. 计算机如何在文段中插入符号,插入项目符号与编号(Word 2013基础)——想象力电脑应用...
  3. nltk.stem 词干提取(stemming)
  4. 浅析贴片电感的作用及使用原理
  5. linux桌面小程序开发日记_1(pyqt5 + yolov5)
  6. excel 中vb组合框_Excel 2013中的工作表组合框问题
  7. Python名词解释
  8. java简单小程序输出所有汉字代码实例
  9. 【计算机网络 (谢希仁) 习题题解】第5章 运输层 (5)——TCP的运输连接管理
  10. friendly发音_friendly是什么意思