《volatile使用与学习总结:2023-12-17》多层面分析学习java关键字--volatile

news2024/11/24 4:42:40

一、简介

volatile是java中提供的一种轻量级的同步机制,相比于synchronized更加轻量,因为volatile避免了线程频繁的上下文切换和调度,但是也有缺点,同步性差并且使用起来易出错。

二、并发编程的三个概念

  1. 原子性
    原子性即一个操作或者多个操作要不全部执行完(执行过程不被干扰),要不都不执行。
  2. 可见性
    可见性指的是,当一个线程操作一个变量的值时,在其他线程是可以及时看到这个变量修改后的值,并能获取来操作。对比与synchronized和lock,这两者也都能保证可见性,因为它们在每次释放锁之前都会将修改好的变量值更新到主内存中,保证了可见性。
  3. 有序性
    即程序执行的顺序是按照代码的先后顺序来执行的。
    java内存模型中所说的有序性总结为,在本线程中看都是有序执行的,在一个线程看另外一个线程都是无序的,也就是说前半句表示“线程内表现为串型语义”,后面句表示“指令重排序”现象和“主内存与工作内存同步延迟”现象。
    Java的内存模型

三、锁的互斥和可见性

  1. 互斥:即一次只允许一个线程持有某个特定的锁,而且一次只允许一个线程访问共享数据
  2. 锁的可见性:在前一个线程释放锁之前对变量进行更改后,将更改后的变量由工作内存更新到主内存中,保证下一个获得该锁的线程对这个变量具有可见性,如果没有及时更新到主内存中后果很严重,会导致数据不一致问题。
    要让volatile线程安全,必须保证两点
    (1)对变量的写操作不依赖于当前值
    (2)该变量没有包含在具有其他变量的变式中
    事实上就是保证该变量操作的原子性。

四、volatile变量的特性

  1. 保证可见性,不保证原子性
    (1)当写一个变量是,当前线程会将该变量从工作内存强制更新到主内存,保证其他线程实时获取的变量是最新的,保证了可见性。
    (2)这个写操作会导致其他线程中这个变量缓存无效。
  2. 禁止指令重排序
    重排序是程序为了更好地性能调度,对指令进行新的排序的一种手段,需要遵守以下规则
    (1)重排序不会对存在数据依赖关系的程序重排序
    (2)重排序是为了优化性能,但无论怎么排序,在单线程下,从排序后的运算结果都不会改变,但是在多线程下重排序则可能会影响结果
    (3)使用volatile能够禁止指令重排序,但要遵守一些规则。
    1)当执行到volatile修饰的变量进行读或者写时,该操作前面的指令都执行完毕,且该操作结果对后面指令可见,而且该操作后面的指令都未执行
    2)当指令优化时,不能将指令放到volatile后面执行,也不能把把volatile后面的语句放到前面执行。

五、volatile不适合的场景

  1. volatile不适合复合操作,下面模拟十个线程对num自增,每个线程自增1000,
    比如 num++,不是一个原子性操作,分为读取,加,赋值三个操作,所以结果达不到10000
public class VolatileTest1 {

    private static volatile int num = 0;

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    num++;
                }
            });
            thread.start();
        }
        System.out.println("num=" + num);
    }
}

结果
num=5657
num=4414
num=7491
…(完全随机)

  1. 解决办法
    (1)使用synchornized锁住变量所在的类
public class VolatileTest1 {

    public static volatile int num = 0;


    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 10000; j++) {
                        synchronized (VolatileTest1.class) {
                            num++;
                        }
                    }
                }
            }).start();
        }
        // 保证线程执行完毕
        while (Thread.activeCount() > 1) {
            Thread.yield();
        }
        System.out.println("num=" + num);
    }
}

结果:
num=100000

(2)使用lock锁住该方法

public class VolatileTest3 {

    public static volatile int num = 0;

    public static Lock lock = new ReentrantLock();


    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 10000; j++) {
                        lock.lock();
                        try {
                            num++;
                        }finally {
                            lock.unlock();
                        }
                    }
                }
            }).start();
        }
        // 保证线程执行完毕
        while (Thread.activeCount() > 1) {
            Thread.yield();
        }
        System.out.println("num=" + num);
    }
}

结果:
num=100000

(3)使用并发原子操作类AtomicInteger ,其原理是通过CAS循环的方式来保证原子性

public class VolatileTest5 {

    public static volatile AtomicInteger num = new AtomicInteger(0);


    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 10000; j++) {
                        num.getAndIncrement();
                    }
                }
            }).start();
        }
        // 保证线程执行完毕
        while (Thread.activeCount() > 1) {
            Thread.yield();
        }
        System.out.println("num=" + num);
    }
}

结果:
num=100000

六、单例模式的双重检验锁为什么要加volatile

