【JUC系列第一篇】-Volatile关键字及内存可见性

简介: 版权声明:本文为博主原创文章,未经博主允许不得转载。如需授权请联系微信:878799579 https://blog.
版权声明:本文为博主原创文章,未经博主允许不得转载。如需授权请联系微信:878799579 https://blog.csdn.net/qq_878799579/article/details/85635932

作者:毕来生
微信:878799579

  1. 什么是JUC?

JUC全称 java.util.concurrent 是在并发编程中很常用的实用工具类

2.Volatile关键字

1、如果一个变量被volatile关键字修饰,那么这个变量对所有线程都是可见的。
2、如果某条线程修改了被Volatile修饰的这个变量值,修改后的值对于其他线程来时是立即可见的。
3、并不是经过Volatile修饰过的变量在多线程下就是安全的
4、多线程间可以使用SynchronousQueue或者Exchanger进行数据之间传递

3.内存可见性

内存可见性(Memory Visibility)是指当某个线程正在使用对象状态 而另一个线程在同时修改该状态,需要确保当一个线程修改了对象 状态后,其他线程能够看到发生的状态变化。
可见性错误是指当读操作与写操作在不同的线程中执行时,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有时甚至是根本不可能的事情。
原理同CAS原理相同,不懂的同学可以自行百度,附上一张CAS演示图供大家参考

在这里插入图片描述

4.实战举例

通过线程来修改变量count的值,使用Volatile关键字修饰和不使用Volatile修饰count变量结果对比。

首先我们来看一下通过内部类实现Runnable,变量使用Volatile关键字修饰演示以及结果

package org.bilaisheng.juc;

/**
 * @Author: bilaisheng
 * @Wechat: 878799579
 * @Date: 2019/1/1 16:29
 * @Todo: 通过内部类实现Runnable,变量使用Volatile关键字修饰演示
 * @Version : JDK11 , IDEA2018
 */
public class NoVolatileTest{

	public static void main(String[] args) {
		NoVolatileThread noVolatileThread = new NoVolatileThread();
		new Thread(noVolatileThread).start();

		while (true){
			if(noVolatileThread.isFlag()){
				System.out.println("flag 此时为true !");
				break;
			}
		}
	}
}

class NoVolatileThread implements Runnable{

	private boolean flag = false;
	
	@Override
	public void run() {
		try {
			Thread.sleep(500);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
        
		flag = true;

		System.out.println(Thread.currentThread().getName() + " flag = " + flag);
	}

	public boolean isFlag() {
		return flag;
	}

	public void setFlag(boolean flag) {
		this.flag = flag;
	}
}

运行结果如下图所示:

在这里插入图片描述


接下来我们来看一下通过内部类实现Runnable,变量不使用Volatile关键字修饰演示以及结果

package org.bilaisheng.juc;

/**
 * @Author: bilaisheng
 * @Wechat: 878799579
 * @Date: 2019/1/1 16:53
 * @Todo: 通过内部类实现Runnable,变量使用Volatile关键字修饰演示
 * @Version : JDK11 , IDEA2018
 */
public class VolatileTest{

	public static void main(String[] args) {
		VolatileThread volatileThread = new VolatileThread();
		new Thread(volatileThread).start();

		while (true){
			// if的判断volatile保证当时确实正确,然后线程a可能处于休眠状态,
			// 线程b也判断不存在,b线程就new了一个。
			// 然后a线程wake up,据需执行new volatile获取最新值。
			if(volatileThread.isFlag()){
				System.out.println("flag 此时为true !");
				break;
			}
		}
	}
}

class VolatileThread implements Runnable{

	private volatile boolean flag = false;

	@Override
	public void run() {
		try {
			Thread.sleep(500);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		flag = true;

		System.out.println(Thread.currentThread().getName() + " flag = " + flag);
	}

	public boolean isFlag() {
		return flag;
	}

	public void setFlag(boolean flag) {
		this.flag = flag;
	}
}

运行结果如下图所示:
在这里插入图片描述

通过对比我们发现在通过Volatile修饰和不通过Volatile修饰的变量,输出结果竟然会有些偏差。到底是为什么呢?

我们逐步拆解上面代码执行步骤:

1、针对于不使用Volatile关键字修饰变量:

