• JUC_Volatile简述


    概述

    Volatile关键字是JVM提供的轻量级同步机制,其特性为:

    1.保证可见性

    2.不保证原子性

    3.禁止指令重排

    保证可见性

    示例代码

    1. public class Demo01 {
    2. private static volatile int num = 0;
    3. public static void main(String[] args) {
    4. new Thread(()->{
    5. while (num==0){
    6. };
    7. }).start();
    8. try {
    9. TimeUnit.SECONDS.sleep(3);
    10. } catch (InterruptedException e) {
    11. e.printStackTrace();
    12. }
    13. System.out.println(num = 1);
    14. }
    15. }

    运行效果

    主线程执行完 num = 1 语句后就马上结束了,显然此时子线程知晓了num == 1,跳出了while循环,子线程结束,主线程也随之结束

    换言之,主线程中对num的修改被子线程知晓了,此为可见性

    不保证原子性 

    原子性:一个操作或者多个操作 要么全部执行 并且执行的过程不会被任何因素打断 要么就都不执行
    若要保证原子性:
    1.上锁 即synchronized或lock
    2.使用原子类 AtomicxXXX
    

    示例代码

    1. public class Demo02 {
    2. //++本身即是非原子性操作
    3. private volatile static int num;
    4. private volatile static AtomicInteger AtomicNum = new AtomicInteger();
    5. private static void add(){
    6. num++;
    7. }
    8. private static void AtomicAdd(){
    9. AtomicNum.addAndGet(1);//底层使用CAS实现
    10. }
    11. public static void main(String[] args) {
    12. for (int i = 0; i < 10; i++) {
    13. new Thread(()->{
    14. for (int j = 0; j < 10000; j++) {
    15. add();
    16. }
    17. }).start();
    18. }
    19. for (int i = 0; i < 10; i++) {
    20. new Thread(()->{
    21. for (int j = 0; j < 10000; j++) {
    22. AtomicAdd();
    23. }
    24. }).start();
    25. }
    26. //JVM中一直开启的线程 main GC
    27. //所以Thread.activeCount()>2时 可知上述线程尚未执行完
    28. //此处作用是让main线程让出CPU直到上述线程执行完 否则会在上述线程未执行完的情况下就输出执行输出语句
    29. while (Thread.activeCount()>2){
    30. Thread.yield();
    31. }
    32. System.out.println("num : " + num);
    33. System.out.println("AtomicNum : " + AtomicNum);
    34. }
    35. }

    运行效果

    可以看到,num的值明显不符合预期,这是因为num++并不是原子性操作,并发情况下会出现多个线程同时执行num++的情况;假定一时刻num == 100,此时线程A与线程B同时执行num++,A与B均认为num == 100,所以最终二者向主内存提交的结果均为num == 101,导致最终结果缩水

    禁止指令重排

    指令重排:指令重排序是指编译器或CPU为了优化程序的执行效率 而对不存在数据依赖的指令进行重新排序的一种机制 并发时指令重排可能会导致预期与结果不一致

    关于可见性问题的说明:

    一.单线程情况下,无可见性问题

    1. public class Demo03 {
    2. public static void main(String[] args) {
    3. int i = 1; //1
    4. int j = 1; //2
    5. int k = i + j;//3
    6. System.out.println(k);
    7. }
    8. }

    上述代码中,1与2不存在数据依赖性,所以可能会被指令重排,调换执行顺序;而3数据依赖1与2,所以不可能被指令重排,先于1、2执行

    二.多线程情况下,可能引发可见性问题

    1. public class Demo03 {
    2. static int num = 0;
    3. static boolean flag = false;
    4. public static void main(String[] args) {
    5. new Thread(() -> {
    6. if (flag) { //1
    7. num = num + num; //2
    8. System.out.println(num);
    9. }
    10. }).start();
    11. new Thread(() -> {
    12. num = 1; //3
    13. flag = true; //4
    14. }).start();
    15. }
    16. }

    若在单线程情况下,1数据依赖4,2数据依赖3,所以3、4必然先于1、2执行,可能的执行顺序为3412或4312;但在多线程情况下,3、4可能会被指令重排,若此时的执行顺序为4123,则会导致输出的结果为0,产生预期与结果不一致的问题

  • 相关阅读:
    AMD发布22.11.1驱动,支持《使命召唤:战区2.0》
    项目工作流程
    SpringBoot集成Tomcat服务
    [附源码]java毕业设计某互联网公司人力资源管理系统
    进阶笔录-深入理解Java线程之-AQS
    用户态切换到内核态的方式
    2021:Python的下载安装教程(很详细,初学者也能懂)
    Java中enum的使用
    Vue3管理后台项目使用高德地图选点
    院校信息 | 布朗、宾大更新标化政策!八大藤校2025Fall标化要求全部出炉!
  • 原文地址:https://blog.csdn.net/Mudrock__/article/details/127129000