public class InstanceTest{
  private volatile static InstanceTest instance;
  public static InstanceTest getInstance(){   //1
    if(instance == null){   //2
      synchronized(InstanceTest.class){   //3
        if(instance == null){   //4
          instance = new InstanceTest(); //5
        }
      }
    }
    return instance;   //6
  }
}

在并发情况下,如果没有volatile修饰的话,代码中5处会出现问题instance = new InstanceTest();
这个可以分为三步,

a. memory = allocate() //分配内存
b. ctorInstanc(memory) //初始化对象
c. instance = memory   //设置instance指向刚分配的地址

在编译时,指令重排序,不一定按照a->b->c的顺序来执行,可能会是a->c->b,多线程下一个线程在执行完a,马上执行c,设置instance指向刚分配的地址,这个时候另外一个线程刚好到第2步判断,则会出现不为空,直接跳到第6步,而此时并没有初始化,会返回一个未初始化的对象。

七、volatile原理

volatile可以保证线程的可见性并提供一定的有序性,底层采用的是“内存屏障”来实现的,但是不能保证原子性,需要结合lock,synchornized,AtomicInteger 等来实现线程并发安全。
内存屏障会提供三个功能:

  1. 他不会把volatile修饰的变量执行的操作的前面的指令方放到后面,当然相反也不会将后面的指令放到前面。(禁止指令重排序)
  2. 他会强制将当前修改的变量立即写入主缓存
  3. 如果是写操作,他会将其他线程的缓存置为无效

借鉴学习-侵删

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

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

相关文章

单词搜索 II

题目链接 单词搜索 II 题目描述 注意点 board[i][j] 是一个小写英文字母words[i] 由小写英文字母组成words 中的所有字符串互不相同同一个单元格内的字母在一个单词中不允许被重复使用 解答思路 要想找到一个完整的单词&#xff0c;首先想到的是深度优先遍历&#xff0c;如…

patchless amsi学习(中)

DR7 DR7被称为“调试控制寄存器”&#xff0c;允许对每个硬件断点进行精细控制。其中&#xff0c;前8位控制是否启用了特定的硬件断点。偶数位&#xff08;0、2、4、6&#xff09;称为L0-L3&#xff0c;在本地启用了断点&#xff0c;这意味着仅在当前任务中检测到断点异常时才…

RocketMQ 顺序消息收发实践

目录 概述局部有序创建 Topic配置代码测试 结束 概述 顺序消息 全局有序&#xff1a;适用于性能不是特别高的场景&#xff0c;但是又要求消息又严格一致的概念。局部有序&#xff1a;适用于性能要求高的场景&#xff0c;想办法通过在设计层面处理有序的消息尽量发送至同一个 T…

qt QFile文件操作处理,QFileInfo文件信息读取的简单介绍

QFile类用于文件操作,对文件进行读写操作,可读写文件,二进制文件和qt资源文件.处理文本文件和二进制文件可使用QTextStream类和QDataStream类,处理临时文件可以使用QTemporary,获取文件信息可以使用QFileInfo,处理目录可以使用QDir,监视文件和目录变化可以使用QFileSystemWatch…

整合SSH(Spring+Struts+Hibernate)

0.前言, 由于工作需要故来学习SSH的整合,structs其实相当于(把view和controller结合起来,没有像现在的前后端分离,请求会发送给Action处理,在structs.xml映射地址和类) Hibernate(就是处理数据库的,几乎自动化,也可以写sql语句) Struts&#xff1a;Struts 是一个基于 MVC&#…

【Python】计算一年内的总天数(还有跨年日期)

花了一段时间才找到Python中求一年中总日数&#xff08;total day of the Year&#xff09;的格式代码&#xff0c;所以也把计算方法记录下来。 基本 首先&#xff0c;简单地找出一年中的总天数&#xff0c; strftime() 和 strptime() 的格式代码是 %j ↓看这里 使用 strft…

算法基础之Prim算法求最小生成树

Prim算法求最小生成树 核心思想&#xff1a;Prim 算法 类似于dijkstra算法 更新距离时改为到**集合(生成树)**的距离 最小生成树长这样 每次迭代放一个最近(有边)点&#xff0c;一条最短边进生成树 #include <cstring>#include <iostream>#include <algori…

08-Event Sources和Sink架构

1 PingSource -> Kubernetes Service Sink 架构模型 示例1 部署一个kubernetes类型的sink&#xff0c;这里面还是以event-display为例&#xff0c;下面是资源清单 --- apiVersion: apps/v1 kind: Deployment metadata:name: event-display spec:replicas: 1selector:matc…

Axie Infinity 之后,Ronin 的潜力何在?

