java多线程(六)关键字Volatile可见性、有序性以及单个变量的原子性

volatile关键字

作用

volatile 是 Java 虚拟机提供的轻量级的同步机制,主要用来确保变量被线程安全地读取和写入。

当一个变量定义为 volatile 后,它具备以下特性:

  1. 可见性:确保不同线程对这个变量操作的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。

  2. 有序性:禁止进行指令重排序优化。

实现原理

在 Java 内存模型中,每个线程在运行时都有自己的工作内存,其中存放着主内存中变量副本的拷贝。

当线程对变量进行修改时,首先修改的是自己工作内存中的副本,然后线程再将修改后的副本值刷新到主内存中。

当其他线程需要读取该变量时,会从主内存中重新读取最新的副本到自己的工作内存中。

volatile 变量的特殊规则如下:

  • 每次使用前都需要从主内存中刷新最新的值,保证读取的总是最新的数据。

  • 每次修改后都需要立即同步回主内存中,保证其他线程可以看到最新的值。

使用场景
  1. 状态标记量:如中断标记、状态标记等。

  2. 单例模式的双重检查锁定:防止指令重排序。

注意事项
  1. volatile 变量不适合作为状态的复合操作,因为 volatile 不能保证复合操作的原子性。

  2. volatile 只能保证变量的可见性和有序性,不能保证原子性。

volatile示例讲解

1. 可见性

Volatile保证了变量的可见性,即当一个线程修改了某个变量的值,这个新值对其他线程来说是立即可见的。

反面代码示例:不使用volatile时,可能导致线程读取到过时数据。

package com.hmblogs.backend.study.thread;public class VolatileExample {// 假设threadA和threadB同时访问isFlag变量,且没有使用volatile关键字static boolean isFlag = false;public static void main(String[] args) throws InterruptedException {Thread thread1 = new Thread(() -> {System.out.println("Thread 1 is running");// thread1中执行isFlag = true;});Thread thread2 = new Thread(() -> {System.out.println("Thread 2 is running");// thread2中执行if (!isFlag) {// 这里的代码可能会执行,即使isFlag已经被thread1设置为trueSystem.out.println("isFlag 是 false.");}});thread1.start();thread2.start();System.out.println("Both threads have finished their execution");}
}

正面代码示例:使用volatile确保变量值的实时更新。

package com.hmblogs.backend.study.thread;public class VolatileExample {// 假设threadA和threadB同时访问isFlag变量,且没有使用volatile关键字volatile static boolean isFlag = false;public static void main(String[] args) throws InterruptedException {Thread thread1 = new Thread(() -> {System.out.println("Thread 1 is running");// thread1中执行isFlag = true;});Thread thread2 = new Thread(() -> {System.out.println("Thread 2 is running");// thread2中执行if (!isFlag) {// 这里的代码可能会执行,即使isFlag已经被thread1设置为trueSystem.out.println("isFlag 是 false.");}});thread1.start();thread2.start();System.out.println("Both threads have finished their execution");}
}

验证结果:volatile没有对应的特性。这是什么原因呢?

2. 原子性

Volatile对单个变量的读写具有原子性,但复合操作(如i++)不是原子的。

反面代码示例:volatile变量在复合操作中可能出现问题。

