【JVM】谈谈你对volatile的理解

大多数的面试中,都会问到这个问题,基本成为了Java程序员必备的知识了。
本文带你一次性理清答题思路及扩展

1. JMM(Java内存模型)

1.1 定义及规定

1.1.1 定义

JMM 本身是一种抽象的概念并不是真实存在,它描述的是一组规定或则规范,通过这组规范定义了程序中的访问方式。

1.1.2 规定

  • 线程解锁前,必须把共享变量的值刷新回主内存
  • 线程加锁前,必须读取主内存的最新值到自己的工作内存
  • 加锁解锁是同一把锁

1.2 三大特性

1.2.1 可见性

线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝的自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存

一旦主内存中的变量发生改变,必须让所有的线程都能看见,第一时间通知,这就是可见性

1.2.2 原子性

1.2.3 有序性

2. volatile

1.1 volatile是什么?

volatile 是 Java 虚拟机提供的轻量级的同步机制

1.2 三大特性

他的特点和我们的JMM也有点类似,volatile 不保证原子性,保证可见性和禁止指令重排

1.2.1 保证可见性

线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝的自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存

如果不加 volatile 关键字,则主线程会进入死循环,加 volatile 则主线程能够退出,说明加了 volatile 关键字变量,当有一个线程修改了值,会马上被另一个线程感知到,当前值作废,从新从主内存中获取值。对其他线程可见,这就叫可见性。

class Mydata {
	volatile int number = 0;

	public void add() {
		this.number = 60;
	}
}
/*
 * 1 验证volatile的可见性 
 *  1.1 假如 int number = 0;
 *      number变量之前根本没有添加volatile关键字修饰
 */

