Java并发编程实践——读书笔记(一)
《Java Concurrency in Practice》第一部分的阅读总结
关键字:并发、锁、线程安全、共享对象、并发容器、信号量
目录
第一章 介绍
1.1 并发的简短历史
1.2 线程的优点
1.3 线程的风险
1.4 线程无处不在
第二章 线程安全
2.1 什么是线程安全性
2.2 原子性
2.3 锁
2.4 用锁来保护状态
2.5 活跃度与性能
第三章 共享对象
3.1 可见性
3.2 发布和逸出
3.3 线程封闭
3.4 不可变性
3.5 安全发布
第四章 组合对象
4.1 设计线程安全的类
4.2 实例限制
4.3 委托线程安全
4.4 向已有的线程安全类添加功能
4.5 同步策略的文档化
第五章 构建块
5.1 同步容器
5.2 并发容器
5.3 阻塞队列
5.4 阻塞和可中断的方法
5.5 Synchronizer
5.6 为计算结果建立高效、可伸缩的高速缓存
第一章
-
线程的风险
-
竞争场景(race condition)
多个线程并发地对共享数据进行操作
-
活跃度失败(liveness failure)(程序无法继续执行或退出)
如果安全意味着”什么坏事都没有发生“,那么活跃度关注的则是”好事最终发生了“
-
性能危险(performance)
...,线程仍然会给运行时带来一定程度的开销。上下文切换(Context switches) ,...,这在多个线程组成的应用程序中是很频繁的,并且带来巨大的系统开销。...,CPU的时间会花费在对线程的调度上而不是在运行上。
-
第二章
-
线程的安全性
正确性意味着一个类与它的规约保持一致。良好的规约定义了用于强制对象状态的不变约束(invariants)以及描述操作影响的后验条件(postcoditions)
一个类是线程安全的,是指在被多个线程访问时,类可以持续进行正确的行为
-
原子性
假设有操作A和B,如果从执行A的线程的角度看,当其他线程执行B的时候,耀目B全部执行完成,要么一点都没执行,这样A和B互为原子操作
-
Java.uitl.concurrent.atomic包中包括了原子变量类(atomic variable),这些类实现了数字和对象引用的原子转换(该对象的操作都是原子的)
- AtomicLong(基本类型对应的原子对象)
- AtomicReference<V>(一些对象引用对应的原子对象)
-
复合操作——”读-改-写“和”检查再运行“
- 这是两类常见的看似原子,但是却不是原子的操作。读改写对应赋值语句,检查再运行对应条件判断
-
-
锁
-
内部锁
- synchronized(锁对象的引用,锁代码块)
- 方法级别的synchronized,锁对象是方法的调用者。静态方法的synchronized,锁对象是对应的调用者对象的class对象
- 内部锁是一种互斥锁,至多只有一个线程可以拥有锁,但是内部锁是可重入的。
-
重进入(Reentrancy)
线程在试图获得它自己占优的锁的时候,如果请求成功,那么该锁是可重入的。
-
-
锁对状态的保护
对于每个可被多个线程访问的可变状态变量,如果所有访问它的线程在执行时都占有同一个锁,这种情况下,我们称这个变量是由这个锁保护的
- 内部锁只能确保一件事,一个线程获得对象的锁之后,将阻塞其他线程获得这个锁
- Vector 和Hashtable
- 通过使用对象的内部锁(synchronized关键字)来封装所有的可变状态
对于每一个涉及多个变量的不变约束,需要同一个锁保护其他所有的变量
第三章
-
可见性(defined)
在没有同步的情况下,编译器、处理器,运行时安排操作的执行顺序可能完全出入意料。在没有进行适当同步的多线程程序中,尝试推断那些”必然"发生在内存中的动作,你总会判断错误。
可见性保证了,内存中的值是已定义的(也就是能够判断出来的),可能听着还是有些抽象。举个例子,多个线程对一个64位的long long变量进行值的修改,那么可能线程A正在修改变量的高32位地址,而线程B在修改变量的低32位地址,这样最后的产生的值就是undefined,也就是无法判断的。
-
可见性比并发的要求弱, 意味着数据可能过期
-
Volatile
- 轻量级的同步机制
- 只保证了对应变量引用的内存是“可见的”,不保证数据同步 。
- 从主存读,并且写入内存。(不存在cpu寄存器中,性能损失)
-
锁不仅仅是关于同步与互斥的,也是关于内存可见的。为了保证所有线程都能够看到共享的、可变变量的最新值,读取和写入线程必须使用公共的锁进行同步
-
-
对象的发布与逸出(publishing & escape)
- 发布:使它能够被当前范围之外的代码所使用
- 将对象的引用存储到公共静态域中
- 从非私有方法中返回引用
- 发布一个对象,同样也发布了该对象所有非私有域所引用的对象
- 逸出:未经计划的发布
- 发布:使它能够被当前范围之外的代码所使用
-
线程封闭
-
局部变量
-
ThreadLocal类
ThreadLocal允许你将每个线程与持有数值的对象关联在一起。ThreadLocal提供了get和set访问器,为每个使用它的线程维护一份单独的拷贝。所以get总是返回由当前执行线程通过set设置的最新值。
本质是全局设置一个容器,通过判断currentThread,然后存入容器中来实现。
-
单一线程对Volatile变量进行写操作。
-
-
不可变性(不可变对象)
- 不可变对象:只有访问器方法,可变状态被封装
- 不可变对象永远是线程安全的(可变对象的final引用,不叫不可变)
第五章
-
同步容器
-
Vector和Hashtable
-
Collections.synchronizedXxx(Xxx xxx)方法创建的同系列,如:
List t = Collections.synchronizedList(new ArrayList()) ;
-
不要轻易使用/隐藏使用同步容器的toString方法,由于被synchronized关键字修饰,串行,所以可能非常耗时。
-
-
并发容器
同步容器通过对容器的所有状态进行串行访问,从而实现了它们的线程安全。这样做的代价是削弱了并发性。并发容器就是在此基础上进行了设计,牺牲了部分的同步性来换取并发性能。
- Queue
- BlockingQueue
- LinkedBlockingQueue
- ArrayBlockingQueue
- PriorityBlockingQueue
- SynchronousQueue
- ConcurrentLinkedQueue
- Deque
- ArrayDeque
- LinkedBlockingDeque
- BlockingQueue
- Map
- ConcurrentHashMap—— Hashtable/ synchronizedMap
- ConcurrentSkipListMap ——synchronizedSortedMap
- Set
- ConcurrentSkipListSet —— synchronizedSortedSet
- List
- CopyOnWriteArrayList —— synchronizedList
- 读不同步,写同步
- CopyOnWriteArrayList —— synchronizedList
- Queue
-
阻塞队列(Blocking Queue) & 生产者-消费者模式
- 阻塞队列天然支持生产者-消费者模式
- Deque与窃取模式
-
Synchronizer(同步器)
- latch(闭锁)
- 同步开始与结束
- semaphore(信号量)
- 实现阻塞队列
- barrier(关卡)
- cycle
- latch(闭锁)