package com.hmblogs.backend.study.thread;public class VolatileExample {static volatile int counter = 0;public static void main(String[] args) throws InterruptedException {Thread thread1 = new Thread(() -> {System.out.println("Thread 1 is running");// thread1中执行counter++;});Thread thread2 = new Thread(() -> {System.out.println("Thread 2 is running");// thread2中执行System.out.println("counter 是 "+counter+".");});thread1.start();thread2.start();System.out.println("Both threads have finished their execution");}
}

有时候打印的是1,有时候是0

正面代码示例:使用synchronized或原子类来保证复合操作的原子性。

package com.hmblogs.backend.study.thread;import java.util.concurrent.atomic.AtomicInteger;public class VolatileExample {static volatile AtomicInteger counter = new AtomicInteger();public static void main(String[] args) throws InterruptedException {Thread thread1 = new Thread(() -> {System.out.println("Thread 1 is running");// thread1中执行int temp = counter.getAndAdd(1);});Thread thread2 = new Thread(() -> {System.out.println("Thread 2 is running");// thread2中执行System.out.println("counter 是 "+counter.get()+".");});thread1.start();thread2.start();System.out.println("Both threads have finished their execution");}
}

一直都是0.

验证结果:volatile没有对应的特性。这是什么原因呢?

3. 有序性

Volatile变量还可以确保指令的有序性,防止JVM进行指令重排序。

反面代码示例:不使用volatile时,JVM可能进行指令重排序。

int a = 0;
boolean flag = false; // 假设这里不使用volatile
int b = 0;// 线程A执行
a = 1; // Step 1
flag = true; // Step 2
b = 2; // Step 3// 线程B执行
if (flag) {// JVM可能重排序,导致线程B先看到b=2,再看到a=1
}

 正面代码示例:使用volatile防止指令重排序。

int a = 0;
volatile boolean flag = false;
int b = 0;// 线程A执行
a = 1; // Step 1
flag = true; // Step 2,volatile变量,确保前面操作不会重排序到后面
b = 2; // Step 3// 线程B执行
if (flag) {// 确保先看到a=1,再看到b=2
}

最后

Volatile是Java多线程编程中的一个重要关键字,它确保了变量的可见性、有序性和部分原子性。

正确使用volatile关键字可以避免多线程中的一些问题,如数据不一致和指令重排序等。

然而,需要注意的是,volatile并不能保证复合操作的原子性,对于这类操作,需要使用synchronized或原子类来保证线程安全。

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

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

相关文章

深入学习SQL优化的第三天

目录 聚合函数 排序和分组 聚合函数 1251. 平均售价 表:Prices------------------------ | Column Name | Type | ------------------------ | product_id | int | | start_date | date | | end_date | date | | price | int …

K8S - Java微服务配置 - 使用ConfigMap配置redis

参考文档&#xff1a;https://v1-27.docs.kubernetes.io/zh-cn/docs/tutorials/configuration/configure-redis-using-configmap/ cat <<EOF >./example-redis-config.yaml apiVersion: v1 kind: ConfigMap metadata:name: example-redis-config data:redis-config: …

“解决Windows电脑无法投影到其他屏幕的问题:尝试更新驱动程序或更换视频卡“

目录 背景: 解决方法1: 解决方法2: 什么是驱动程序&#xff1a; 背景: 今天在日常的工作中&#xff0c; 我想将笔记本分屏到另一个显示屏&#xff0c;我这电脑Windows10系统&#xff0c;当我按下Windows键P键&#xff0c;屏幕信息上提示我"你的电脑不能投影到其他屏幕…

C++:继承(protected、隐藏、不能被继承的类、)

目录 继承的概念 继承的使用 继承方式 protected 继承类模板 赋值兼容转换 隐藏 子类的默认成员函数 构造函数 拷贝构造函数 赋值重载函数 析构函数 不能被继承的类 方法1&#xff1a;父类的构造函数私有 方法2&#xff1a;final 继承与友元 继承与静态成员 …

Redis 键值型数据库

一、Redis是什么 Redis&#xff1a;REmote DIctionary Server&#xff08;远程字典服务器&#xff09; 是完全开源免费的&#xff0c;用C语言编写的&#xff0c;遵守BSD协议&#xff0c;是一个高性能的&#xff08;Key/Value&#xff09;分布式内存数据 库&#xff0c;基于内存…

tcp 和udp通信

一.recvfrom recvfrom函数是一个系统调用&#xff0c;用于从套接字接收数据。该函数通常与无连接的数据报服务&#xff08;如 UDP&#xff09;一起使用&#xff0c;但也可以与其他类型的套接字使用。与简单的 recv() 函数不同&#xff0c;recvfrom() 可以返回数据来源的地址信息…

FreeRTOS学习:内存管理

FreeRTOS内存管理简介 在使用 FreeRTOS 创建任务、队列、信号量等对象的时候&#xff0c; FreeRTOS 一般都提供了两种方法&#xff0c; 动态方法创建&#xff1a;自动地从 FreeRTOS 管理的内存堆中申请所创建对象所需的内存&#xff0c;在对象被删除后&#xff0c;又可以将这…

Vue3开始

1.创建 Vue3 工程 1.基于 vue-cli创建 2.基于vite创建工程 Home | Vite中文网 (vitejs.cn) nodejs 环境可以 使用 nvm 来管理 nodejs 的版本 3.开始创建 4.目录结构 5.创建完 执行 安装依赖 npm i 6.运行项目 为什么命令是 dev 就是从这里配置的 npm run dev

大白话社融中M1和M2的学习笔记

一、背景 为什么我看社融数据呢&#xff1f;因为作为一个码农&#xff0c;我不清楚当前个人所处的样本情况是否真实。我所处的环境是在深圳南山&#xff0c;身边一些同事有不少找工作不怎么顺利&#xff0c;我所在的公司今年也没招大学生了&#xff0c;人员也只出不进为主&…

本地生活服务商系统如何利用本地推获得更多曝光?

随着本地生活赛道中的竞争愈演愈烈&#xff0c;越来越多的本地生活服务商和本地生活商家开始计划着通过在本地推等平台投放相关信息&#xff0c;以提高品牌店铺的曝光量和知名度。不过&#xff0c;就目前的情况来看&#xff0c;绝大多数人都陷入了一种“投入多&#xff0c;转化…

教育部-华为产学合作协同育人项目 | 仓颉编程语言专项

为响应《教育部高等教育司关于调整产学合作协同育人项目运行模式及征集2024年产学合作协同育人项目的通知》号召&#xff0c;华为公司2024年第二批70个项目已发布&#xff0c;其中仓颉编程语言领域共计10个项目&#xff0c;如下所示&#xff0c;通过新工科建设项目&#xff0c;…

U盘安装Ubuntu24.04,乌邦图,UltralISO

文章目录 前言通过UltraISO&#xff0c;制作启动U盘下载镜像制作工具UltraISO(软碟通)下载ubuntu镜像文件制作启动U盘 安装ubuntu设置root密码&#xff0c;并登陆root 前言 在Ubuntu作为主流的linux系统&#xff0c;有时候使用VMware安装使用&#xff0c;总归有一定的性能损耗…

力扣每日一题 数组最后一个元素的最小值 位运算

Problem: 3133. 数组最后一个元素的最小值 &#x1f468;‍&#x1f3eb; 灵神题解 class Solution {public long minEnd(int n, int x) {n--; // 先把 n 减一&#xff0c;这样下面讨论的 n 就是原来的 n-1long ans x;int i 0;int j 0;while((n >> j) > 0){// …

springboot集成海康sdk,针对视频流获取某一点的实时温度

直接上代码吧: 前端页面专递点的x和y的坐标及其设备的ip @RequestMapping(value = "/getRealTemperatureByPoint") public float getRealTemperatureByPoint(HttpServletRequest request) {Map<String, Object> params = ParamUtil.getParams(request);Strin…

小区社区超市商城停车场管理系统-计算机毕设Java|springboot实战项目

&#x1f34a;作者&#xff1a;计算机毕设匠心工作室 &#x1f34a;简介&#xff1a;毕业后就一直专业从事计算机软件程序开发&#xff0c;至今也有8年工作经验。擅长Java、Python、微信小程序、安卓、大数据、PHP、.NET|C#、Golang等。 擅长&#xff1a;按照需求定制化开发项目…

Linux 信号 signal,sigaction,sigqueue,kill,相关函数

驱动与应用的结合&#xff0c;参考我的这篇&#xff1a;https://blog.csdn.net/rjszcb/article/details/113573517 一、 什么是信号 信号是软中断,用于通知进程某个事件已经发生。进程可以选择如何响应信号:忽略、默认处理、自定义处理等。 常见信号有:SIGINT(键盘中断)、SIGK…

C语言程序设计-练习篇

华夏波澜壮阔&#xff0c;少年仍需前行。 十&#xff0c;实现一个函数&#xff0c;打印乘法口诀表&#xff0c;口诀表的行数和列数自己指定 #include <stdio.h> //实现一个函数&#xff0c;打印乘法口诀表&#xff0c;口诀表的行数和列数自己指定 void print_table(int …

启动docker镜像

1、运行容器 2、当前运行的进程 3、当前位置和启动时间 4、cat/etc/redhat-release查看版本 5.镜像是模版&#xff0c;容器是实例 6.容器中没有命令运 7.容器总是能轻易获取 8.配置yum 9.安装http 10.修改index⽂件 11.httpd -k start 12.访问 13.退出就没有服务了 14…

Redis配置及idea部分操作

配置Redis远程访问 修改访问IP地址 #跳转到redis安装目录 cd /usr/local/redis-6.2.1 #修改redis.conf配置文件 vi redis.conf #注释redis.conf第69行的配置项 #bind 127.0.0.1 设置登录密码 找到下面这一行并去除注释&#xff0c;并添加密…

【微服务】SpringCloud Alibaba 10-14章

10 SpringCloud Alibaba入门简介 10.1 是什么 诞生 2018.10.31&#xff0c;Spring Cloud Alibaba 正式入驻了 Spring Cloud 官方孵化器&#xff0c;并在 Maven 中央库发布了第一个版本。 Spring Cloud Alibaba 介绍 10.2 能干嘛 https://github.com/alibaba/spring-cloud-al…