首页 > 编程语言 >Java关键字-volatile详解

Java关键字-volatile详解

时间:2023-03-22 10:36:21浏览次数:49  
标签:count 线程 Java num 详解 static private volatile

点击  ​​Mr.绵羊的知识星球​​ 解锁更多优质文章。

一、介绍

1. 简介

    volatile是java关键字,同时也是JVM 提供的轻量级的同步机制

Java关键字-volatile详解_volatile

2. 特性

    你需要先了解一下Java内存模型Java Memory Model (JMM详解,写完上传),而volatile关键字拥有以下特性(不保证原子性),也就是说他无法保证线程安全

    (1) 保证可见性:git地址

    可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。也就是一个线程修改的结果。另一个线程马上就能看到。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* VolatileVisibility
* 保证可见性验证
*
* @author wxy
* @date 2023-02-23
*/
public class VolatileVisibility extends AbstractVolatile {
private static final Logger LOGGER = LoggerFactory.getLogger(VolatileVisibility.class);

public static void main(String[] args) {
// 验证可见性
visibility();
}

public static void visibility() {
Count count = new Count();
// 将num初始值设置为0
count.num = 0;

new Thread(() -> {
// 线程1休眠3秒
sleep(3000);
// 休眠3秒后, 将num + 1
count.add(1);
}, "thread 1").start();

new Thread(() -> {
// 循环
while (true) {
if (count.getNum() == 1) {
// 如果线程2获取到线程1修改完成的num, 则打印后跳出while循环
LOGGER.info("current num value: {}", count.getNum());
break;
}
}
}, "thread 2").start();

// main线程等待5秒后结束, 你也可以使用其他的方式等待上面线程执行完毕。例如CountdownLatch...之后文章都会介绍
sleep(5000);
LOGGER.info("thread main end");
}
}

    a. 未加volatile关键字

    上述代码中线程2先获取num=0的值,由于不满足if (count.getNum() == 1)的条件,所以无法跳出循环。三秒钟之后线程1将num的值修改为1,但是线程2无法感知将一直处于循环状态,导致程序无法停止

Java关键字-volatile详解_不保证原子性_02

    b. 加了volatile关键字

    上述代码中线程2先获取num=0的值,由于不满足iif (count.getNum() == 1)的条件,所以处于循环状态。三秒钟之后线程1将num的值修改为1,因为num被volatile修饰,具有可见性,num=1的修改对线程2可见,满足条件,所以线程2跳出循环,程序停止


Java关键字-volatile详解_可见性_03

    (2) 不保证原子性:git地址

    无法保证变量同时只能被一个线程修改,也就是无法保证线程安全。(代码可以不看,就是多个线程操作同一个共享变量)

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* VolatileNonAtomicity
* 不保证原子性验证
*
* @author wxy
* @date 2023-02-23
*/
public class VolatileNonAtomicity extends AbstractVolatile {
private static final Logger LOGGER = LoggerFactory.getLogger(VolatileNonAtomicity.class);

public static void main(String[] args) {
// 验证不保证原子性
nonAtomicity();
}

public static void nonAtomicity() {
Count count = new Count();

for (int num = 0; num < 10; num++) {
// 创建10个线程
new Thread(() -> {
for (int index = 0; index < 20; index++) {
// 循环十次, 每次循环+1
count.add(1);
// 休眠1毫秒(我电脑性能太好, 不休眠不好看结果)
sleep(1);
}
}).start();
}
// main线程休眠3秒等待上面执行完毕, 你也可以使用其他的方式等待上面线程执行完毕。例如CountdownLatch...之后文章都会介绍
sleep(3000);
LOGGER.info("current num value: {}", count.getNum());
}
}

    循环了200次,实际结果应该是200,但是最后结果是175