public class SeeOkValatile {
	public static void main(String[] args) {
		Mydata mydata = new Mydata();
		new Thread(() -> {
			System.out.println(Thread.currentThread().getName() + "\t come in");
			try {
				TimeUnit.SECONDS.sleep(3);
			} catch (Exception e) {
				e.getStackTrace();
			}
			mydata.add();
			System.out.println(Thread.currentThread().getName() + "\t update number " + mydata.number);
		}, "aaa").start();

		// 傻乎乎的在这转着
		while (mydata.number == 0) {

		}

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

1.2.2 不保证原子性

1.2.2.1 什么是原子性?

不可分割,完整性,也就是某个线程正在做某个具体业务时,中间不可以被加塞或者被分割,需要整体完整,要么同时成功,要么同时失败

class MydataDemo {
	volatile int number = 0;

	public void add() {
		this.number = 60;
	}

	// 请注意:此时number前面是加了volatile修饰的
	public void addPlus() {
		number++;
	}

	AtomicInteger atomicInteger = new AtomicInteger();

	public void addAtomic() {
		atomicInteger.getAndIncrement();
	}
}
/*
 * 验证volatile的原子性
 * 
 * 2.3 why 对于number++这个操作 分为3步: 
 * 			1. A = number 线程从主内存中拿到该值 
 * 			2. B = A + 1  在自己的线程内存中加一 
 * 			3. number = B 最后存入到主内存中
 * 		由于多线程的并发性,可能导致值的覆盖
 * 
 * 2.4 怎么解决?
 * 		加synchronizedvoid
 * 		使用AtomicInteger,原子类的数据
 */

public class SeeOkValatile2 {
	public static void main(String[] args) {
		MydataDemo mydataDemo = new MydataDemo();

		for (int i = 0; i < 20; i++) {
			new Thread(() -> {
				for (int j = 0; j < 1000; j++) {
					mydataDemo.addPlus();
					mydataDemo.addAtomic();
				}
			}, String.valueOf(i)).start();
		}

		while (Thread.activeCount() > 2) {
			// 礼让线程
			Thread.yield();
		}
		System.out.println(Thread.currentThread().getName() + "number is->" + mydataDemo.number);
		System.out.println(Thread.currentThread().getName() + "number is->" + mydataDemo.atomicInteger);
	}
}

关于count++的知识:

在这里插入图片描述

1.2.3 禁止指令重排

计算机在执行程序时,为了提高性能,编译器个处理器常常会对指令做重排,一般分为以下 3 种

  • 编译器优化的重排
  • 指令并行的重排
  • 内存系统的重排

在这里插入图片描述

  1. 单线程环境里面确保程序最终执行结果和代码顺序执行的结果一致。
  2. 处理器在进行重排序时必须要考虑指令之间的数据依颍悝
  3. 多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的,结果无法预测
class ReOrderDemo {
    int a = 0;
    boolean flag = false;
 
    public void write() {
        a = 1;                   //1
        flag = true;             //2
    }
     
    public void read() {
        if (flag) {                //3
            int i =  a * a;        //4
    		
        }
    }
}
// 单线程:1234
// 多线程:会出现混乱的错误,指令重排
1.2.3.1 禁止指令重排的实现

volatile 实现禁止指令重排序的优化,从而避免了多线程环境下程序出现乱序的现象

内存屏障(Memory Barrier)又称内存栅栏,是一个 CPU 指令,他的作用有两个:

  • 保证特定操作的执行顺序
  • 保证某些变量的内存可见性(利用该特性实现 volatile 的内存可见性)

由于编译器个处理器都能执行指令重排序优化,如果在指令间插入一条 Memory Barrier 则会告诉编译器和 CPU,不管什么指令都不能个这条 Memory Barrier 指令重排序,也就是说通过插入内存屏障禁止在内存屏障前后执行重排序优化。内存屏障另一个作用是强制刷出各种 CPU 缓存数据,因此任何 CPU 上的线程都能读取到这些数据的最新版本。
在这里插入图片描述

1.3 线程安全性获得保证

工作内存与主内存同步延迟现象导致可见性问题

  • 可以使用 synchronzied 或 volatile 关键字解决,它们可以使用一个线程修改后的变量立即对其他线程可见

对于指令重排导致可见性问题和有序性问题

  • 可以利用 volatile 关键字解决,因为 volatile 的另一个作用就是禁止指令重排序优化

1.4 你在哪里用到过volatile

单例模式下的DCL

正常情况

  1. 分配对象内存空间
  2. 初始化对象
  3. 设置instance指向刚分配的内存地址,此时instance != null

指令重排

  1. 分配对象内存空间
  2. 设置instance指向刚分配的内存地址,此时instance != null 我们的对象还没有初始化
  3. 初始化对象
public class SingletonDemo {
	private static volatile SingletonDemo instance = null;

	private SingletonDemo() {
		System.out.println(Thread.currentThread().getName() + "我是构造方法");
	}

	// DCL Double Check Lock双端检锁机制
	public static synchronized SingletonDemo getInstance() {
		if (instance == null) {
			synchronized (SingletonDemo.class) {
				if (instance == null) {
					instance = new SingletonDemo();
				}
			}
		}
		return instance;
	}

	public static void main(String[] args) {
		// 单线程(main线程的操作动作----)
		// System.out.println(SingletonDemo.getInstance() ==
		// SingletonDemo.getInstance());
		// System.out.println(SingletonDemo.getInstance() ==
		// SingletonDemo.getInstance());
		// System.out.println(SingletonDemo.getInstance() ==
		// SingletonDemo.getInstance());
		for (int i = 0; i <= 10; i++) {
			new Thread(() -> {
				SingletonDemo.getInstance();
			}, String.valueOf(i)).start();
		}
	}
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
### 回答1: 我对Java的理解是,它是一种面向对象的编程语言,它使用类和对象来组织代码,有助于创建大型、可扩展的应用程序。Java可以让开发人员高效地开发出可移植性强、运行速度快的程序,同时也拥有一个强大的库,可以满足不同类型应用程序的需求。 ### 回答2: Java是一种面向对象的高级编程语言,我对它的理解是它的跨平台性和广泛应用。 首先,Java的跨平台性是它最为出众的特点之一。它的编译器将源代码编译成称为字节码的平台中立的中间代码,然后由Java虚拟机(JVM)解释执行。这种设计使得Java程序可以在任何支持Java虚拟机的操作系统上运行,不需要针对不同平台进行重新编写或重新编译。这为开发者提供了极大的便利性,使得Java成为互联网和企业级应用开发的首选语言之一。 其次,Java的广泛应用也是我对它的理解。Java已经成为了许多领域的事实标准,特别是在企业领域。无论是开发网站和Web应用,还是构建大规模的分布式系统和数据库,Java都能胜任,并且有许多优秀的框架和工具可供选择。此外,Java还被广泛应用于Android移动应用开发,使得它成为全球最流行的移动应用开发语言之一。 总的来说,我认为Java是一门极具灵活性和可扩展性的编程语言,具有良好的跨平台性和广泛的应用领域。它不仅适用于不同规模的项目开发,而且还有一个庞大的开发者社区,提供了许多有价值的资源和支持。因此,我认为对于想要进入软件开发行业或者在企业级应用开发领域发展的人来说,了解和掌握Java是非常有必要的。 ### 回答3: 对于我来说,Java是一种高级的、通用的面向对象的程序设计语言。它可以运行在各种平台上,因此具有很强的跨平台性。Java的语法简洁清晰,易于学习和理解,而且具有更高的可读性,使得开发者能够更加方便地编写和维护代码。 Java是一个极其强大的语言,拥有丰富的类库和工具,可以满足各种应用程序的开发需求。Java的标准库提供了大量的类和方法,涵盖了从基本的数据结构到网络编程、图形用户界面等各个方面。同时,Java还有许多第三方库和框架,可以帮助开发者更加高效地进行开发工作。 Java还具有良好的跨平台性,这意味着开发者可以编写一次代码,就可以在不同的操作系统和硬件平台上运行。这是因为Java源代码被编译成一种称为字节码的中间格式,然后在Java虚拟机(JVM)上运行。JVM充当了一个虚拟计算机的角色,它可以将字节码翻译成特定平台的机器码,从而实现跨平台的能力。 另外,Java还具有良好的安全性和稳定性。Java的设计目标之一是提供一种可以在开放网络环境下运行的安全语言,因此在Java中,有许多内置的安全机制来确保程序的运行安全。同时,Java也具有垃圾回收机制,可以自动管理内存,减少了内存管理的负担,提高了程序的稳定性和可靠性。 总之,我认为Java是一种非常优秀的编程语言,它不仅可以满足各种需求,而且具有良好的可读性、跨平台性、安全性和稳定性。对我来说,学习和使用Java是非常有意义的。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱敲代码的小黄

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值