volatile 是JAVA虚拟机提供的轻量级的同步机制,有三大特性
1、保证可见性 2、不保证原子性 3、禁止指令重排
JMM JAVA内存模型本身是一种抽象的概念并不真实存在 它描述的是一组规则或规范,提供这组规范定义了程序中各个变量(包括实例变量、静态变量)的访问方式。
JMM关于同步的规定:
线程解锁前必须将工作内存的变量刷新到主内存
线程加锁前必须将主内存的数据加载到线程的工作内存中
加锁解锁必须是同一把锁
主内存是共享内存区域可以简单理解为堆,工作内存是线程独享的空间可以简单理解为栈
各个线程不能直接操作主内存的变量,只能通过操作主内存的变量副本来实现线程间的通信。
当某个线程在自己的工作内存中修改了某个主内存中的变量副本,并将该数据在主内存中进行了刷新,就会第一时间通知到其他线程该变量的值已经改变并更新该变量的值,这就是可见性,volatile是保证可见性的
当 age变量没有被volatile关键字修饰的时候,AAA线程中修改的age的值,已经在主内存里面更新了,但是没有通知到main线程,所以main线程里面age的值还是10,会一直在while循环里面。
使用volatile修饰age变量后,当主内存里面的age变量更新后main线程里面age的值也会第一时间被更新,所以不会一直在while循环里面文章来源:https://www.toymoban.com/news/detail-647294.html
package com.juc.demo;
import java.util.concurrent.TimeUnit;
/**
* @ClassName: VolitalTest
* @Description:
* @Author: 01412126
* @Date: 2023/8/10 19:27
*/
class Dog{
volatile int age = 10;
// int age = 10;
public void changeAgeTo90(){
this.age = 90;
}
}
class VolitalTest {
public static void main(String[] args) {
Dog dog = new Dog();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " come in");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
dog.changeAgeTo90();
System.out.println(Thread.currentThread().getName() + " update dog age " + dog.age);
},"AAA").start();
while (dog.age == 10) {
//System.out.println(" main thread come in and find dog age" );
}
System.out.println(Thread.currentThread().getName() + " finally come in and find dog age " + dog.age);
}
}
volatile 不保证原子性
num++ 其实有三个步骤 1、主内存取到num的数值放到工作内存 2、对num进行加一 3、将num+1的数值写回主内存 这样即使加了volatile也不能保证原子性,为什么数值总是小于 20000,出现了丢失值的情况 举例: 两个线程同时从主内存读回各自的工作内存 num = 1 1、当第一个线程将num加一num = 2的时候,准备将num写回主内存时 cpu切换到第 二 个线程 2、第二个线程直接完成了num加1,并将num=2写回主内存,由于num加了volatile关键字,正准备将num=2通知给各个线程的时候,cpu 切换到了第一个线程 3、第一个线程又将num=1写入主内存,并成功通知了各个线程,这时主内存和各个内存里面num=1 可以在方法前面加synchronized 也可以使用AtomicInteger类
package com.juc.demo;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @ClassName: VolitalTest01
* @Description:
* @Author: 01412126
* @Date: 2023/8/14 19:18
*/
/*
* volatile 不保证原子性
*
* num++ 其实有三个步骤
* 1、主内存取到num的数值放到工作内存
* 2、对num进行加一
* 3、将num+1的数值写回主内存
*
* 这样即使加了volatile也不能保证原子性
*为什么数值总是小于 20000,出现了丢失值的情况
*
* 举例:
* 两个线程同时从主内存读回各自的工作内存 num = 1
* 当第一个线程将num加一num = 2的时候,准备将num写回主内存时 cpu切换到第二个线程
* 第二个线程直接完成了num加1,并将num=2写回主内存,由于num加了volatile关键字,正准备将num=2通知给各个线程的时候,cpu 切换到了第一个线程
* 第一个线程又将num=1写入主内存,并成功通知了各个线程,这时主内存和各个内存里面num=1
*
* 可以在方法前面加synchronized
* 也可以使用AtomicInteger类
* * */
class Cat{
private int catNum = 0;
// public synchronized void catNumAdd(){
// this.catNum++;
// }
public void catNumAdd(){
this.catNum++;
}
public int getCatNum() {
return catNum;
}
}
public class VolitalTest01 {
private static volatile int num = 0;
private static AtomicInteger a = new AtomicInteger();
public static void main(String[] args) {
Cat cat = new Cat();
for (int i = 0; i < 10; i++){
new Thread(() ->{
for(int j = 0; j < 1000; j++){
// num++;
// a.incrementAndGet();
cat.catNumAdd();
}
},"Thraed " + i).start();
}
while (Thread.activeCount() > 2){
Thread.yield();
}
// System.out.println("num的数值" + a.get());
// System.out.println("a的数值" + num);
System.out.println("a的数值" + cat.getCatNum());
}
}
3、禁止指令重排文章来源地址https://www.toymoban.com/news/detail-647294.html
package com.juc.demo;
/**
* @ClassName: SingletonDemo
* @Description:
* @Author: 01412126
* @Date: 2023/8/18 19:28
*/
public class SingletonDemo {
// private static SingletonDemo instance = null;
//加上volatile 禁止指令重排
private static volatile SingletonDemo instance = null;
private SingletonDemo() {
System.out.println(Thread.currentThread().getName() + " 执行了构造方法");
}
//整个方法加synchronized 太重了
// public static synchronized SingletonDemo getInstance() {
// if(instance == null){
// instance = new SingletonDemo();
// }
// return instance;
// }
//双端检锁机制
public static synchronized SingletonDemo getInstance() {
if(instance == null){
//使用synchronized + 代码块 加锁
synchronized (SingletonDemo.class) {
if(instance == null){
/*
* 1、为SingletonDemo 对象分配内存空间
* 2、初始化 SingletonDemo 对象
* 3、instance 指向 SingletonDemo对象
*
* 正常来说是这三步,但是有可能会指令重排,将2和3倒过来,这样的话
* 就有可能出现 判断 instance != null,直接返回SingletonDemo对象的时候,SingletonDemo对象还没有进行初始化
* ,返回了一个null。加上volatile 禁止指令重排,可以避免这种情况 private static volatile SingletonDemo instance = null;
* */
instance = new SingletonDemo();
}
}
}
return instance;
}
public static void main(String[] args) {
// 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();
}
}
}
到了这里,关于JAVA volatile 关键字的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!