作者&#xff1a;stellafootprint.network 数据来源&#xff1a;Ronin Dashboard 备受欢迎的 Web3 游戏 Pixels 在 2023 年 10 月下旬从 Polygon 迁移到了专为游戏设计的区块链 Ronin。Pixels 此前作为 Polygon 上活跃用户&#xff08;钱包数量&#xff09;最多的 Web3 游戏&…

【重点】【DFS】【子集】78.子集

题目 法1&#xff1a;DFS 必须掌握的方法&#xff01;&#xff01;&#xff01; 我们也可以用递归来实现子集枚举。 假设我们需要找到一个长度为 nnn 的序列 aaa 的所有子序列&#xff0c;代码框架是这样的&#xff1a; void dfs(int cur, int n, LinkedList<Integer>…

HTML5+CSS3小实例:3D发光切换按钮效果

目录 一、运行效果 图片效果 二、项目概述 三、开发环境 四、实现步骤及代码 1.创建空文件夹 2.完成页面内容 3.完成css样式 五、项目总结 六、源码获取 一、运行效果 图片效果 二、项目概述 该项目是一个基于HTML和CSS的动态小猫动画。通过使用CSS样式和动画效果…

开源支付项目,还有强大的聚合支付项目,值得借鉴学习,收藏备用

开源支付项目,还有强大的聚合支付项目,值得借鉴学习,收藏备用 自学编程之道2023-03-18 14:26 移动支付随处可见,以前都是微信、支付宝分别各一个收款码,二维码多了之后就不好管理了,随着支付方式的增多,现在基本上都用聚合支付,多种支付方式,一码搞定。针对支付及聚…

Linux常用网络指令

网络参数设定使用的指令 手动/自动设定与启动/关闭 IP 参数&#xff1a;ifconfig, ifup, ifdown ifconfig ifconfig常用于修改网络配置以及查看网络参数的指令 [rootwww ~]# ifconfig {interface} {up|down} < 观察与启动接口 [rootwww ~]# ifconfig interface {options…

项目管理:产品经理如何保障项目按时完成

一个项目的成功要考虑多方因素&#xff0c;即使经过了精细的策划&#xff0c;但是在项目推进过程中也会遇到各种问题。 产品经理的任务就不仅仅在于完成产品策划&#xff0c;还需要承担项目管理工作&#xff0c;跟进项目进度&#xff0c;必要时还得出面协调、解决冲突&#x…

猜数字小游戏(猜错了会关机推荐让室友帮你玩)

前言 今天来带大家写一个简易的猜数字小游戏&#xff0c;如果连着猜错n次&#xff08;自己设定&#xff09;就会导致电脑关机&#xff0c;还在等什么呢&#xff1f;赶紧学会咯&#xff0c;发给你的室友让他帮你玩吧&#xff01; 正文 随机数的生成 首先我们还要学会如何创建随…

Windows10 如何开机自动启动redis

前言 当我们在Windows 10上使用Redis时&#xff0c;通常希望能够使Redis服务在系统启动时自动启动&#xff0c;以便我们无需手动介入就能够方便地访问和管理数据。在这个过程中&#xff0c;我们将通过下载、安装和配置Redis为Windows服务的方式&#xff0c;使其成为系统的一部分…

python与机器学习4,激活函数

目录 1 激活函数1: 单位阶跃函数 1.1 函数形式 1.2 函数图形 1.3 函数特点 1.4 代码实现这个 单位阶跃函数 2 激活函数2 sigmoid函数 2.1 函数形式 2.2 函数图形 2.3 函数特点 2.3.1 是一个连续函数&#xff0c;且是一个渐变的曲线 2.3.2 是连续区间的[0,1] , 可以…

JVM基础扫盲

什么是JVM JVM是Java设计者用于屏蔽多平台差异&#xff0c;基于操作系统之上的一个"小型虚拟机"&#xff0c;正是因为JVM的存在&#xff0c;使得Java应用程序运行时不需要关注底层操作系统的差异。使得Java程序编译只需编译一次&#xff0c;在任何操作系统都可以以相…

定制 Electron 窗口标题栏

Electron 是一款流行的桌面应用开发框架&#xff0c;基于 Web 技术构建&#xff0c;提供了强大的跨平台能力。在开发过程中&#xff0c;经常需要定制窗口标题栏以创造独特的用户体验。 1. 完全隐藏默认标题栏 有时候&#xff0c;我们希望创建一个自定义的标题栏&#xff0c;完…

光伏收益计算工具:实现可持续能源投资的决策支持

随着全球能源结构的转型&#xff0c;光伏发电作为主要的可再生能源之一&#xff0c;其投资前景日益光明。然而&#xff0c;光伏发电项目的投资决策需要基于准确的收益预测。因此&#xff0c;光伏收益计算工具应运而生&#xff0c;为投资者提供决策支持。 光伏收益计算工具是一种…