并发编程[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更加轻量级。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix