JUC02

it2025-05-01  6

文章目录

①. 什么是volatile②. JMM的理解③. volatile保证可见性详解④. Volatile不保证原子性⑤. 禁止指令重排(有序性)⑥. 你在哪些地方用到过volatile?

①. 什么是volatile

1>. 什么是volatile?

①. volatile是Java虚拟机提供的 轻量级的同步机制(乞丐版的synchronized)

②. 特征:

保证可见性不保证原子性禁止指令重排

②. JMM的理解

2>. JMM的理解(java内存模型)

①. JMM(Java内存模型Java Memory Model,简称JMM)本身是一种抽象的概念 并不真实存在,它描述的是一组规则或规范通过规范定制了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式.

②. JMM关于同步规定:

线程解锁前,必须把共享变量的值刷新回主内存线程加锁前,必须读取主内存的最新值到自己的工作内存加锁解锁是同一把锁 ③. 原理的理解: 由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方成为栈空间),工作内存是每个线程的私有数据区域,而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝到自己的工作空间,然后对变量进行操作,操作完成再将变量写回主内存,不能直接操作主内存中的变量,各个线程中的工作内存储存着主内存中的变量副本拷贝,因此不同的线程无法访问对方的工作内存,此案成间的通讯(传值) 必须通过主内存来完成,其简要访问过程如下图:

③. volatile保证可见性详解

3>. volatile保证可见性详解

①. 原理的理解: 由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方成为栈空间),工作内存是每个线程的私有数据区域,而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝到自己的工作空间,然后对变量进行操作,操作完成再将变量写回主内存,不能直接操作主内存中的变量,各个线程中的工作内存储存着主内存中的变量副本拷贝,因此不同的线程无法访问对方的工作内存,此案成间的通讯(传值) 必须通过主内存来完成,其简要访问过程如下图:

②. 代码演示

/* 验证volatile的可见性: 1.加入int number=0; number变量之前没有添加volatile关键字修饰,没有可见性 2.添加了volatile,可以解决可见性问题 * */ class Resource{ //volatile int number=0; volatile int number=0; public void addNumber(){ this.number=60; } } public class Volatile_demo1 { public static void main(String[] args) { Resource resource=new Resource(); new Thread(()->{ System.out.println(Thread.currentThread().getName()+"\t coming "); try {TimeUnit.SECONDS.sleep(4);}catch (InterruptedException e){e.printStackTrace();} resource.addNumber(); System.out.println(Thread.currentThread().getName()+"\t update "+resource.number); },"线程A").start(); //如果主线程访问resource.number==0,那么就一直进行循环 while(resource.number==0){ } //如果执行到了这里,证明main现在通过resource.number的值为60 System.out.println(Thread.currentThread().getName()+"\t"+resource.number); } } ③. 对以上代码的详解

④. Volatile不保证原子性

4>. Volatile不保证原子性

①. 代码演示 public class Volatile_demo3 { public static void main(String[] args) { /* System.out.println(Thread.activeCount());*/ AutoResource autoResource=new AutoResource(); //20个线程每个线程循环100次 for (int i = 1; i <=20; i++) { new Thread(()->{ for (int j = 1; j <=100; j++) { autoResource.numberPlusPlus(); autoResource.addAtomicInteger(); } },String.valueOf(i)).start(); } //需要等待上面20个线程都全部计算完后,再用main线程取得的最终的结果值是多少 //默认有两个线程,一个main线程,二是后台gc线程 while(Thread.activeCount()>2){ Thread.yield(); } System.out.println(Thread.currentThread().getName()+"\t int type"+autoResource.number); System.out.println(Thread.currentThread().getName()+"\t AutoInteger type"+autoResource.atomicInteger.get()); } } class AutoResource{ volatile int number=0; public void numberPlusPlus(){ number++; } //使用AutoInteger保证原子性 AtomicInteger atomicInteger=new AtomicInteger(); public void addAtomicInteger(){ atomicInteger.getAndIncrement(); } }

②. 解释不保证证原子性

③. 关于n++源码解析 ④. 解决方案: 1.使用synchronized 2.使用AtomicInteger(推荐) 原理部分在CAS部分有解释 AtomicInteger atomicInteger=new AtomicInteger(); public void addAtomicInteger(){ atomicInteger.getAndIncrement(); }

⑤. 禁止指令重排(有序性)

5>. 禁止指令重排(有序性)

①. 计算机在执行程序时,为了提高性能,编译器和处理器常常会做指令重排,一把分为以下3中 ②. 代码演示重排1 public void mySort(){ int x=11;//语句1 int y=12;//语句2 x=x+5;//语句3 y=x*x;//语句4 } 1234 2134 1324 问题: 请问语句4 可以重排后变成第一条码? 存在数据的依赖性 没办法排到第一个

③. 重排2

④. 加上volatile关键字的时候会禁止指令重排,编程会按照指定的顺序执行

⑥. 你在哪些地方用到过volatile?

6>. 你在哪些地方用到过volatile?

①. 单例模式DCL代码 public class SingletonDemo { private static volatile SingletonDemo instance=null; private SingletonDemo(){ System.out.println(Thread.currentThread().getName()+"\t 构造方法"); } /** * 双重检测机制 * @return */ public static SingletonDemo getInstance(){ if(instance==null){ synchronized (SingletonDemo.class){ if(instance==null){ instance=new SingletonDemo(); } } } return instance; } public static void main(String[] args) { for (int i = 1; i <=10; i++) { new Thread(() ->{ SingletonDemo.getInstance(); },String.valueOf(i)).start(); } } } ②. 出现线程不安全原因的分析如下:

原因: DCL(双端检锁) 机制不一定线程安全,原因是有指令重排的存在,加入volatile可以禁止指令重排原因在于某一个线程在执行到第一次检测,读取到的instance不为null时,instance的引用对象 可能没有完成初始化. instance=new SingletonDem(); 可以分为以下步骤(伪代码) memory=allocate();//1.分配对象内存空间 instance(memory);//2.初始化对象 instance=memory;//3.设置instance的指向刚分配的内存地址,此时instance!=null 步骤2和步骤3不存在数据依赖关系.而且无论重排前还是重排后程序执行的结果在单线程中并没有改变,因此这种重排优化是允许的. memory=allocate();//1.分配对象内存空间 instance=memory;//3.设置instance的指向刚分配的内存地址,此时instance!=null 但对象还没有初始化完. instance(memory);//2.初始化对象 但是指令重排只会保证串行语义的执行一致性(单线程) 并不会关心多线程间的语义一致性 所以当一条线程访问instance不为null时,由于instance实例未必完成初始化,也就造成了线程安全问题

最新回复(0)