volatile相关

{
"title":"volatile相关",
"date":2020-05-08T21:21:49+08:00,
"draft":true,
"tags":["volatile、JMM内存模型"],
"comments":true,
"share":true
}

  1. volatile关键字介绍

    volatile是Java虚拟机提供的轻量级的同步机制。当一个变量(类的成员变量、类的静态成员变量)被volatile修饰之后 ,那么就具备以下三个特点: 保证可见性,不保证i++原子性(和synchronized的不同点),禁止指令重排(保证了有序性)。

    在并发编程中,我们通常会遇到以下三个术语:可见性,原子性,有序性 。

    原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。

    可见性:当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

    有序性:即程序执行的顺序按照代码的先后顺序执行。

  2. JMM内存模型

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

    JMM的特点:可见性、原子性、有序性。

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

  3. 代码验证

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

全部评论

相关推荐

11-04 21:17
江南大学 Java
穷哥们想卷进大厂:肯定会问技术呀,面试你的可能是别人
点赞 评论 收藏
分享
不愿透露姓名的神秘牛友
10-12 10:48
已编辑
秋招之苟:邻居家老哥19届双2硕大厂开发offer拿遍了,前几天向他请教秋招,他给我看他当年的简历,0实习实验室项目技术栈跟开发基本不沾边😂,我跟他说这个放在现在中厂简历都过不了
点赞 评论 收藏
分享
评论
点赞
收藏
分享
牛客网
牛客企业服务