Java并发编程实战——volatile

引言

Java 语言提供了一种弱同步机制——volatile 变量。它的作用是确保将变量的更新操作通知到其他线程。

当把变量声明为volatile后,编译器和运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。

另外,volatile变量不会被缓存在寄存器或对其他处理器不可见的地方,因此在读取volatile变量时总是返回最新写入的值。

一、volatile的两个核心作用

volatile 变量是一种比 synchronized 关键字更轻量级的同步机制,这种弱同步机制并不会加锁,它有两方面的作用:

  1. 保证线程可见性
  2. 禁止指令重排序

可见性指的是共享变量的多线程环境下的变化可以被其他线程所察觉到。在虚拟机中,变量一般存储于堆内存中,线程会在自己的线程空间中拷贝一个变量副本,如果线程修改了变量副本,它需要刷回到公共空间后才,新的值才能够被其他线程获取到,volatile 底层使用了MESI——CPU缓存一致性协议,来实现变量的及时通步。

重排序,在现代CPU架构设计中,往往为了更有效的提升CPU性能,在不影响程序逻辑的前提下,会将一些指令进行重排序。例如单例模式中,new 创建对象时,正常流程发生三个步骤:

  1. new 命令分配内存空间,为成员属性设置默认值,如Integer默认为0,这时一种半初始化状态。
  2. 为成员属性设置初始值,如静态代码块或一些构造语句
  3. 将变量地址分配给引用

但由于指令重拍,第一步已经生成的地址可能会立刻分配给引用,而后执行初始化,即第二和第三步颠倒。

但如果在高并发场景下,单例模式中,如果没有对单例对象增加volatile,这时发生了重排序,在 new 指令完成默认值设置后,其他线程就会判断为非空对象,从而读取一个错误的变量。

二、volatile的典型用法

2.1 循环条件的检查

先看下下面代码:

public class T {/*volatile*/ boolean running = true;// 对比一下有无volatile的情况下,整个程序运行结果的区别void m() {System.out.println("m start...");while (running) {}System.out.println("m end...");}public static void main(String[] args) {T t = new T();new Thread(t::m, "t1").start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}t.running = false;}
}

无volatile运行结果左,有volatile运行结果右:

                           

结果分析

在上面代码中,变量running存在于堆内存的 t 对象中。

当线程 t1 开始运行的时候,会把running值从内存中读到 t1 线程的工作区,在运行过程中直接使用这个copy,并不会每次都去读取堆内存,这样,在主线程修改running 的值之后,t1 线程忙于执行while死循环(这里有个变式,如果在while循环中加入一些执行的代码,让线程有时间在下一次循环之前读取一下running的值,可能结果会有不同)而感知不到,所以不会停止运行。

使用volatile ,会强制所有线程都去堆内存中读取 running 的值

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

2.2 volatile单例

还有一种用法就是在双重检查逻辑中,volatile 类型的单例,它也可以确保变量的初始化及时被其他线程感知。

参考《Java常用设计模式————单例模式》

2.3 用于操作非原子64位数值变量

另外,64位的数值变量——double、long 在多线程环境下的读取和写入需要添加 volatile。

当线程在没有同步的情况下读取变量,可能会得到一个失效值,但至少是由之前某个线程设置的值,而不是一个无效的随机的值,这种安全保证叫做最低安全性,简单来说就是,虽然并发导致了数据不一致,但最起码还是个旧值,不是无意义的值。

这种最低安全性适用于绝大多数变量,但 64 位的double 和long除外。JVM允许 64 位的读操作或写操作分解为两个32位的操作。以long类型为例,如果对该变量的读和写不在同一个线程,那么可能会读到某个值的高32位和另一个值的低32位。因此,一定记得在多线程环境下操作 double 和 long 时,加上 volatile,或者是 synchronized。

三、无法保证的原子性

volatile与synchronized区别体现在原子性上。

public class T {volatile int count = 0;void m() {for (int i = 0; i < 10000; i++)count++;}public static void main(String[] args) {T t = new T();List<Thread> threads = new ArrayList<>();for (int i = 0; i < 10; i++) {threads.add(new Thread(t::m, "t" + i));}threads.forEach((o) -> o.start());threads.forEach((o) -> {try {o.join();} catch (Exception e) {e.printStackTrace();}});System.out.println(t.count);}
}

上面代码中,成员变量count 在线程之间可见,10个线程共同完成为count 自加10000的操作,并通过join()方法将10个线程结果合到一起,我们理想的计算结果应该是count 被加了10,0000次(10个线程每个线程加10000次),但是执行结果却是:

                                                    

结果分析

volatile保证了数据的可见性,但是没有保证对数据操作的原子性,也就是说,共享数据可能会因高并发被同一个值覆盖。通俗点解释,多个线程同时改变主内存中的某个值的时候,一个线程改变了这个值,并通知给其他线程及时更新自己线程内缓冲区的副本,但是由于线程改变volatile修饰的变量后需要写入到公共内存中+其他线程再读取,这个过程必然会慢于其他线程写出的速度,导致其他线程还没来得及更新自己副本变量就执行了写出,导致主内存中的数据被覆盖,因此在高并发的情况下不对某个数据的写入加锁,即便设置了volatile可见性,依然会出现问题。

