Java并发实践笔记2021
2021-08-16 16:45:28 1 举报
AI智能生成
根据《Java并发编程实践》一书,整理的学习笔记,
作者其他创作
大纲/内容
线程安全性
概念的理解
一、理解前提术语
共享的概念
某个变量可以被多个线程访问
可变的概念
变量的值在其生命周期内可以发生变化
状态的概念
能够表示系统状态的那些变量称为状态变量
有哪四种状态变量
二、理解线程安全性
出现线程安全问题
本质原因
如何判断线程是否安全
三种解决线程安全问题的思路
java中的几种同步机制
线程安全性
一、定义
复杂的定义
困惑的定义
这些定义让人困惑的两个原因
如何定义线程安全,才不会困惑
理解前提
理解『正确性』
正确的定义
简易版
完全版
二、举例
因数分解 servlet
1、功能说明
2、代码举例
3、此 servlet 线程安全的原因
4、无状态对象,线程安全的原因
1)只有局部变量
A、如何理解局部变量,不会产生线程安全性问题
2)没有共享变量
A、如何理解无状态对象,没有共享状态?
3)为什么说,多个线程访问同一个无状态对象,就好像访问不同的实例一样,也即好像没有访问共享的对象一样
原子性
一、新的需求
假设我们希望为 servlet,增加一个『计数器』来统计所处理的请求数量
二、解决方案
叙述
代码
可能的错误
1、起初
2、过程
3、最后
4、错误原因
三、竞态条件
概念
1、什么是『竞态条件』
1)概念:在并发编程中,可能会因为出现『不恰当的执行时序』而出现『不正确的结果』,『这样的情况』就被称作『竞态条件』。
2)特点
3)解决思路
2、什么时候会出现『竞态条件』
3、为什么竞态条件,会带来线程安全性问题
分类
1、『先检查后执行』类型竞态条件
1)为什么『先检查后执行』类型是竞态条件中最常见的
2)本质
3)举例(没有做思维导图,今后做)
现实生活中『先检查后执行』类型的竞态条件
延迟初始化中的『先检查后执行』类型的竞态条件
2、『读取-修改-写入』类型竞态条件(没有做思维导图,今后做)
存在的问题
执行时序、竞态条件与线程安全的关系
解决方案
1、解决『竞态条件』引起的线程安全性问题的『思路』
1)思路叙述
2)思路解释
A、为什么,要解决『竞态条件』问题,就要解决执行时序不确定的问题?而不是把多线程变为单线程?
B、使用什么方法,解决执行时序不正确的问题?
C、什么是正确的执行时序?
D、如何确保正确的执行时序
2、复合操作
1)严谨地描述复合操作
2)什么是复合操作?
3)如何定义复合操作
A、方法一:加锁机制(下一节讲)
B、方法二:使用一个现有的原子变量类
a、代码
b、解释
c、结论
加锁机制
一、原子变量类的局限性
当前的原子变量类,使用情况?
当前的原子变量类,存在的问题?
1、原子变量可以满足需要的情况
1)对象只有一个状态变量
2)对象有多个状态变量,但是状态变量之间,没有联系,也即不变性条件,不会由多个变量共同来描述
2、原子变量不可以满足需要的情况
1)多个状态变量,共同描述不变性条件
局限性
二、带有缓存的因式分解servlet
目的
带有缓存的因式分解 servlet,线程不安全的实现方式
1、思路
2、代码
3、存在的问题
1)servlet 是线程不安全的
2)UnsafeCachingFactorizer的不变性条件之一
3)解释:为什么不满足不变性条件
4)错误结果
5)解决方案
疑问
三、内置锁
java 如何实现原子性
同步代码块的两个部分
1、第一个部分——作为锁的对象引用
2、第二个部分——作为由这个锁保护的代码块
锁
1、何时获得锁,何时释放锁?
1)获得锁
2)释放锁
2、如何获得内置锁?
3、锁的作用?
1)特点
2)举例解释
『带有缓存的因式分解 servlet』线程安全的实现方式
1、思路
2、特点:简单
3、方法
4、问题
5、代码
四、重入
什么是重入?(内置锁是可重入的)
重入的一种实现方法
1、变量
2、过程
为什么需要『重入』这种功能?
1、叙述
2、代码
3、解释
1)两个同步方法
2)同一把锁
3)什么情况下,线程会请求一个『已经由自己持有的』锁
4)为什么,如果不能重入,就会出现死锁?
用锁来保护状态
共享状态的理解:指各个线程共享的有状态对象等
如果用同步来协调对某个变量的访问,那么在访问这个变量的所有位置上都需要同步。
当使用锁来协调对某个变量的访问时,在访问变量的所有位置上都要使用同一个锁。
对于可能被多个线程同时访问的可变状态变量,在访问它时都需要持有同一个锁,在这种情况下,我们称状态变量是由这个锁来保护的。
每个共享的和可变的变量都应该只有一个锁来保护,从而使维护人员知道是哪一个锁。
活跃性与性能
一、举什么例子说明『活跃性与性能』
第一阶段:不安全的 servlet
第二阶段:没有并发的 servlet
存在的问题
1、叙述
2、问题根本原因
3、可以实现功能——保证线程安全
4、代价很大
新的解决思路
1、叙述
2、注意事项
3、同步只需要加在什么地方?
新的代码
1、解释
1)第一个同步代码块
2)第二个同步代码块
3)因数分解功能之外,引入了两个计数器
4)同步块之外的代码
对象的共享
概念的理解
一、关键字 synchronized 的两个作用
保证原子性
保证内存可见性
二、何时需要确保原子性,内存可见性
什么时候需要保证『原子性』
什么时候需要保证『内存可见性』
三、锁、同步块、阻塞的关系
可见性
一、概念
单线程和多线程,对『内存写入操作』的『可见性』
1、在单线程环境中
2、在多线程环境中
二、举例 说明
叙述
代码
解释
1、共享变量
2、主线程和读线程的功能
3、可能的两种『不希望情况』
1)正确情况一
2)错误情况一
3)错误情况二
A、叙述
B、原因
C、解释——重排序的结果
D、什么是『重排序』
E、出现『重排序』的根本原因
4)几种结果的分析
A、打印出0的原因
B、主线程很长时间没有打印
三、失效数据
什么时候会产生失效数据?
更糟糕的是失效值可能不会同时出现
失效数据带来的错误后果?
非线程安全的可变整数类——Mutablelnteger
1、代码
2、不安全的原因
3、什么时候会出现『失效值问题』
线程安全的可变整数类——SynchronizedInteger
1、代码
2、解释
四、Volatile变量
如何确保可见性
1、含义
2、不重排序
3、不缓存(也即总会返回最新值)
volatile变量和sychronized同步块的区别
『volatile变量』和可见性的关系
1、volatile变量对可见性的影响比volatile变量本身更为重要
2、如何使用
3、作用
4、『volatile变量』类比同步代码块的在使用方法上的比较
5、为什么,不建议过度依赖volatile变量提供的可见性
6、『当且仅当』满足以下『所有条件』时,才应该使用『volatile变量』
1)不需要『确保原子性』的情况下
A、在多个线程进行修改时,一定不能出现竞态条件
B、单个线程进行修改时(一定不会出现竞态条件,你不需要确保原子性)
2)不需要使用更高级的技术
发布与逸出
一、概念
什么是发布(Publish)一个对象
1、叙述
2、举例
不同的需求
1、有时需要『不发布对象』
2、有时需要『发布对象』
3、有时需要『发布对象,并确保对象的线程安全性』
为什么说,发布对象可能会破坏对象的『线程安全性』
1、叙述
2、举例
什么是逸出(Escape)
发布对象的几个方法
发布对象——举例说明
1、代码
2、解释
3、结论
逸出 - 举例说明
1、代码
2、解释
3、什么是 Alien 外部方法
1)叙述
2)包括
4、为什么把一个对象,传递给某个外部方法时,就『相当于』发布了这个对象?
5、解决方案 - 如何才能不逸出
1)叙述
2)代码
3)解释
发布内部类实例,会连带发布其外围类实例
1、代码
2、解释
二、安全的对象构造过程(需进行实践)
防止发布(线程封闭)
一、基本概念
实现『线程安全』的几个办法
1、同步
2、不共享:『线程封闭』
1)叙述
2)原因
3)如何实现一个对象(数据的一种),只由单个线程独占
3、不可变
『同步』技术和『线程封闭』技术的比较
Java 语言及其核心库中,维持『线程封闭性』的一些机制
1、局部变量
2、ThreadLocal 类
线程封闭技术举例
1、例子叙述
2、如何在程序中,实现 Connection 对象的线程封闭
3、为什么JDBC规范,并不要求 Connection 对象必须是线程安全的
二、三种分类
Ad-hoc 线程封闭
1、定义
2、特征
3、这是一个看上去很牛逼,实际上很弱鸡的线程封闭技术,果断放弃
栈封闭
1、『栈封闭』与『线程封闭』的关系?
2、『栈封闭』的充分条件是什么
3、为什么说,『封闭在执行线程中』是『局部变量』的固有属性
4、『栈封闭』相较于『Adhoc线程封闭』的优点
5、代码举例
解释
6、为什么说,对于『基本类型的局部变量』无论如何都不会破坏『栈封闭性』
1)叙述
2)我的猜想
3)举例
7、在『维持对象引用』的『栈封闭性』时,程序员『需要多做一些工作』以确保被『引用的对象不会逸出』
1、当前『集合animals』的引用,『封闭在执行线程』中
2、发布引用,对象逸出
3、引用、对象的存储位置
ThreadLocal 类
1、原理
2、作用
3、举例说明
1)单线程中的『数据库连接——Connection对象』
2)多线程中的『数据库连接——Connection对象』
4、线程封闭的Connection对象,举例说明
1)代码
2)解释
6、如何『单线程应用程序』移植到『多线程环境』
7、在实现应用程序框架时,大量使用了ThreadLocal
8、开发人员经常滥用ThreadLocal
不变性
一、概念
带来『原子性』和『内存可见性』的根本原因
此前介绍了许多,与原子性和可见性,相关的问题
根本原因
一种解决思路
什么是『不可变对象』
为什么说,线程安全性是『不可变对象』的固有属性之一
不可变对象一定是线程安全的
为什么说,不可变对象很简单
为什么『不可变对象』相较于『可变对象』更加可信
定义不可变对象的两种方法
1、不可变对象内部,使用不可变对象来管理它们的状态(也即不可变对象嵌套,也即权威final)
2、不可变对象内部,使用可变对象来管理它们的状态,但是可变对象,不能对外发布
所有的域都声明为final类型,就是不可变性对象吗?
满足不可变对象的几个条件
对象创建以后,其状态就不能修改
对象的所有域,都是final类型
对象是正确创建的(在对象的创建期间,this引用没有逸出)
在不可变对象的内部仍可以使用可变对象来管理它们的状态,但需要满足一定的条件
1、代码
2、解释:不清楚为什么是这样
3、条件
二、Final
final的作用?
什么时候使用『不可变对象』
即使对象是可变的,final也可以『限制对象的可变性』
良好的编程习惯
1、『除非需要更高的可见性,否则应该将所有的域都声明为私有域』
2、『除非需要某个域是可变的,否则应该将其声明为final域』
三、使用Volatile类型发布【不可变对象】
Volatile的定义
Volatile有什么特性?
使用Volatile的前提
Volatile可见性
Synchronized与volatile区别
四、定义不可变对象的两个方法
1、使用 final
2、不定义 setter方法
3、不定义 getter 或其它方法,返回状态的引用(如果状态是一个对象的情况下)
安全发布
一、安全发布概念
1、什么时候你不能发布对象
2、如何确保对象不被发布
3、什么情况下,你需要发布对象
4、不安全的发布
1)代码
2)疑惑
3)原因
4)解释
二、不正确的发布:正确的对象被破坏
1、什么是『未被正确发布』
2、在『未被正确发布的对象』中存在哪两个问题
3、以上三种情况,没有理解是什么回事!!
三、不可变对象与初始化安全性
要求
四、安全发布的常用模式
在静态初始化函数中初始化一个对象引用。
将对象的引用保存到volatile类型的域或者AtomicReference对象中
将对象的引用保存到某个正确构造对象的final类型域中
将对象的引用保存到一个由锁保护的域中
五、安全的共享对象
策略
线程封闭
只读共享
线程安全共享
保护对象
对象的组合
设计线程安全的类
一、概念的理解
设计线程安全类的三个基本要素
1、找出『构成对象状态』的『所有变量』
2、找出约束状态变量的『不变性条件』
3、建立『对象状态』的『并发访问』『管理策略』
如何分析『对象的状态』?
1、如果对象中所有的域都是『基本类型的变量』
1)叙述
2)举例
2、如果在对象的域中引用了其他对象
1)叙述
2)举例
什么是同步策略(Synchronization Policy)
1、说法一
2、说法二
为什么必须将『同步策略』写为『正式文档』
二、收集同步需求
什么是收集同步需求
不可变条件
1、在『并发访问的情况下』,为什么要防止,线程的『不变性条件』,遭到破坏
2、如何防止,类的『不变性条件』,遭到破坏
3、什么是『状态空间』
4、『状态空间』大小与『可能状态』分析难度
5、『不可变条件』的作用?
6、『不可变条件』与『状态空间』的举例说明:
1)例子
2)状态空间
3)不可变条件
4)关系
后验条件
1、『后验条件』的作用?
2、『后验条件』举例
3、什么时候,不需要『后验条件』?
4、举例说明——不需要『后验条件』的情况
三、如何满足同步需求
换一种说法
如何满足不变性条件
1、叙述
2、举例
如何满足后验条件
方式比较
1、封装——》满足『不变性条件』——》避免产生『无效状态』
2、同步——满足『后验条件』——》避免产生『无效的状态转换』
3、说明
存在的问题
1、叙述
2、存在着什么特殊情况
3、举例
4、为什么『包含多个变量的不变性条件』将带来原子性需求
为什么『不变性条件』和『后验条件』都会带来原子性要求
1、『不变性条件』可能带来原子性要求
2、『后验条件』可能带来原子性要求
3、举例说明:『后验条件』与『不变性条件』是不同的
1)差异叙述
2)共同合作
四、依赖状态的操作
『先验条件』
1、作用叙述
1)『先验条件』
2)『后验条件』
2、举例
3、对比
1)『先验条件』
2)『后验条件』
什么是『依赖状态的操作』
『单线程和多线程』中的『先验条件』
1、在『单线程程序』中
2、在『并发程序』中
3、无论在『单线程程序』还是在『并发程序』中,都需要等到『先验条件』为真,才会执行该操作
如何实现『依赖状态的操作』?也即如何『实现』某个『等待先验条件为真时』才执行的『操作』
1、使用在『平台与类库中』提供的『各种底层机制』来『创建依赖状态的类』(第14章将介绍)
2、一种更简单的方法——利用『现有类库』:(第5章将介绍)
3、阻塞类是同步工具类的子集,阻塞类包括——阻塞队列(Blocking Queue)或信号量(Semaphore)
五、状态的所有权
概念
1、对象的状态,包括哪些
1)叙述
2)解释
A、什么是对象图——从对象图与类图的关系说起
a、不同点
b、相同点
0 条评论
下一页