  • 步骤一:默认flag = false;
  • 步骤二main线程的缓存区域没有刷新 flag的值。所以flag 还是false。故没有输出<flag 此时为true !>
  • 步骤三:子线程输出 Thread-0 flag = true

2、针对于使用Volatile关键字修饰变量:

  • 步骤一:默认flag = false;
  • 步骤二:主线程看到flag是被Volatile关键字修饰的变量。则获取最新的flag变量值,此时flag = true。故输出<flag 此时为true !>
  • 步骤三:子线程输出 Thread-0 flag = true

  1. Volatile的优点

可见性:被Volatile修饰的变量可以马上刷新主内存中的值,保证其他线程在获取时可以获取最新值,所有线程看到该变量的值均相同。

轻量级的synchronized,高并发下保证变量的可见性。


6.Volatile的缺点

1、频繁刷新主内存中变量,可能会造成性能瓶颈

2、不具备操作的原子性,不适合在对该变量的写操作依赖于变量本身自己。例如i++,并不能通过volatile来保证原子性

目录
相关文章
|
2月前
|
存储 缓存 Java
【JavaEE】——内存可见性问题
volatile,一个线程读,一个线程写,两个线程互相读,多个线程多把锁
|
6月前
|
存储 SQL 缓存
揭秘Java并发核心:深度剖析Java内存模型(JMM)与Volatile关键字的魔法底层,让你的多线程应用无懈可击
【8月更文挑战第4天】Java内存模型(JMM)是Java并发的核心,定义了多线程环境中变量的访问规则,确保原子性、可见性和有序性。JMM区分了主内存与工作内存,以提高性能但可能引入可见性问题。Volatile关键字确保变量的可见性和有序性,其作用于读写操作中插入内存屏障,避免缓存一致性问题。例如,在DCL单例模式中使用Volatile确保实例化过程的可见性。Volatile依赖内存屏障和缓存一致性协议,但不保证原子性,需与其他同步机制配合使用以构建安全的并发程序。
79 0
|
4月前
|
缓存 Java 编译器
【多线程-从零开始-伍】volatile关键字和内存可见性问题
【多线程-从零开始-伍】volatile关键字和内存可见性问题
63 0
|
7月前
|
存储 缓存 Java
(一) 玩命死磕Java内存模型(JMM)与 Volatile关键字底层原理
文章的阐述思路为:先阐述`JVM`内存模型、硬件与`OS`(操作系统)内存区域架构、`Java`多线程原理以及`Java`内存模型`JMM`之间的关联关系后,再对`Java`内存模型进行进一步剖析,毕竟许多小伙伴很容易将`Java`内存模型(`JMM`)和`JVM`内存模型的概念相互混淆,本文的目的就是帮助各位彻底理解`JMM`内存模型。
138 0
|
3月前
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
544 1
|
2月前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
3月前
|
Java
JVM内存参数
-Xmx[]:堆空间最大内存 -Xms[]:堆空间最小内存,一般设置成跟堆空间最大内存一样的 -Xmn[]:新生代的最大内存 -xx[use 垃圾回收器名称]:指定垃圾回收器 -xss:设置单个线程栈大小 一般设堆空间为最大可用物理地址的百分之80
|
3月前
|
Java
JVM运行时数据区(内存结构)
1)虚拟机栈:每次调用方法都会在虚拟机栈中产生一个栈帧,每个栈帧中都有方法的参数、局部变量、方法出口等信息,方法执行完毕后释放栈帧 (2)本地方法栈:为native修饰的本地方法提供的空间,在HotSpot中与虚拟机合二为一 (3)程序计数器:保存指令执行的地址,方便线程切回后能继续执行代码
34 3
|
3月前
|
存储 缓存 监控
Elasticsearch集群JVM调优堆外内存
Elasticsearch集群JVM调优堆外内存
67 1
|
3月前
|
Arthas 监控 Java
JVM进阶调优系列(9)大厂面试官:内存溢出几种?能否现场演示一下?| 面试就那点事
本文介绍了JVM内存溢出(OOM)的四种类型:堆内存、栈内存、元数据区和直接内存溢出。每种类型通过示例代码演示了如何触发OOM,并分析了其原因。文章还提供了如何使用JVM命令工具(如jmap、jhat、GCeasy、Arthas等)分析和定位内存溢出问题的方法。最后,强调了合理设置JVM参数和及时回收内存的重要性。