程序员基础原理Java 并发

【Java 并发笔记】并发机制底层实现整理

2018-12-05  本文已影响14人  58bc06151329

文前说明

作为码农中的一员,需要不断的学习,我工作之余将一些分析总结和学习笔记写成博客与大家一起交流,也希望采用这种方式记录自己的学习之旅。

本文仅供学习交流使用,侵权必删。
不用于商业目的,转载请注明出处。

1. 缓存一致性问题

硬件内存架构 缓存架构

1.2 MESI 协议

MESI 协议缓存状态
状态 说明
M(修改,Modified) 本地处理器已经修改缓存行, 即是脏行, 它的内容与内存中的内容不一样. 并且此 cache 只有本地一个拷贝(专有)。
E(专有,Exclusive) 缓存行内容和内存中的一样, 而且其它处理器都没有这行数据。
S(共享,Shared) 缓存行内容和内存中的一样, 有可能其它处理器也存在此缓存行的拷贝。
I(无效,Invalid) 缓存行失效, 不能使用。

2. 优化重排序问题

重排序

数据依赖性

as-if-serial

2.1 指令级并行的重排序(处理器)

指令级并行的重排序
不优化时的执行过程 优化时的执行过程
指令获取。 指令获取。
如果输入的运算对象是可以获取的(比如已经存在于寄存器中),这条指令会被发送到合适的功能单元。如果一个或者更多的运算对象在当前的时钟周期中是不可获取的(通常需要从主内存获取),处理器会开始等待直到它们是可以获取的。 指令被发送到一个指令序列(也称执行缓冲区或者保留站)中。
指令在合适的功能单元中被执行。 指令将在序列中等待,直到它的数据运算对象是可以获取的。然后,指令被允许在先进入的、旧的指令之前离开序列缓冲区。(此处表现为乱序)
功能单元将运算结果写回寄存器。 指令被分配给一个合适的功能单元并由之执行。
结果被放到一个序列中。
仅当所有在该指令之前的指令都将他们的结果写入寄存器后,这条指令的结果才会被写入寄存器中。(重整乱序结果)

2.2 编译器优化的重排序

3. 内存模型

通信

同步

3.1 顺序一致性内存模型

顺序一致性内存模型 一致性模型执行效果 未同步程序在一致性模型中执行效果 JMM 中与在顺序一致性内存模型中的执行结果的异同

4. 抽象结构(JMM)

Java 内存模型抽象

4.1 happens-before 关系(先行发生原则)

JMM 的设计示意图
规则 说明
程序次序规则 一个线程内,按照代码顺序,书写在前面的操作 happens-before 书写在后面的操作。
锁定规则 一个 unLock 操作 happens-before 后面对同一个锁的 lock 操作。
volatile 变量规则 对一个变量的写操作 happens-before 后面对这个变量的读操作。
传递规则 如果操作 A happens-before 操作 B,而操作 B 又 happens-before 操作 C,则可以得出操作 A happens-before 操作 C。
线程启动规则 Thread 对象的 start() 方法 happens-before 此线程的每个一个动作。
线程中断规则 对线程 interrupt() 方法的调用 happens-before 被中断线程的代码检测到中断事件的发生。
线程终结规则 线程中所有的操作都 happens-before 线程的终止检测,我们可以通过 Thread.join() 方法结束、Thread.isAlive() 的返回值手段检测到线程已经终止执行。
对象终结规则 一个对象的初始化完成 happens-before 它的 finalize() 方法的开始。
happens-before 与 JMM 的关系

4.1.1 as-if-serial 和 happens-before 比较

4.2 Java 内存模型的实现

8 种基本操作
操作 作用
lock (锁定) 作用于主内存的变量,它把一个变量标识为一条线程独占的状态。
unlock (解锁) 作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
read (读取) 作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的 load 动作使用。
load (载入) 作用于工作内存的变量,它把 read 操作从主内存中得到的变量值放入工作内存的变量副本中。
use (使用) 作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时就会执行这个操作。
assign (赋值) 作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
store (存储) 作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后 write 操作使用。
write (写入) 作用于主内存的变量,它把 Store 操作从工作内存中得到的变量的值放入主内存的变量中。

原子性

可见性

有序性

4.3 Memory Barrier(内存屏障)

Memory Barrier
屏障类型 指令示例 说明
LoadLoad 屏障 Load1; LoadLoad; Load2 在 Load2 及后续读取操作要读取的数据被访问前,保证 Load1 要读取的数据被读取完毕。
StoreStore 屏障 Store1; StoreStore; Store2 在 Store2 及后续写入操作执行前,保证 Store1 的写入操作对其它处理器可见。
LoadStore 屏障 Load1; LoadStore; Store2 在 Store2 及后续写入操作被执行前,保证 Load1 要读取的数据被读取完毕。
StoreLoad 屏障 Store1; StoreLoad; Load2 在 Load2 及后续所有读取操作执行前,保证 Store1 的写入对所有处理器可见。它的开销是四种屏障中最大的(冲刷写缓冲器,清空无效化队列)。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能。

