volatile学习(可见性,不保证原子性,禁止指令重排(双端检索机制))

volatile是java虚拟机提供的轻量级的同步机制:

1.保证可见性:线程之间可见性(及时通知)
2.不保证原子性
3.禁止指令重排

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

一、volatile的可见性demo验证

一、没有加volatile

package Volatile;

import java.util.concurrent.TimeUnit;

/**
 * volatile的可见性
 * demo
 */
class MyData{
    int number = 0;
    public void addTO60(){
        this.number = 60;
    }
}

public class demo {
    public static void main(String[] args) {
        MyData myData = new MyData();

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "come in");
            //让线程等待3s
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            myData.addTO60();
            System.out.println(Thread.currentThread().getName() + "updated number value:" + myData.number);
        },"AAA").start();

        //第二个线程是main线程
        while(myData.number == 0){
            //循环等待
        }
        System.out.println(Thread.currentThread().getName());
    }
}

运行结果可以看出来会卡在while循环处
在这里插入图片描述
二、加上volatile后

/**
 * volatile的可见性
 * demo
 */
class MyData{
    volatile int number = 0;
    public void addTO60(){
        this.number = 60;
    }
}

public class demo {
    public static void main(String[] args) {
        MyData myData = new MyData();

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "come in");
            //让线程等待3s
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            myData.addTO60();
            System.out.println(Thread.currentThread().getName() + "updated number value:" + myData.number);
        },"AAA").start();

        //第二个线程是main线程
        while(myData.number == 0){
            //循环等待
        }
        System.out.println(Thread.currentThread().getName()+"mission is over;main get number value:"+myData.number);
    }
}

结果:
在这里插入图片描述

结果可以看出,当其他线程修改了主内存空间的值时,加上了volatile主内存空间的值改变后会及时通知其他线程主物理内存的值被修改。

二:不保证原子性demo验证

下面代码:20个线程,每个线程进行1000次number++,理论上结果是两万,实际运行:

    public static void main(String[] args) {
        MyData myData = new MyData();
        for (int i = 1; i <= 20; i++) {
            new Thread(() -> {
                for (int j = 0; j <1000 ; j++) {
                    myData.addPlusPlus();
                }

            },String.valueOf(i)).start();
        }

        while(Thread.activeCount()>2){
            Thread.yield();
        }

        System.out.println(myData.number);
    }

结果并不是两万:所以说不能保证原子性,不能保证结果一致性,存在线程安全问题
在这里插入图片描述

解决办法:

1.synchronized(有点小题大做)
synchronized public void addPlusPlus() {
    this.number++;
}
2.使用AtomicInteger
class MyData {
    volatile int number = 0;

    public void addTO60() {
        this.number = 60;
    }

    public void addPlusPlus() {
        this.number++;
    }

    AtomicInteger atomicInteger = new AtomicInteger();

    public void atomicPlusPlus(){
        atomicInteger.getAndIncrement();
    }
}
public class demo {
    public static void main(String[] args) {
        MyData myData = new MyData();
        for (int i = 1; i <= 20; i++) {
            new Thread(() -> {
                for (int j = 0; j <1000 ; j++) {
                    myData.addPlusPlus();
                    myData.atomicPlusPlus();
                }

            },String.valueOf(i)).start();
        }

        while(Thread.activeCount()>2){
            Thread.yield();
        }

        System.out.println(myData.number);
        System.out.println(myData.atomicInteger);
    }

就是用AtomicInteger来代替number,用getAndIncrement来代替number++(Atomic相关内容可以看API了解方法怎么用。)就可以保证原子性。

三、禁止指令重排

理解这里需要了解一点编译器,编译器在编译时,会有个优化指令重排,在多线程下指令重排会造成线程安全问题,最终一致性无法保证

多线程环境中线程的交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的,结果无法预测。

例:单例模式为例(在多线程下,普通的单例模式并不适用)

class SingletonDemo{
    private static SingletonDemo instance = null;
    private SingletonDemo(){
        System.out.println(Thread.currentThread().getName()+"\t"+"调用构造方法SingletonDemo");
    }
    public static SingletonDemo getInstance(){
        if (instance == null){
            instance = new SingletonDemo();
        }
        return instance;
    }
}
public class demo1 {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                SingletonDemo.getInstance();
            }).start();
        }

    }
}

结果:可以看到并不是只会创建一个对象
在这里插入图片描述

使用dlc(Double Check Lock双端检索机制),代码如下

class SingletonDemo{
    private static SingletonDemo instance = null;
    private SingletonDemo(){
        System.out.println(Thread.currentThread().getName()+"\t"+"调用构造方法SingletonDemo");
    }

//    public static SingletonDemo getInstance(){
//        if (instance == null){
//            instance = new SingletonDemo();
//        }
//        return instance;
//    }

    //使用dlc双端检索机制
    public static SingletonDemo getInstance(){
        if (instance == null){
            synchronized(SingletonDemo.class){
                if (instance == null){
                    instance = new SingletonDemo();
                }
            }
        }
        return instance;
    }
}
public class demo1 {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                SingletonDemo.getInstance();
            }).start();
        }

    }
}

结果:
在这里插入图片描述

那么这样就可以了吗?

不行,因为编译器会进行指令重排,instance = new SingletonDemo();编译器会拆分成三步,
1.memory = allocate(); //分配对象内存空间
2.instance(memory);//初始化对象
3.instance = memory; //设置instance指向刚分配的内存地址,此时instace!=null

所以可能出现,另一个线程进入了第一个if (instance == null){时,instance的引用对象还未初始化完成,所以要加入volatile来禁止指令重排

package Volatile;

class SingletonDemo{
    private static volatile SingletonDemo instance = null;
    private SingletonDemo(){
        System.out.println(Thread.currentThread().getName()+"\t"+"调用构造方法SingletonDemo");
    }

//    public static SingletonDemo getInstance(){
//        if (instance == null){
//            instance = new SingletonDemo();
//        }
//        return instance;
//    }

    //使用dlc双端检索机制
    public static SingletonDemo getInstance(){
        if (instance == null){
            synchronized(SingletonDemo.class){
                if (instance == null){
                    instance = new SingletonDemo();
                }
            }
        }
        return instance;
    }
}
public class demo1 {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                SingletonDemo.getInstance();
            }).start();
        }

    }
}

发布了83 篇原创文章 · 获赞 61 · 访问量 9182

猜你喜欢

转载自blog.csdn.net/weixin_43736084/article/details/103761498