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.有序性补充
