JAVA并发编程学习笔记10-volatile

概念

volatile关键字是由JVM提供的最轻量级的同步机制,它能保证内存可见性和防止指令重排序。

JMM(JAVA内存模型)常见概念

  • 原子性:保证指令不会受到上下文切换的影响
  • 有序性:保证指令不会受到CPU并行优化的影响
  • 可见性:保证指令不会受到CPU缓存的影响

可见性

在这里插入图片描述
多核CPU,由于CPU速度远大于内存速度,故在CPU和内存之间,存在缓存,可以一定程度降低两者之间的差距。但也因此出现了主存和缓存不一致的问题,这个问题我们称为可见性问题。

@Slf4j
public class Test {

    private static boolean flag = true;
    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            while (flag) {
            }
        }, "t1").start();

        Thread.sleep(1000);
        log.info("stop");
        flag = false;
    }
}
  • 期望结果:在主线程休眠结束后,由于flag变为false,while循环会结束。
  • 实际结果:t1仍旧处于死循环中,并未因为flag变为false而终止。
  • 原因分析:因为t1除了最初从主存中读取了flag的值之后,后续循环一直都读的缓存的值,当主线程更改了flag的值,并未通知t1缓存做出更改,导致循环未退出。
  • 解决方案:在flag前加上volatile关键字。因为加上volatile关键字后,当线程修改volatile所修饰的变量时,会直接将缓存中的值写入主存中,同时通知其他缓存更新主存中的数据,故当flag发生变化的同时,t1就监测到其变化,进而终止循环。

指令重排序

如下代码,CPU在保证最终结果一直的情况下,回对程序执行顺序进行优化,可能会先执行j = 2再执行i = 1;

int i = 1;
int j = 2;

说到指令重排序就不得不说到经典的dcl(double check locking)问题了

public class Singleton {

    private Singleton(){};

    private static Singleton INSTANCE = null;

    public static Singleton getInstance() {
        if (INSTANCE == null) {
            synchronized (Singleton.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Singleton();
                }
            }
        }
        return INSTANCE;
    }
}
  • 问题分析:上面代码是经典的dcl代码,但由于有指令重排序问题,可能会出现new Singleton()初始化时,先给INSTANCE绑定了对象地址,但对象还没来得及赋值的情况,而此时另一个线程拿到了这个未赋值的对象时,就会出问题。
  • 解决方法:给INSTANCE增加volatile修饰符
  • 原理分析:对volatile修饰的属性的读操作,会在读之前加上读屏障(防止读之后的代码跑到前面),对volatile修饰的属性的写操作,会在写操作之后加一个写屏障(防止写之前的操作跑后面去)。构造方法是在赋值前,故不会出现刚刚所说的结果。

happens-before规则

happens-before仅仅要求前一个操作的执行结果对后一个操作是可见的,且前一个操作按顺序排在后一个操作之前。

synchronized
@Slf4j
public class Test {

    private static int count = 0;

    private static Object lock = new Object();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            synchronized (lock) {
                count++;
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
            synchronized (lock) {
                log.info("count:{}", count);
            }
        }, "t2");
        t1.start();
        t2.start();
    }
}
volatile
@Slf4j
public class Test {

    private static volatile int count = 0;

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            count++;
        }, "t1");
        Thread t2 = new Thread(() -> {
            log.info("count:{}", count);
        }, "t2");
        t1.start();
        t2.start();
    }
}
Thread.start()方法
  • 调用start()方法前,需要用到的变量都是读可见
  • 同理,线程运行结束时,线程内做出的修改对其他线程读可见
@Slf4j
public class Test {

    public static void main(String[] args) {
        int count = 10;
        new Thread(() -> {
            log.info("count = {}", count);
        }, "t1").start();
    }
}
Thread.internupt()方法

线程被打断后,打断前的变量修改可见

@Slf4j
public class Test {

    private static int count = 10;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            while (true) {
                if (Thread.currentThread().isInterrupted()) {
                    log.info("count = {}", count);
                    break;
                }
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
            count = 20;
            t1.interrupt();
        }, "t2");
        t1.start();
        t2.start();
        while (!t1.isInterrupted()) {
            Thread.yield();
        }
        log.info("count = {}", count);
    }
}
传递性

由于x被volatile修饰,x的修改是可见的,而y在x写操作之前,由于传递性y也是可见的

@Slf4j
public class Test {

    private static volatile int x = 0;

    private static int y = 0;

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            y = 10;
            x = 10;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
            log.info("x = {}, y = {}", x, y);
        }, "t2");
        t1.start();
        t2.start();
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值