《Java并发编程的艺术》方腾飞

第1章 并发编程的挑战

1.上下文切换:cpu通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个
任务。但是,在切换前会保存上一个任务的状态,以便下次切换回这个任务的时,可以再加载这个任务
的状态。所以任务从保存到加载的过程就是一次上下文切换。
2.死锁:是指两个或多个线程相互持有对方的资源导致这些线程处于等待状态无法执行。
3.资源限制。
总结:并发编程的目的就是为了让程序更快的运行,但是会遇到很多挑战,比方说上下文切换的问题、
死锁以及资源的限制等问题。其中JDK提供的并发容器和工具类。

第2章 Java并发机制的底层原理

Java代码在编译后会生成字节码,字节码会被类加载器加载到JVM中执行,最终会转化成汇编指令在
CPU上执行,Java并发机制都依赖于JVM的实现和CPU的指令。

2.1 volatile的应用

2.2 synchronized的实现原理与应用

synchronized实现同步的基础:Java中的每一个对象都可以作为锁。
对于普通同步方法,锁是当前实例对象。
对于静态同步方法,锁是当前类的Class对象。
对于同步方法块,锁是synchronized括号里配置的对象。

第3章 Java内存模型

3.1 Java内存模型的基础

3.1.1 并发编程模型的两个关键问题

在并发编程中需要处理两个问题:一个是线程之间如何通信,一个是线程之间的同步。

通信是指线程之间以何种方式来交换信息在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递,在
共享内存的并发模型里,线程之间共享程序的公共状态,通过写读内存中的公共状态来进行隐式通信,在消息传递的并
发模型里没有公共状态,线程之间必须发送消息显示的通信。

同步是指程序中用于控制不同线程间操作发生相对顺序的机制。在共享内存的并发模型里,同步是显示进行的。程序员
必须显示指定某段代码或方法需要在线程之间互斥执行。

Java并发采用的是共享内存模型,Java线程之间的通信总是隐式的进行,整个通信过程对程序员完全透明。

3.1.2 Java内存模型的抽象结构

在Java中所有的实例域、静态域和数组元素都存储在堆内存中,堆内存在线程之间共享。本章用共享变量来代指实例
域、静态域和数组元素。局部变量不会在线程之间共享,它们不会存在内存可见性问题,也不受内存模型影响。

从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(Main Memory)中,
每个线程都有一个私有的本地内存(Local Memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是
JMM一个抽象概念,并不真实存在,它涵盖了缓存、写缓冲区、寄存器以及其它的硬件和编译器优化。
在这里插入图片描述
从3-1图来看,线程A和线程B之间的通信必须经历这两个步骤:
1)线程A把本地内存中更新的共享变量副本刷新到主内存中去。
2)线程B到主内存中读取线程A已经更新过的共享变量。

从整体来看,JMM控制主内存与每个线程的本地内存之间共享变量的交互来为Java程序员提供内存可见性的保证

3.1.3 从源代码到指令序列的重排序

在程序运行时,为了提高性能,编译器和处理器往往会对指令做重排序。
1)编译器优化的重排序
2)指令级并行的重排序
3)内存系统的重排序
在这里插入图片描述
上述的1属于编译器重排序,2和3属于处理器重排序。这些重排序可能会导致多线程程序出现内存可见性的问题
对于编译器重排序JMM的编译器重排序规则会禁止特定类型的编译器重排序(不是所有的编译器重排序都要禁止)。
对于处理器重排序JMM的处理器重排序规则会要求Java编译器在生成指令顺序时,插入特定类型的内存屏障
(Memory Barriers)指令,通过内存屏障指令来禁止特定类型的处理器重排序。

3.1.4 并发编程模型的分类

3.1.5 happens-before简介

从JDK5开始,Java使用新的JSR-133内存模型,JSR-133使用happens-before的概念来阐述操作之间的内存可见性
在JMM中,如果一个操作执行结果需要对另一个操作可见,那么这两个操作之间必须存在happens-before关系。这两个
操作既可以是在一个线程之内,也可以是多个线程之间

一个happens-before规则对应于一个或多个编译器和处理器重排序规则。
与程序员密切相关的happens-before规则:
程序顺序规则:程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作。
监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。
volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。
传递性:如果A happens-before B,且B happens-before C,那么A happens-before C。
注意:两个操作之间存在happens-before关系,并不意味着前一个操作必须要在后一个操作之前执行!happens-before仅仅
要求前一个操作执行结果对后一个操作可见,且前一个操作按顺序排在第二个操作之前。

3.2 重排序

重排序是指编译器和处理器为了优化程序性能而对指令顺序进行重新排序的一种手段。

3.2.1 数据依赖性

如果两个操作访问同一变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖性。
数据依赖分为三种:
在这里插入图片描述
上面3种情况,只要重排序两个操作的执行顺序,程序的执行结果就会改变。
编译器和处理器在重排序时会遵守数据依赖性,编译器和处理器不会改变存在数据依赖关系的两个操作之间的顺序。

3.2.2 as-if-serial

as-if-serial语义:不管怎么重排序,程序的执行结果不能改变。
为了遵守as-if-serial语义,编译器和处理器不会对存在数据依赖性的两个操作重排序。

3.2.3 程序顺序规则

3.2.4 重排序对多线程的影响

3.3 顺序一致性

3.4 volatile的内存语义

当声明共享变量为volatile后,对这个变量的读写会很特别。

3.4.1 volatile的特性

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

猜你喜欢

转载自blog.csdn.net/shiquan202101/article/details/112297598