并发编程[8]_并发编程三大性质和volatile关键字


本文介绍并发编程的三大性质:原子性,可见性,有序性以及volatile关键字。

1. 原子性

原子性指一个操作或者多个操作,要么全部执行要么全部不执行。
在介绍synchronized关键字的时候,就有介绍过,在执行 i++ 的时候,实际上执行的是三个步骤:读取 i 的值,i+1,将结果写入。所以这个操作不是原子操作,在多线程执行 i++ 和 i-- 的时候,结果跟我们所想的不同。
而synchronized关键字可以对代码块或方法加锁,就确保了操作的原子性。

2. 可见性

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

public class Test1 {
    private static final Logger log = LoggerFactory.getLogger(Test1.class);

    private static boolean stop = false;    // 线程判断是否要停下

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            while(!stop){

            }
            log.debug("stop...");
        }, "t1");

        t1.start();
        Thread.sleep(100);
        stop = true;
    }
}

例1很简单的逻辑,t1线程判断stop标志位是否为true,不为true的话,就不退出,为true的话,就退出循环,打印“stop...”。按一般的思维方式,在主线程将stop设置为true之后,t1循环读取stop的值时,应该是true,然后退出循环。然后程序运行之后,却发现并没有任何输出,且循环并没有退出,程序也还没结束。
原因是由于缓存的存在,t1线程在读取stop值时,主线程对stop修改后的值,还没有真正写入内存,导致读取的是旧值false。
解决办法是可以对stop变量加volatile关键字,这样jvm就会保证它的可见性。

    private volatile static boolean stop = false;    // 线程判断是否要停下

jvm在读取一个volatile变量时,会强制它从内存中读取,
在写入一个volatile变量时,也会强制它写入到内存。
另外,通过synchronized和lock也能够保证可见性。它们能够保证同一时刻只有一个线程获取锁,然后执行同步代码,锁释放前会将变量的修改刷新到内存当中。所以也能够保证可见性。

3. 有序性

有序性指的是程序执行的顺序是按代码的先后顺序执行的。
java编译器和处理器对一些指令会进行优化和重排序,这个过程不会影响到单线程的操作,但是在多线程并发中就会影响程序的正确性。

1. int i = 0;
2. int k = 0;
3. i = i +1;
4. k = i +1;

四个语句的执行顺序,可能是 1 -> 2 -> 3 -> 4; 也可能是 2 -> 1 -> 3 -> 4等,出现指令重排的情况,当然,因为语句4是得用到语句3的结果,所以指令间是存在着依赖性,结果不可能出现 4 -> 3。
我们可以用volatile关键字,来限制指令的重排,保证有序性。
volatile 修饰变量,jvm会为对应的变量的读和写创建屏障:

  • 写屏障:不会将写屏障之前的代码排在写屏障之后
  • 读屏障:不会将读屏障之后的代码排在读屏障之前

一些其他的执行顺序,可以参考happens-before原则,我这也从网上摘抄出了这个原则:

  • 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作
  • 锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作
  • volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
  • 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
  • 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作
  • 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
  • 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
  • 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始

4. 总结

volatile 关键字可以保证程序的可见性和有序性,但是不能保证原子性,保证原子性可用lock和synchronized来实现。
synchronized 可以保证synchronized块间的原子性,可见性和有序性。
volatile 不需要加锁,比synchronized更加轻量级。

posted @   Aeons  阅读(8)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
点击右上角即可分享
微信分享提示