volatile关键字

volatile关键字是什么

在上一章我们讲到了并发的的三个概念,那么今天在讲解下在java中可以保证可见性和有序性的一个关键字。

volatile关键字 :当变量的值被该关键字修饰后该值任何读写操作对于其他线程是立即可见的。并且被关键字修饰后的变量被禁止重排序。

volatile原理解析

在定义中我们可以看到volatile关键字有2个特性,可见性和有序性,那么volatile是如何保证这个可见性和有序性的呢?那他为什么不能保证原子性呢?

首先volatile通过加入内存屏障和禁止指令重排序优化来实现的

可见性

写操作

对volatile 变量进行写操作时,会在写操作后添加一个Store Memory Barrier屏障指令将工作内存中的变量写入主内存中。该指令会优先执行在缓冲区所有该变量的相关操作。相当于手动刷新到主内存。

读操作

对volatile变量进行读操作时,会在读操作之前添加一个Load Memory Barrier屏障指令,从主内存读取共享变量。该指令会将失效队列中所有的指令执行,让其他cpu对该变量的改动全部生效,并且刷新回主内存,然后在读取该主内存中的值。相当于手动刷新该变量的最新值。

有序性

  1. StoreStore屏障 该指令之前的写操作不能和该指令之后的写操作重排序.
  2. StoreLoad屏障 该指令之前的写操作和该指令之后的读操作重排序.
  3. LoadLoad屏障 该指令之前的读操作不能和该指令之后的读操作重排序.
  4. LoadStore屏障 该指令之前的读操作不能和该指令之后的写操作重排序。

写操作

在volatile写和普通写不能交换位置。也就能保证volatile写的值是最新的值。

volatile写以及后来的读不能交换位置,也就是后来的读必须在volatile之后执行。

读操作

而在volatile读不能与在volatile读之后的读操作和写操作交换位置。也就是说volatile读之后的读写操作都必须在volatile读之后完成。

注意:Memory Barrier 可以参看CPU缓存一致性协议MESI-硬件内存模型

volatile适用场景

例子一

  1. @Slf4j
  2. public class UnsafeExample {
  3. private static final int CLIENT_TOTAL = 30000;
  4. private static final int THREAD_TOTAL = 300;
  5. private static volatile int count = 0;
  6. public static void main(String[] args) throws InterruptedException {
  7. //模拟并发
  8. ExecutorService executorService = Executors.newCachedThreadPool();
  9. CountDownLatch countDownLatch = new CountDownLatch(CLIENT_TOTAL);
  10. Semaphore semaphore = new Semaphore(THREAD_TOTAL);
  11. for(int i = 0;i<CLIENT_TOTAL;i++){
  12. executorService.execute(()->{
  13. try{
  14. semaphore.acquire();
  15. add();
  16. semaphore.release();
  17. }catch (Exception e){
  18. e.printStackTrace();
  19. log.error(e.getMessage(),e);
  20. }
  21. countDownLatch.countDown();
  22. });
  23. }
  24. countDownLatch.await();
  25. System.out.println("统计次数:"+count);
  26. }
  27. private static void add(){
  28. count++;
  29. }
  30. }

通过测试得出volatile并不能保证上述demo的线程安全。也就是说依赖当前值来决定下一个值的场景并不适合volatile

通过上述描述可以总结出两点使用场景。

  1. 对该变量的操作不依赖当前值。(读自己 写自己)
  2. 该变量没有包含在具有其他变量的不变式中。 (读别人 写自己)

举个简单的例子

  1. //这种volatile的使用就是错误的 违反了第一条
  2. volatile int x = x+1;
  3. int a = 1;
  4. //这种volatile使用场景是错误的 违反了第二条
  5. volatile int b = a + 1;

实际上,这些条件表明,可以被写入 volatile 变量的这些有效值独立于任何程序的状态,包括变量的当前状态。

因此特别适合作为状态标记量。

使用场景

1.状态标记

重新举一下在上一章中的例子

  1. @Slf4j
  2. public class SimpleHappenBefore {
  3. private static int a = 0;
  4. private static boolean flag = false;
  5. public static void main(String[] args) throws InterruptedException {
  6. for (int i = 0; i < 1000; i++) {
  7. ThreadA threadA = new ThreadA();
  8. ThreadB threadB = new ThreadB();
  9. threadA.start();
  10. threadB.start();
  11. threadA.join();
  12. threadB.join();
  13. a = 0;
  14. flag = false;
  15. }
  16. }
  17. static class ThreadA extends Thread {
  18. @Override
  19. public void run() {
  20. a = 1;
  21. flag = true;
  22. }
  23. }
  24. static class ThreadB extends Thread {
  25. @Override
  26. public void run() {
  27. if (flag) {
  28. a = a * 1;
  29. }
  30. if (a == 0) {
  31. System.out.println("ha,a==0");
  32. }
  33. }
  34. }
  35. }

上面的例子中可以看出flag的改变与其他状态 和他自己本身的状态完全没有关系。所以这里使用volatile关键字是合格的。

2.线程安全的单例对象发布

双重检查单例模式方式发布对象

单例模式-懒汉单例

  1. public class LazySingleton {
  2. private static volatile LazySingleton lazySingleton;
  3. private LazySingleton() {
  4. }
  5. public static LazySingleton getInstance() {
  6. if (lazySingleton == null) {
  7. synchronized(LazySingleton.class) {
  8. if (lazySingleton == null) {
  9. lazySingleton = new LazySingleton();
  10. }
  11. }
  12. }
  13. return lazySingleton;
  14. }
  15. }

