• 并发编程 - 并发可见性,原子性,有序性 与 JMM内存模型


    1. 并发三大特性

    并发编程Bug的源头: 原子性 可见性 有序性 问题

    1.1 原子性

    一个或多个操作,要么全部执行且在执行过程中不被任何因素打断,要么全部不执行。 在 Java
    中,对基本数据类型的变量的读取和赋值操作是原子性操作(64位处理器)。 不采取任何的原子性保障措施的自增操作并不是原子性的,比如i++操作。
    原子性案例分析
    下面例子模拟多线程累加操作
    1. public class AtomicTest {
    2. private static volatile int counter = 0;
    3. public static void main(String[] args) {
    4. for (int i = 0; i < 10; i++) {
    5. Thread thread = new Thread(() -> {
    6. for (int j = 0; j < 10000; j++) {
    7. //synchronized (AtomicTest.class) {
    8. counter++;
    9. // }
    10. }
    11. });
    12. thread.start();
    13. }
    14. try {
    15. Thread.sleep(3000);
    16. } catch (InterruptedException e) {
    17. e.printStackTrace();
    18. }
    19. //思考counter=?
    20. System.out.println(counter);
    21. }
    22. }
    执行结果不确定, 与预期结果不符合,存在线程安全问题
    如何保证原子性?
            1.通过 synchronized 关键字保证原子性
            2.通过 Lock锁保证原子性
            3.通过 CAS保证原子性
    思考:在 32 位的机器上对 long 型变量进行加减操作是否存在并发隐患?
    https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.7

    1.2 可见性

    可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到 修改的值。
    可见性案例分析
    下面是模拟两个线程对共享变量操作的例子,用来分析线程间的可见性问题
    1. @Slf4j
    2. public class VisibilityTest {
    3. // volatile -> lock addl $0x0,(%rsp)
    4. private boolean flag = true;
    5. // private volatile boolean flag = true;
    6. //private volatile int count;
    7. public synchronized void refresh() {
    8. // 希望结束数据加载工作
    9. flag = false;
    10. System.out.println(Thread.currentThread().getName() + "修改flag:"+flag);
    11. }
    12. public void load() {
    13. System.out.println(Thread.currentThread().getName() + "开始执行.....");
    14. while (flag) {
    15. //TODO 业务逻辑:加载数据
    16. //shortWait(10000);
    17. //synchronized可以保证可见性
    18. //System.out.println("正在加载数据......");
    19. // count++;
    20. //添加一个内存屏障 可以保证可见性
    21. //UnsafeFactory.getUnsafe().storeFence();
    22. // try {
    23. // Thread.sleep(0);
    24. // } catch (InterruptedException e) {
    25. // throw new RuntimeException(e);
    26. // }
    27. //Thread.yield(); //让出cpu使用权
    28. }
    29. System.out.println(Thread.currentThread().getName() + "数据加载完成,跳出循环");
    30. }
    31. public static void main(String[] args) throws InterruptedException {
    32. VisibilityTest test = new VisibilityTest();
    33. // 线程threadA模拟数据加载场景
    34. Thread threadA = new Thread(() -> test.load(), "threadA");
    35. threadA.start();
    36. // 让threadA先执行一会儿后再启动线程B
    37. Thread.sleep(1000);
    38. // 线程threadB通过修改flag控制threadA的执行时间,数据加载可以结束了
    39. Thread threadB = new Thread(() -> test.refresh(), "threadB");
    40. threadB.start();
    41. }

    当flag没有volatile修饰时,不可见,执行结果线程A跳不出循环

    运行结果:threadA没有跳出循环,也就是说threadB对共享变量flag的更新操作对threadA不可见, 存在可见性问题。

    思考:上面例子中为什么多线程对共享变量的操作存在可见性问题?

    当flag有volatile修饰时,具有可见性,执行结果线程A可以跳循环

    当flag没有volatile修饰时,但是在load()方法内的while()中输出打印语句如:System.out.println("正在加载数据......")后,,执行结果线程A还是可以跳循环,原因是println()方法内有synchronized (this),具有可见性。

    当flag没有volatile修饰时,但是在load()方法内的while()中加上内存屏障,执行结果线程A也是可以跳循环,具有可见性。
    
    1. public class UnsafeFactory {
    2. /**
    3. * 获取 Unsafe 对象
    4. * @return
    5. */
    6. public static Unsafe getUnsafe() {
    7. try {
    8. Field field = Unsafe.class.getDeclaredField("
  • 相关阅读:
    【李宏毅】深度学习-2021HW3-CNN(图像分类)
    常用的数据库连接池、配置参数及相应的调优方法
    AI项目十三:PaddleOCR训练自定义数据集
    JVM虚拟机学习笔记之-5. 字节码执行引擎
    学习笔记-java代码审计-反序列化
    MVC架构回顾
    【JAVA】最容易忽视的数据类型——枚举
    索尼 toio™应用创意开发征文|toio俄罗斯方块游戏
    到底什么是Linux?快进来学习!
    .Net 7内容汇总(3)--反射优化
  • 原文地址:https://blog.csdn.net/weixin_43874650/article/details/134084929