openshell 的个人博客

一天很长,但十年很短。

Open Source, Open Mind,
Open Sight, Open Future!
  menu
110 文章
5051 浏览
0 当前访客
ღゝ◡╹)ノ❤️

【JMM】voliate的作用

JMM

Java内存模型(即Java Memory Model,简称JMM)本身是一种抽象的概念,是一种规范,并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方称为栈空间),用于存储线程私有的数据,而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝的自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,工作内存中存储着主内存中的变量副本拷贝,前面说过,工作内存是每个线程的私有数据区域,因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成。

上图:
image.png
JMM是大多数多线程开发需要遵守的规范,JMM的三大特性,可见、原子、有序性。

Voliate

voliate可以保证可见性和有序两大特性,但是无法保证原子性
测试代码:

public class VolatileTestData {
    //如果不加volatile,本程序会死循环,无法退出
    private volatile int number = 0;

    private AtomicInteger atomicInteger=new AtomicInteger();

    public void add() {
        number++;
    }

    public void atomicAdd() {
        atomicInteger.getAndIncrement();
    }


    public AtomicInteger getAtomicInteger() {
        return atomicInteger;
    }

    public void setAtomicInteger(AtomicInteger atomicInteger) {
        this.atomicInteger = atomicInteger;
    }


    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }
}

Volatile保证可见性

/**
     * volatile保证可见性
     */
    @Test
    public void testVisibility() {
        VolatileTestData volatileTestData = new VolatileTestData();

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("任务开始!");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                volatileTestData.add();
                System.out.println(Thread.currentThread().getName() + "递增值" + volatileTestData.getNumber());

            }
        });
        thread.start();
        //注:
        //1.number 默认值为0,故此处为死循环
        //2.当上方的线程执行成功之后,利用volatile的可见性下方循环即可跳出
        while (volatileTestData.getNumber() == 0) {
            System.out.println("等待线程执行中...");
        }
        System.out.println("任务执行完毕");
    }
}

Volatile不保证原子性

public class VolatileTest {
/**
     * 不保证原子性
     */
    @Test
    public void testAtomicity() {
        VolatileTestData volatileTestData = new VolatileTestData();
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    volatileTestData.add();
                    volatileTestData.atomicAdd();
                }
            }).start();
        }
        //注:
        //1.Thread.activeCount()用于返回当前线程的线程组中活动线程的数量,返回的值只是一个估计值,因为当此方法遍历内部数据结构时,线程数可能会动态更改。
        //2.此处无内部数据结构故无影响
        //3.IntelliJ IDEA执行用户代码的时候,实际是通过反射方式去调用,而与此同时会创建一个Monitor Ctrl-Break 用于监控目的,故线程会多一个。
        while (Thread.activeCount() > 2) {
            System.out.println("线程数量:" + Thread.activeCount());
            Thread.yield();
        }
        System.out.println("普通i++的执行结果:" + volatileTestData.getNumber());
        System.out.println("Atomically increments的结果:" + volatileTestData.getAtomicInteger());
    }

运行结果:

普通i++的执行结果:11553
Atomically increments的结果:20000

Volatile保证有序性

首先编写一个DCL版的单例:

public class LazSingletonByDCL {
    /**
     * 声明实例(未初始化)
     */
    private static LazSingletonByDCL instance;

    /**
     * 构造方法私有化,防止外部任意初始化实例
     */
    private LazSingletonByDCL() {
        System.out.println("我被构建啦!");
    }

    /**
     * 为外部提供获取实例的方法
     *
     */
    public static synchronized LazSingletonByDCL getInstance() {
        if (instance == null) {
            synchronized (LazSingletonByDCL.class) {
                if (instance == null) {
                    instance = new LazSingletonByDCL();
                }
            }
        }
        return instance;
    }
}

测试方法:

public class VolatileTest {
@Test
    public void testSingletonDemo() {
        for (int j = 0; j < 1000; j++) {
            new Thread(() -> {
                LazSingletonByDCL.getInstance();
            }).start();
        }
    }
}

上面的代码可能99.9%的情况都是正常的,但是有一定几率出现问题,原因如下:

其中instance = new LazSingletonByDCL();可以分为三步执行

  1. memory=allocate(); // 分配内存空间
  2. instance(memory); //初始化对象
  3. instance=memory; //将声明指向的内存,此时intance!=null

在单线程环境时,无论重排前还是重排后该执行结果是正常的。

但是,由于步骤2和3不存在数据依赖关系,所以在多线程环境下有可能出现顺序为132的情况。声明提前指向了步骤1分配的内存空间,但是该内存空间尚未完成初始化。
为了防止出现132的情况,就需要使用 volatile关键字来声明:

private static volatile LazSingletonByDCL instance;

标题:【JMM】voliate的作用
作者:openshell
地址:http://solo.caiqz.cn/articles/2021/07/12/1626089697619.html