4.3.1 x86架构的内存屏障

Store Barrier

Load Barrier

Full Barrier

4.4 同步规则

规则 说明
规则 1 如果要把一个变量从主内存中复制到工作内存,就需要按顺序的执行 read 和 load 操作,如果把变量从工作内存中同步回主内存中,就要按顺序的执行 store 和 write 操作。但 Java 内存模型只要求上述操作必须按顺序执行,而没有保证必须是连续执行。
规则 2 不允许 read 和 load、store 和 write 操作之一单独出现。
规则 3 不允许一个线程丢弃它的最近 assign 的操作,即变量在工作内存中改变了之后必须同步到主内存中。
规则 4 不允许一个线程无原因的(没有发生过任何 assign 操作)把数据从工作内存同步回主内存中。
规则 5 一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load 或 assign )的变量。即对一个变量实施 use 和 store 操作之前,必须先执行过了 load 或 assign 操作。
规则 6 一个变量在同一个时刻只允许一条线程对其进行 lock 操作,但 lock 操作可以被同一条线程重复执行多次,多次执行 lock 后,只有执行相同次数的 unlock 操作,变量才会被解锁。所以 lock 和 unlock 必须成对出现。
规则 7 如果对一个变量执行 lock 操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前需要重新执行 load 或 assign 操作初始化变量的值。
规则 8 如果一个变量事先没有被 lock 操作锁定,则不允许对它执行 unlock 操作;也不允许去 unlock 一个被其他线程锁定的变量。
规则 9 对一个变量执行 unlock 操作之前,必须先把此变量同步到主内存中(执行 store 和 write 操作)。

4.5 volatile 关键字

保证可见性

禁止进行指令重排序

不能确保原子性

4.5.1 volatile 的实现原理

volatile

4.5.2 volatile 的应用场景

volatile boolean shutdownRequested;
......
public void shutdown() { shutdownRequested = true; }
public void doWork() { 
    while (!shutdownRequested) { 
        // do stuff
    }
}
public class BackgroundFloobleLoader {
    public volatile Flooble theFlooble;
 
    public void initInBackground() {
        // do lots of stuff
        theFlooble = new Flooble();  // this is the only write to theFlooble
    }
}
 
public class SomeOtherClass {
    public void doWork() {
        while (true) { 
            // do some stuff...
            // use the Flooble, but only if it is ready
            if (floobleLoader.theFlooble != null) 
                doSomething(floobleLoader.theFlooble);
        }
    }
}
public class UserManager {
    public volatile String lastUser;
 
    public boolean authenticate(String user, String password) {
        boolean valid = passwordIsValid(user, password);
        if (valid) {
            User u = new User();
            activeUsers.add(u);
            lastUser = user;
        }
        return valid;
    }
}
@ThreadSafe
public class Person {
    private volatile String firstName;
    private volatile String lastName;
    private volatile int age;
 
    public String getFirstName() { return firstName; }
    public String getLastName() { return lastName; }
    public int getAge() { return age; }
 
    public void setFirstName(String firstName) { 
        this.firstName = firstName;
    }
 
    public void setLastName(String lastName) { 
        this.lastName = lastName;
    }
 
    public void setAge(int age) { 
        this.age = age;
    }
}
@ThreadSafe
public class CheesyCounter {
    // Employs the cheap read-write lock trick
    // All mutative operations MUST be done with the 'this' lock held
    @GuardedBy("this") private volatile int value;
 
    public int getValue() { return value; }
 
    public synchronized int increment() {
        return value++;
    }
}

4.6 final 关键字

final 域的重排序规则

写 final 域的重排序规则

读 final 域的重排序规则

4.6.1 使用 final 的限制条件和局限性

public class MyClass {
  private final int myField = 1;
  public MyClass() {
    ...
  }
}

或者

public class MyClass {
  private final int myField;
  public MyClass() {
    ...
    myField = 1;
    ...
  }
}

下面的方法仍然可以修改该 list。

private final List myList = new ArrayList();
myList.add("Hello");

声明为 final 可以保证如下操作不合法

myList = new ArrayList();
myList = someOtherList;

4.7 synchronized 关键字

4.8 锁

concurrent 包的源码实现

4.9 long 和 double 型变量

4.10 常见处理器内存模型

内存模型的强弱对比
内存模型名称 对应的处理器 Store-Load 重排序 Store-Store重排序 Load-Load 和Load-Store重排序 可以更早读取到其它处理器的写 可以更早读取到当前处理器的写
TSO sparc-TSO X64 Y Y
PSO sparc-PSO Y Y Y
RMO ia64 Y Y Y Y
PowerPC PowerPC Y Y Y Y Y

参考来源

https://www.jianshu.com/p/64240319ed60
https://www.jianshu.com/p/d3fda02d4cae
https://www.jianshu.com/p/d52fea0d6ba5
https://blog.csdn.net/suifeng3051/article/details/52611310
http://www.cnblogs.com/zhiji6/p/10037690.html

上一篇下一篇

猜你喜欢

热点阅读