Java关键字-volatile详解_禁止指令重排_04

    (3) 禁止指令重排序:git地址

    计算机在执行程序时,为了提高性能,编译器和处理器通常都会对指令做重排序。这会产生什么样的问题呢?

    多线程环境下,由于编译器优化重排,导致不同线程中使用的变量不能保证一致性,无法确定执行的结果。volatile实现禁止指令重排优化,从而避免多线程环境下程序出现乱序执行的现象

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* VolatileInstructReset
* 禁止指令重排序验证
*
* @author wxy
* @date 2023-02-23
*/
public class VolatileInstructReset extends AbstractVolatile {
private static final Logger LOGGER = LoggerFactory.getLogger(VolatileInstructReset.class);

/* 未使用volatile关键字前: n次循环后a,b同时为0 */
private static int a;
private static int b;
private static int x;
private static int y;

/* 增加volatile关键字后: a,b都符合不同时为0的预期 */
/*private static volatile int a;
private static volatile int b;
private static volatile int x;
private static volatile int y;*/

public static void main(String[] args) throws InterruptedException {
// 指令重排
instructReset();
}

public static void instructReset() throws InterruptedException {
long startTime = System.currentTimeMillis();

for (; ; ) {
/* 定义四个数组, 每个里面仅有一个元素并且初始值为0 */
a = 0;
b = 0;
x = 0;
y = 0;

Thread thread1 = new Thread(() -> {
// 将x赋值为1, 执行后x=1
x = 1;
// 将a赋值为y, 如果线程1先执行那么a=0, 如果线程2先执行那么a=1
a = y;
}, "线程1");

Thread thread2 = new Thread(() -> {
// 将y赋值为1, 执行后y=1
y = 1;
// 将b赋值为x, 如果线程1先执行那么b=1, 如果线程2先执行那么b=0
b = x;
}, "线程2");
thread1.start();
thread2.start();
thread1.join();
thread2.join();

if (a == 0 && b == 0) {
// 理论上不管是先执行线程1还是先执行线程2, a[0] & b[0]不可能同时为0
// 实际上确有同时为0的可能
LOGGER.info("a: {}, b: {}", a, b);
break;
}
}

LOGGER.info("execute end, cost time: {}", System.currentTimeMillis() - startTime);
}
}

    a. 未加volatile关键字

    按照代码中的逻辑,a和b不可能同时为0。但是在多线程下,a和b竟然同时为0,说明在多线程环境下,指令重排使得变量执行或赋值顺序被修改


Java关键字-volatile详解_禁止指令重排_05

    b. 加了volatile关键字

    测试了两次,每次大概四十分钟(手动停了),a和b都不同时为0,说明多线程环境下,使用volatile关键字修饰后,执行或赋值顺序没有被修改


Java关键字-volatile详解_可见性_06

二、实际应用

1. 案例一

    ​​Java单例模式(懒汉式)​

标签:count,线程,Java,num,详解,static,private,volatile
From: https://blog.51cto.com/u_15898747/6142187

相关文章

  • java通过短信发送验证码
    /**如uid是:test,登录密码是:123123pwd=md5(123123test),即pwd=b9887c5ebb23ebb294acab183ecf0769线生成地址:http://www.sms.cn/password*/......
  • java代码中获取classpath路径
    javaweb工程中,有时候需要自己手动的去读取classpath下面的配置文件,这里总结一点读取classpath路径的方法,分享一下。方法一:Stringpath=Test.class.getResource("/").......
  • JavaScript 浏览器打断点 Debugging
      1.下面我们看一个例子,这里是5+1=6,但是结果是51   console.log效率会比较低  2.debug可以暂停代码,和查看变量,按F12,看Sources  3.展开鼠标类型,......
  • Java Spring使用EventSource进行服务端推送
    Java代码:@ResponseBody@RequestMapping(value="/getDate",produces="text/event-stream;charset=UTF-8")publicvoidgetDate(HttpServletResponse......
  • Java中实现按字符串位数在前面补0
    场景现在要将4在前面补位为0004。实现1.首先将方法包装为工具类StringwaterCode=CommonUtils.fillZeroBeforeString(String.valueOf(max),4);其中max为最大值即4,第二个参......
  • Java远程唤醒mac
    关机的,大家都会……想比我就不说了吧!通过IP,使用shutdown命令,外加参数……附上地址,OK。packagecom.iaiai.test;importjava.net.DatagramPacket;i......
  • Java并发夺命50问
    本文已经收录到Github仓库,该仓库包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校......
  • Java面试-基础篇之3
    3.Java中的深拷贝和浅拷贝的区别在解释深浅拷贝前先了解一个数据在Java语言中的类别和存储形式。我们都知道在Java中,数据都叫做对象,拷贝也是针对对象来做的。在Java中存......
  • JavaWeb使用Kaptcha生成验证码(jar版)
    使用方式 Web项目导入jar包kaptcha-2.3.0.jar1.1:自定义jar包位置 自定义的jar包位置需要点击AddasLibrary才能使用,一般选......
  • zipfile模块详解
    模块包含的类'ZipExtFile','ZipFile','ZipInfo'模块包含的方法类方法zipfile.is_zipfile(filename)  判断文件是否是个有效的zipfileZipFile属性filelistZip......