volatile相关
{
"title":"volatile相关",
"date":2020-05-08T21:21:49+08:00,
"draft":true,
"tags":["volatile、JMM内存模型"],
"comments":true,
"share":true
}
volatile关键字介绍
volatile是Java虚拟机提供的轻量级的同步机制。当一个变量(类的成员变量、类的静态成员变量)被volatile修饰之后 ,那么就具备以下三个特点: 保证可见性,不保证i++原子性(和synchronized的不同点),禁止指令重排(保证了有序性)。
在并发编程中,我们通常会遇到以下三个术语:可见性,原子性,有序性 。
原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
可见性:当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
有序性:即程序执行的顺序按照代码的先后顺序执行。
JMM内存模型
JMM(Java内存模型;Java Memory Model;简称JMM)本身是一种抽象的概念 并不真实存在,它描述的是一组规则或规范通过规范定制了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。
JMM的特点:可见性、原子性、有序性。
由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方成为栈空间)。工作内存是每个线程的私有数据区域,而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行。首先要将变量从主内存拷贝到自己的工作空间,然后对变量进行操作,操作完成再将变量写回主内存,不能直接操作主内存中的变量,各个线程中的工作内存储存着主内存中的变量副本拷贝,因此不同的线程无法访问对方的工作内存,此案成间的通讯(传值) 必须通过主内存来完成。
代码验证
3.1 验证volatile的可见性
package com.xpf.Interview.juc.volatileDemo; import java.util.concurrent.TimeUnit; /** * @Author: Xia * @Date: 2020/4/22 14:00 * @Email:x2358114512@163.com * volatile可见性的代码演示 */ class MyData1{ int num1=0; volatile int num2=0; public void addTo1(){ this.num1 = 1; } public void addTo2(){ this.num2 = 2; } } public class VolatileDemo01 { public static void main(String[] args) { notVisibility(); visibility(); } private static void notVisibility() { //num1变量没有用volatile修饰,其不保证可见性 MyData1 myData = new MyData1(); new Thread(() -> { System.out.println(Thread.currentThread().getName()+"\t come into"); // 模拟num更改操作耗时2m,并保证其他线程读取了num2变量 try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } myData.addTo1(); System.out.println(Thread.currentThread().getName()+"\t Update data value:"+myData.num1); },"a").start(); new Thread(() -> { while(myData.num1 == 0){ // 程序会一直卡在while循环那里,因为线程之间不可见,"b"线程永远拿不到"a"线程更改后的值 } System.out.println(Thread.currentThread().getName()+"\t mission is over"); },"b").start(); } private static void visibility() {//num2变量用volatile修饰,其保证可见性 MyData1 myData = new MyData1(); new Thread(() -> { System.out.println(Thread.currentThread().getName()+"\t come into"); // 模拟num更改操作耗时2m,并保证其他线程读取了num2变量 try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } myData.addTo2(); System.out.println(Thread.currentThread().getName()+"\t Update data value:"+myData.num2); },"a").start(); new Thread(() -> { //num2变量用volatile修饰,"b"线程拿到了"a"线程更改后的值,此时num2=2,且不满足while循环里的条件,则跳出while循环。 while(myData.num2 == 0){ } System.out.println(Thread.currentThread().getName()+"\t mission is over"); },"b").start(); } }
结果现象:
notVisibility()函数在执行过程中不能跳出while()循环,程序不能执行完成;
visibility()函数在执行过程中跳出while()循环,程序顺利执行完成;
结果分析:
num1变量没有用volatile修饰,其不保证可见性,程序会一直卡在while循环那里,因为线程之间不可见,"b"线程永远拿不到"a"线程更改后的值。
num2变量用volatile修饰,其保证可见性,"b"线程拿到了"a"线程更改后的值,此时num2=2,且不满足while循环里的条件,则跳出while循环。
3.2 验证volatile不保证i++的原子性
package com.xpf.Interview.juc.volatileDemo; import java.util.concurrent.atomic.AtomicInteger; /** * @Author: Xia * @Date: 2020/4/22 14:00 * @Email:x2358114512@163.com * Volatile不保证i++原子性的代码演示 */ class MyData2{ volatile int num1=0; volatile int num2=0; AtomicInteger atomicInteger = new AtomicInteger(0); //定义的原子整形 public void numAdd1(){ //注意:num1变量前面加了volatile关键字,即不保证原子性 num1++; } public synchronized void numAdd2(){ //注意:num2变量前面加了volatile关键字,即不保证原子性,但是该方法上添加了synchronized来保证原子性 num2++; } public void numAddAtom(){ atomicInteger.getAndIncrement(); //原子整形保证i++的原子性 } } public class VolatileDemo02 { public static void main(String[] args) { MyData2 myData = new MyData2(); for (int i = 0; i < 100; i++) { //开启100个线程 new Thread(() -> { for (int j = 0; j < 50000; j++) { myData.numAdd1(); myData.numAdd2(); myData.numAddAtom(); } },String.valueOf(i)).start(); } //需要以上的线程完成计算,再使用main线程看看最后num的结果是不是正确的 while(Thread.activeCount() > 2){ //java虚拟机默认后台有两个线程:main线程和GC垃圾回收线程。当线程数大于2,说明上述计算还没有结束 Thread.yield(); } System.out.println(Thread.currentThread().getName() + "\t int final num1 value:"+ myData.num1); System.out.println(Thread.currentThread().getName() + "\t int final num2 value:"+ myData.num2); System.out.println(Thread.currentThread().getName() + "\t AtomicInteger final num value:"+ myData.atomicInteger); } }
结果现象:
main int final num1 value:4999714
main int final num2 value:5000000
main AtomicInteger final num value:5000000
结果分析:数值小于100*50000,出现丢失写值的情况。
如何解决volatile的不保证原子性,在numAdd2()方法上加了synchronized 就保证了num2的原子性,
或者使用 AtomicInteger(java.util.concurrent.atomic.AtomicInteger)。底层原理见CAS相关。
原子性指的是不可分割性,完整性。也即某个线程正在做某个业务时,中间不可加塞或分割,需要整体完整。要么同时成功,要么同时失败。
4.volatile关键字的应用
4.1 单例模式代码
package com.xpf.Interview.juc.volatileDemo; /** * @Author: Xia * @Date: 2020/4/22 15:41 * @Email:x2358114512@163.com * 单线程版本的单例模式 * 单例模式:只会new一次对象,也就是只会执行一次构造方法。 */ public class SingletonDemo03 { private static SingletonDemo03 instance = null; private SingletonDemo03(){ System.out.println(Thread.currentThread().getName()+"\t 这是构造方法SingletonDemo03()"); } public static SingletonDemo03 getInstance(){ if(instance == null){ instance = new SingletonDemo03(); } return instance; } public static void main(String[] args) { SingThreadSingleton(); MoreThreadSingleton(); } private static void MoreThreadSingleton() { //多线程下,单例模式出现了多个构造方法,则此时的单例模式有问题。 //解决方法:在getInstance()方法上加上synchronized(重锁不推荐)或者DCL+volatile for (int i = 0; i <10000; i++) { new Thread(() -> { SingletonDemo03.getInstance(); },String.valueOf(i)).start(); } } private static void SingThreadSingleton() { //单线程下,单例模式正确 System.out.println(SingletonDemo03.getInstance() == SingletonDemo03.getInstance()); System.out.println(SingletonDemo03.getInstance() == SingletonDemo03.getInstance()); System.out.println(SingletonDemo03.getInstance() == SingletonDemo03.getInstance()); } }
4.2 DCL+volatile解决多线程下的单例模式
package com.xpf.Interview.juc.volatileDemo; /** * @Author: Xia * @Date: 2020/4/22 15:46 * @Email:x2358114512@163.com */ public class SingletonDemo04 { private volatile static SingletonDemo04 instance = null; private SingletonDemo04(){ System.out.println(Thread.currentThread().getName()+"\t 这是构造方法SingletonDemo03()"); } public static SingletonDemo04 getInstance(){ if(instance == null){ //DCL synchronized (SingletonDemo04.class){ if(instance==null){ instance=new SingletonDemo04(); } } } return instance; } public static void main(String[] args) { for (int i = 0; i <10000; i++) { new Thread(() -> { SingletonDemo04.getInstance(); },String.valueOf(i)).start(); } } }
5.有序性补充