Java并发编程的艺术(底层原理与内存模型总结)

底层原理

(1)volatile:volatile修饰的共享变量进行写操作时后多出lock指令代码,lock前缀指令在多核处理器下会发生两件事:

  • 将处理器缓存行的数据写回到系统内存;
  • 这个写操作会使在其他CPU里缓存了该内存地址的数据无效。

(2)synchronized实现原理:

三种形式:普通方法:锁为当前实例对象;静态同步方法:锁为当前类的Class对象;对于同步方法块:锁为Synchronized括号里配置的对象。

代码块同步:使用monitorenter和monitorexit指令实现

方法同步:在对象???

Java内存模型

1、并发编程中的两个关键问题:线程之间如何通信与同步。

(1)同步:可以理解为在执行完一个函数或方法之后,一直等待系统返回值或消息,这时程序是主动阻塞的,只有接收到返回的值或消息后才往下执行其他的命令。  

(2)异步:执行完函数或方法后,不必阻塞性地等待返回值或消息,只需要向系统委托一个异步过程,那么当系统接收到返回值或消息时,系统会自动触发委托的异步过程,从而完成一个完整的流程。 

2、JMM定义的线程与主内存的抽象关系:线程的共享变量存在主内存中,每个线程都有一个私有的本地内存,本地内存存储着该线程1⃣️读/写共享变量的副本。

扫描二维码关注公众号,回复: 5820249 查看本文章

3、重排序

(1)为了提高性能,编译器和处理器常常会对指令进行重排序,分为三种类型:

  • 编译器优化的重排序:编译器在不改变程序语义的前提下,可以重新安排语句的执行顺序。
  • 指令级并行的重排序:现代处理器采用了指令级并行技术将多条指令重叠执行,如果不存在数据依赖性处理器可以改变语句对应机器指令的执行顺序。
  • 内存系统的重排序:由于处理器使用缓存和读写缓冲区,使得加载和存储操作看上去可能是在乱序执行。

(2)重排序遵守数据依赖性:写后读、读后写、写后写,这三种操作如果执行顺序发生变化执行结果就会改变,不会被重排。

注:这里讲的数据依赖性仅针对单个处理器中执行的指令序列和单个线程中执行的操作,不同处理器以及不同线程之间的数据依赖性不被编译器和处理器考虑。

(3)as-if-serial语义:不管如何重排序,(单线程)程序的执行结果不能被改变。

4、happens-before简介

(1)定义

  • 对于程序员的承诺:如果一个操作happends-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,并且第一个操作的执行顺序排在第二个操作之前;
  • 对于编译器和处理器重排序的约束规则:两个操作之间存在happends-before关系,并不意味着Java平台的具体实现必须要按照happends-before关系指定的顺序来执行,如果重排序后执行与按照happends-before关系指定的顺序执行的结果一致,则这种重排序并不非法。

注:

  • 综上所述:两个操作之间具有happends-before关系,仅仅要求前一个操作对后一个操作可见,且前一个操作按顺序排在后一个操作之前,并不代表前一个操作必须要在后一个操作之前执行。
  • happends-before关系和as-if-serial语义之间的关系:
    • as-if-serial语义保证单线程内程序执行结果不被改变,happends-before关系保证正确同步的多线程的执行结果不被改变。
    • as-if-serial语义给编写单线程的程序员创造了一种幻境:单线程程序是按照程序的顺序来执行的;happends-before关系给编写正确同步的多线程的程序员创造了一种幻境:正确同步的多线程程序是按照happends-before指定的顺序来执行的。

(2)happends-before规则

  • 程序顺序规则:一个线程中的每个操作,happends-before于该线程中的任意后续操作。
  • 监视器锁规则:对一个锁的解锁,happends-before于随后对这个锁的加锁。
  • volatile变量规则:对一个volatile域的写,happends-before于任意后续对于对这个volatile域的读。
  • 传递性:如果A happends-before B,且B happends-before C,那么A happends-before C。
  • start()规则:如果线程A执行操作ThreadB.start(),那么A线程的ThreadB.start()操作happends-before于线程B中的任意操作。
  • join()规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happends-before于A从ThreadB.join()操作成功返回。

5、volatile内存语义

(1)volatile变量的特性:

  • 可见性:对一个volatile变量的读,总能看到(任意线程)对这个volatile变量最后的写入。
  • 原子性:对任意单个volatile变量的读/写具有原子性,但类似volatile++这种复合操作不具有原子性。

(2)volatile写-读的内存语义

当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存。

当读一个volatile变量时,JMM会把该线程对应的本地内存设置为无效。线程接下来将会从主存中读取共享变量。

(3)volatile内存语义的实现

当第二个操作为volatile写,则无论第一个操作是什么都不可被重排序;

当第一个操作为volatile读,则无论第二个操作是什么都不可被重排序;

当第一个操作为volatile读,第二个操作为volatile写,不可被重排序;

在每个volatile写操作前插入一个StoreStroe屏障;在每个volatile写操作后插入一个StoreLoad屏障。

在每个volatile读操作后插入一个LoadLoad屏障;在每个volatile读操作后插入一个LoadStore屏障。

6、锁的内存语义

锁可以让临界区互斥执行,比volatile更强大,volatile只能保证对单个volatile变量的读写具有原子性。

(1)锁的释放和获取的内存语义

当线程释放锁的时候,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中;

当线程获取锁的时候,JMM会把该线程对应的本地内存设置为无效,从而使得被监视器保护的临界区代码必须从主内存中读取共享变量;

7、final域的内存语义

(1)写final域的重排序规则:在构造函数内对一个final域的写入与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不可以重排序。

(2)读final域的重排序规则:初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不可重排序。

猜你喜欢

转载自blog.csdn.net/qq_32133965/article/details/88655588