3.独立观察(independent observation)

比如论坛常用的统计最后一个注册的用户。

  1. public class UserRegister {
  2. public volatile String lastRegisterUser;
  3. public void registerUser(String user, String password) {
  4. User u = new User();
  5. u.setUser(user);
  6. u.setPassword(password);
  7. userService.registerUser(u);
  8. lastRegisterUser = user;
  9. }
  10. }

4.开销较低的读-写锁策略

读操作使用volatile 写操作使用synchronzied

  1. public class Counter {
  2. private volatile int value;
  3. public int getValue() { return value; }
  4. public synchronized int increment() {
  5. return value++;
  6. }
  7. }

总结

volatile关键字是一个保证可见性和有序性的关键字。该关键字修饰的变量操作与该变量本身状态以及其他变量状态无关的情况下使用才可以保证其并发安全性。顺便吐槽一下(该变量没有包含在具有其他变量的不变式中)这句话,楼主理解这句话理解了很久,后面突然就顿悟了。对于语言文字的理解能力真的很重要。

并发之volatile关键字的更多相关文章

  1. Java 高效并发之volatile关键字解析

    摘录 1. 计算机在执行程序时,每条指令都是在CPU中执行的,而执行指令过程中,势必涉及到数据的读取和写入.由于程序运行过程中的临时数据是存放在主存(物理内存)当中的,这时就存在一个问题,由于CPU执 ...

  2. Java并发之volatile关键字

    引言 说到多线程,我觉得我们最重要的是要理解一个临界区概念. 举个例子,一个班上1个女孩子(临界区),49个男孩子(线程),男孩子的目标就是这一个女孩子,就是会有竞争关系(线程安全问题).推广到实际场 ...

  3. Java并发之(1):volatile关键字(TIJ21-21.3.3 21.3.4)

    Java并发Java服务器端编程的一项必备技能. ** 1 简介    volatile是java中的一个保留关键字,它在英语中的含义是易变的,不稳定的.volatile像final.static等其 ...

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

    关联文章: 深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型(@Annotation) 深入理解Java类加载器(ClassLoad ...

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

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

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

    原文地址:全面理解Java内存模型(JMM)及volatile关键字 关联文章: 深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型( ...

  7. volatile关键字的详解-并发编程的体现

    xl_echo编辑整理,欢迎转载,转载请声明文章来源.欢迎添加echo微信(微信号:t2421499075)交流学习. 百战不败,依不自称常胜,百败不颓,依能奋力前行.--这才是真正的堪称强大!! 参 ...

  8. Java并发编程:volatile关键字解析

    Java并发编程:volatile关键字解析 volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在 ...

  9. volatile关键字 学习记录2

    public class VolatileTest2 implements Runnable{ volatile int resource = 0; public static void main(S ...

随机推荐

  1. DOJO常用的函数

    DOJO常用的: 1,通过dojo.require以类似C编程中#include或者Java中import的方式加载所需的部件如dojo.require("dojo.parser" ...

  2. IntelliJ IDEA和Eclipse最常用的快捷键对应表:

    描述 Eclipse Intellij 代码补全 Ctrl+space Ctrl+space 打开类或者接口 (两个IDE都支持使用“驼峰字符”前缀的方式来过滤查找列表,进而轻松完成搜索:比如:可 以 ...

  3. JQuery实现轮播图及其原理

    源码: <!DOCTYPE html> <html> <head> <meta charset="utf-8" name="vi ...

  4. 关于js事件执行顺序小技巧

    js事件执行顺序是js中一个老生常谈的一个话题, 聊这个话题之前我们先谈谈怎么给页面元素绑定我们需要的事件 1.给页面元素绑定事件 a)直接在元素上面加上需要绑定的事件,如 <button ty ...

  5. 转--python 黑魔法2

    Python 高效编程小技巧 个人博客:临风|刀背藏身 Python 一直被我拿来写算法题,小程序,因为他使用起来太方便了,各种niubi闪闪的技能点也在写算法的过程中逐渐被挖掘到,感谢万能的谷哥度娘 ...

  6. 二、主目录 Makefile 分析(2)

    2.7 编译选项---config.mk 代码 163 164 行 # load other configuration include $(TOPDIR)/config.mk 此段就是包含顶层目录下 ...

  7. 复杂HTML解析

    面对页面解析难题时候,需要注意问题: 1.寻找“打印次页”的链接,或者看看网站有没有HTML样式更友好的移动版(把自己的请求头设置成处于移动设备的状态,然后接收网站移动版). 2.寻找隐藏在JavaS ...

  8. VS中修改工程名的解决方案

    VS中修改工程名的解决方案: 一.先修改工程名/解决方案名(在VS中修改即可)举例,原先的工程名为OldProject   想要改成NewProject1.找到工程/解决方案所在的文件夹(已工程名/解 ...

  9. Python 升级致yum 问题,pip 异常

    升级 Python 导致 yum 和 pip 异常: 一些storm 和 自定义项目 需要升级python版本:Linux 系统默认是2.6 版本 ,所以需要根据业务进行升级操作:Python 官方下 ...

  10. ado.net 访问excel

    1 类:OleDbConnection ... 2 connection string : Provider=Microsoft.ACE.OLEDB.12.0;Data Source={excelPa ...