JUC并发编程学习笔记(十六)Volatile

火影  金牌会员 | 2023-12-10 04:45:14 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 889|帖子 889|积分 2667

Volatile

保证可见性
  1. private volatile static Integer num = 0;
复制代码
使用了volatile关键字,即可保证它本身可被其他线程的工作内存感知,即变化时也会被同步变化。
不保证原子性
原子性:不可分割
线程A在执行任务时是不可被打扰的,也不能被分割,要么同时成功,要么同时失败。
  1. package org.example.tvolatile;
  2. public class VDemo02 {
  3.     //synchronized保证原子性,每次只有一条线程执行,所以结果准确
  4.     //volatile不保证原子性,虽然也是同步机制,但是结果不准确
  5.     private volatile static int num = 0;
  6.     public static void add(){
  7.         num++;
  8.     }
  9.     public static void main(String[] args) {
  10.         for (int i = 0; i < 20; i++) {
  11.             new Thread(()->{
  12.                 for (int j = 0; j < 1000; j++) {
  13.                     add();
  14.                 }
  15.             }).start();
  16.         }
  17.         while (Thread.activeCount()>2){//主线程和GC线程
  18.             Thread.yield();//让主线程礼让
  19.         }
  20.         System.out.println(num);
  21.     }
  22. }
复制代码

每次结果也不一样。
如果不加Lock加synchronized,怎么保证原子性?
要了解为什么一个num++都不能保证原子性,首先我们需要查看到他编译好的class字节码文件,找到target,并且右键选择打开外部的文件,找到对应的class文件,再通过javap -c命令反编译查看字节码文件。




查看到字节码文件后可以看到从底层看,其实num++这个操作再多线程下并不安全,有获取和写回这两个操作

那么又回到了这个问题,在volatile中如何保证原子性。打开jdk帮助文档,我们能找到原子性的一些数据类型


在volatile需要保证原子性操作的时候使用原子类来解决原子问题。
原子类为什么高级
原子类的包装类底层使用的是CAS操作。
  1. package org.example.tvolatile;
  2. import java.util.concurrent.atomic.AtomicInteger;
  3. public class VDemo02 {
  4.     //synchronized保证原子性,每次只有一条线程执行,所以结果准确
  5.     //volatile不保证原子性,虽然也是同步机制,但是结果不准确
  6.     private volatile static AtomicInteger num = new AtomicInteger(0);
  7.     public static void add(){
  8.         num.getAndIncrement();//AtomicInteger+1的方法,并不是简单的+1操作,方法:CAS
  9.     }
  10.     public static void main(String[] args) {
  11.         for (int i = 0; i < 20; i++) {
  12.             new Thread(()->{
  13.                 for (int j = 0; j < 1000; j++) {
  14.                     add();
  15.                 }
  16.             }).start();
  17.         }
  18.         while (Thread.activeCount()>2){//主线程和GC线程
  19.             Thread.yield();//让主线程礼让
  20.         }
  21.         System.out.println(num);
  22.     }
  23. }
复制代码
这些类的底层都直接调用c++和操作系统挂钩!在内存中修改值。Unsafe类是一个很特殊的存在!Unsafe类的所有方法都是native方法,调用c++底层
native是与C++联合开发的时候用的!java自己开发不用的!
禁止指令重排
什么是指令重排:你写的程序并不是按照你写的那样去执行的
源代码-----编译器优化的指令重排--------指令并行也可能会指令重排--------内存系统也会重排--------->执行
  1. int x = 1;//1
  2. int y = 2;//2
  3. x = x+5;//3
  4. y = x*x;//4
复制代码
我们所期望的执行顺序:1、2、3、4;但是如果按照1、3、2、4或者2、4、1、3的顺序执行也是能够顺利运行的,计算机执行时可能对对我们所期望执行顺序的程序进行指令重排,结果是正确的但是对运行顺序有所不同
但是指令重排后的顺序不可能是4、3、2、1或者其它不符合逻辑的顺序。因为处理器在进行指令重拍的过程中会考虑数据之间的依赖性
可能造成影响的结果:四个初始值都为零
线程A线程Bx = a;y = b;b = 1;a = 2;由于两个线程的操作都没有数据依赖性,指令重排就不会考虑顺序问题,可能会导致最终的执行顺序如下
线程A线程Bb = 1;a = 2;x = a;y = b;多线程下可能导致一些问题
正常结果:x=0;y=0;
指令重排导致的诡异结果:x=2;y=1;
这是一种概念可能你写1000w行代码都不一定会出现,但是在理论上是一定会参数的。
volatile可以避免指令重排
计算机中存在着一种指令叫做内存屏障,它是一种CPU指令。
作用:
1、保证特定操作的执行顺序(可以让volatile避免指令重排)
2、可以保证某些变量的内存可见性(可以让volatile实现可见性)

Volatile保证可见性、不能保证原子性、由于内存屏障,可以避免指令重排的现象产生!
内存屏障在单例模式中使用的最多!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

火影

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表