【Concurrency】之 互斥与Java内存模型

Topic 1:互斥(竞态条件)
Topic 2:Java内存模型

一、互斥

首先来看个小栗子

《【Concurrency】之 互斥与Java内存模型》
多次运行后会发现运行结果会不同。
即,多线程编程的运行结果可能依赖于时序,多次运行的结果并不稳定

多个线程同时使用共享内存时

按照多线程的尿性,必然会打成一团。

栗子2

《【Concurrency】之 互斥与Java内存模型》
运行结果为什么会不同呢?
① 线程使用counter.count 对象时发生了竞态条件(即代码行为取决于各操作的时序)
++count 并非具有原子性

// Java编译器解释++count 如下:
getfield #2
iconst_1
iadd
putfield #2
上锁

为避免栗子2,使用达到线程之间互斥,(即某一时间至多有一个线程能持有锁)
竞态条件的解决方案: 对count进行同步(synchronize)访问。即使用内置锁(也为:互斥锁(mutex)、管程(monitor)或临界区(critical section))
《【Concurrency】之 互斥与Java内存模型》

二、Java内存模型

Java内存模型 3 个特性
  1. 可见性
  2. 原子性
  3. 有序性

(1)线程

《【Concurrency】之 互斥与Java内存模型》

举个栗子3

《【Concurrency】之 互斥与Java内存模型》

(2)指令重排

重排序(乱序执行)
  1. 编译器的静态优化可以打乱代码的执行顺序
  2. JVM的动态优化也会打乱代码的执行顺序
  3. 硬件可以通过乱序执行来优化其性能
举个栗子4

《【Concurrency】之 互斥与Java内存模型》

那么如何让指令不重排呢?

volatile
当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。volatile变量不会被缓存在寄存器或者队其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。
volatile的典型用法:检查某个状态标记以判断是否退出循环。

加锁机制既可以确保可见性又可以确保原子性,而volatile变量只能确保可见性。

三、再想想

(1) Java内存模型是如何保证对象初始化是线程安全的?是否必须通过加锁才能在线程之间安全地公开对象?

  1. 在静态初始化函数中初始化一个对象引用
  2. 将对象的引用保存到volatile类型的域或者AtomicReferance对象中
  3. 将对象的引用保存到某个正确构造对象的final类型域中,
  4. 将对象的引用保存到一个由锁保护的域中。

(2) 了解反模式“双重检查锁模式”(double-checked locking)以及为什么称其为反模式

https://blog.csdn.net/fanfan4569/article/details/52069101
在现在的测试驱动开发中,单例模式由于难以被模拟其行为而被视为反模式(anti pattern),所以如果你是测试驱动开发的开发者,最好避免使用单例模式。

内部静态类做延迟初始化
public class ResourceFactory {
    //静态初始化不需要额外的同步机制
    private static class ResourceHolder {
        public static Resource resource = new Resource();
    }
    //延迟加载
    public static Resource getResource() {
        return ResourceHolder.resource;
    }
    static class Resource {
    }
}

四、小结

一些避免危害的准则:
(1) 对共享变量的所有访问都需要同步化
(2) 读线程和写线程都需要同步化
(3) 按照约定的全局顺序来获取多把锁
(4) 当持有锁时避免调用外星方法(解决方法:保护性复制(defensive copy))
(5) 持有锁的时间应尽可能短

    原文作者:java内存模型
    原文地址: https://blog.csdn.net/fanfan4569/article/details/80410490
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