因此,volatile比synchronized速度快很多,所以,如果程序中只需要保证可见性,那就要使用volatile;而如果要同时保证可见性 + 原子性 ,则一定要加锁。

四、解决不一致问题(扩展)

上一节中volatile无法保证原子性,导致最后的结果远远小于10,0000,除了比较常规的将count++ 加锁之外是否有其他的比较好的解决方法呢?

/*** 解决同样的问题更高效的方法,是使用AtomicXXX类,* AtomicXXX类中的每一个方法都是原子性的,但是不能保证多个方法连续调用是原子性的。*/
public class T {
//    volatile int count = 0;AtomicInteger count = new AtomicInteger(0);void m() {for (int i = 0; i < 10000; i++)
//            count++;count.getAndIncrement();}public static void main(String[] args) {T t = new T();List<Thread> threads = new ArrayList<>();for (int i = 0; i < 10; i++) {threads.add(new Thread(t::m, "t" + i));}threads.forEach((o) -> o.start());threads.forEach((o) -> {try {o.join();} catch (Exception e) {e.printStackTrace();}});System.out.println(t.count);}
}

上述代码解决了volatile无法保证原子性的问题,使用AtomicXXX类,可以保证其方法操作是原子性的,执行结果如下:

incrementAndGet()方法,可以理解为加了synchronized的count++(保证了count++的原子性),但其实它的实现并不是通过synchronized而是使用了一种系统相当底层的实现,所以AtomicXXX类中方法的效率要比synchronized高很多。所以,对于纯计算的操作,建议使用AtomicXXX类。

总结

A B线程都用到了一个变量,Java默认是A线程中保留一个copy , 这样如果B 线程修改了该变量,则A线程未必知道

使用volatile关键字,会让所有线程都会读到变量的修改值。

但是,使用volatile并不能保证在多个线程共同修改共享变量时所带来的不一致问题,也就是说volatile 不能代替 synchronized

参考:《马士兵-高并发编程》56:35-67:00 + 68:50

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/558951.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java中类的加载顺序介绍(ClassLoader)

Java中类的加载顺序介绍(ClassLoader)1、ClassNotFoundExcetpion   我们在开发中&#xff0c;经常可以遇见java.lang.ClassNotFoundExcetpion这个异常&#xff0c;今天我就来总结一下这个问题。对于这个异常&#xff0c;它实质涉及到了java技术体系中的类加载。Java的类加载机…

UP装机部署步骤大纲

Linux装机 插上网线&#xff0c;然后&#xff0c;Ubuntu系统安装&#xff08;略&#xff09; 更改root密码 以装机时设置的用户登陆后&#xff0c;sudo修改root用户密码&#xff0c;然后退出&#xff0c;重新登录root。 $ sudo passwd root 下载并安装JDK $ java (根据提…

Java 多线程 —— wait 与 notify

引言 认识一下 Object 类中的两个和多线程有关的方法&#xff1a;wait 和 notify。 wait&#xff0c;当前线程进入 WAITING 状态&#xff0c;释放锁资源。 notify&#xff0c;唤醒等待中的线程&#xff0c;不释放锁资源。 一、使用 wait-notify 实现一个监控程序 实现一个…

重写equal()时为什么也得重写hashCode()之深度解读equal方法与hashCode方法渊源

重写equal()时为什么也得重写hashCode()之深度解读以及equal方法与hashCode方法渊源转载自&#xff1a;http://blog.csdn.net/javazejian/article/details/51348320 今天这篇文章我们打算来深度解读一下equal方法以及其关联方法hashCode()&#xff0c;我们准备从以下几点入手分…

Java8————Optional

引言 Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true&#xff0c;调用get()方法会返回该对象。 Optional 是个容器&#xff1a;它可以保存类型T的值&#xff0c;或者仅仅保存null。Optional提供很多有用的方法&#xff0c;这样我们就不用显式进…

问题反馈信息处理平台开发过程

问题反馈信息处理平台开发过程 “问题反馈信息处理平台”是一个将用户反馈上来的出错信息进行处理和收集的一个平台。 这个项目主要都是在实习的时候由我一个人进行开发&#xff0c;我导师在旁边进行指导完成的。 该项目的技术架构主要是&#xff1a; 前端主要基于Vue框架的…

Java8————Base64

Base64&#xff1f; Base64是一种用64个字符来表示任意二进制数据的方式。 对于二进制文件如图片、exe、音频、视频等&#xff0c;包含很多无法显示和打印的字符&#xff0c;如果希望能够通过记事本这样的文本处理软件处理二进制数据&#xff0c;就需要一个二进制转字符串的转…

大众点评后端项目解析

restful Api是一种设计风格&#xff1a; 启动前端项目&#xff1a;在前端项目根目录 npm install&#xff1a;加载依赖包 npm run mock&#xff1a;提供模拟数据的接口&#xff0c;前端脱离于后台&#xff1b;start /b npm run mock&#xff08;于后台运行&#xff09; npm…

Java中类及方法的加载顺序

代码展示 请运行下面代码&#xff0c;查看运行结果&#xff0c;并带着问题&#xff0c;尝试第二次debug程序。 class A {private static int numA;private int numA2;static {System.out.println("A的静态字段 : " numA);System.out.println("A的静态代码块…

新手入门教程-------Spring Boot中集成RabbitMQ

AMQP&#xff1a;是Advanced Message Queuing Protocol的简称&#xff0c;高级消息队列协议&#xff0c;是一个面向消息中间件的开放式标准应用层协议。 定义了以下特性&#xff1a; 消息方向消息队列消息路由&#xff08;包括&#xff1a;点到点和发布-订阅模式&#xff09;可…

Java 多线程 —— ReentrantLock 与 Condition

引言 ReentrantLock 是 JUC 下的一个功能强劲的锁工具&#xff0c;支持公平锁、非公平锁&#xff0c;以及多等待队列的 Condition 。 也常常被称为“手动锁”。本篇博客主要分析它的使用方法以及 Condition 实现的一个生产者消费者模式。 一、可替代 synchronized 的手动锁 …

Rabbitmq+Springboot设计秒杀应用

秒杀业务的核心是库存处理&#xff0c;用户购买成功后会进行减库存操作&#xff0c;并记录购买明细。当秒杀开始时&#xff0c;大量用户同时发起请求&#xff0c;这是一个并行操作&#xff0c;多条更新库存数量的SQL语句会同时竞争秒杀商品所处数据库表里的那行数据&#xff0c…

Java8————Stream API

引言 Java8 加入了java.util.stream包&#xff0c;这个包中的相关API将极大的增强容器对象对元素的操作能力。 它专注于对集合对象进行各种便利、高效的聚合操作&#xff0c;或大批量数据处理。 Stream API借助于同样新出现的Lambda表达式&#xff0c;极大的提高了编程效率和…

MySQL数据库知识点总结

数据库&#xff1a; 数据库索引的好处&#xff1a;索引是对数据库表中的一个或多个列的值进行排序的结构&#xff0c;这样检索或者查询某条记录的时候就不在是顺序查找&#xff0c;而是使用特定的查找方式进行查找&#xff0c;如通过二分查找或者是hash值来查找&#xff0c;提高…

Java8 函数式对齐约定————Eclipse自定义代码风格

引言 Java8 的函数式代码风格在Stream的使用上尤为突出。尽管我们可以通过连续调用函数完成一系列操作&#xff0c;但是其可读性并不能保证&#xff0c;还需要有与之相辅的Code Style。例如&#xff0c;请尝试阅读下面两段完全相同的代码&#xff1a; 未遵守约定格式&#xf…

Java核心篇之JVM--day3

Java核心篇之JVM--day3 Java JVM详解--通俗易懂教程 JVM&#xff1a;Java虚拟机的简称。 谈到JVM&#xff0c;通常会聊到三个问题&#xff1a; 1. 什么时候触发Java GC&#xff1f; 2. 对什么东西进行Java GC&#xff1f; 3. 如何进行Java GC&#xff1f; 首先解决第…

使用springboot来实现WebLog

使用websocket技术实时输出系统日志到浏览器端&#xff0c;实现WebLog boot-websocket-log&#xff1a; spring boot系统中使用websocket技术实时输出系统日志到浏览器端&#xff0c;因为是实时输出&#xff0c;所有第一时间就想到了使用webSocket,而且在spring boot中&#…

设计模式---观察者模式介绍与理解

设计模式---观察者模式介绍与理解&#xff1a; 观察者模式原理&#xff1a;类似于定牛奶业务 1. 奶站&#xff0c;subject&#xff1a;登记注册&#xff0c;移除&#xff0c;通知&#xff08;register&#xff0c;remove&#xff0c;notify&#xff09; 2. 用户&#xff0c;…

CRS-4995: The command ‘start resource’ is invalid in crsctl.

ntp时间调整后&#xff0c;节点1&#xff0c;advm 和acfs offline 处理办法&#xff1a; /u01/app/12.2.0.1/grid/bin/crsctl stop crs /u01/app/12.2.0.1/grid/bin/crsctl start crs 曾经尝试如下命令不起作用 /u01/app/12.2.0.1/grid/bin/acfsload start /u01/app/12.2…

抽象工厂模式升级版————泛型化实现

引言 今天回看之前总结的抽象工厂模式的实现《Java常用设计模式————抽象工厂模式》&#xff0c;聚焦于抽象工厂模式的缺点&#xff0c;试着改进了一下。 回顾一下抽象工厂模式的缺点&#xff1a; 在添加新的产品类型时&#xff0c;难以扩展抽象工厂来生产新种类的产品。…