<Java Concurrency in Practice> 读书笔记 - Part4: 高级主题 (TODO)

Table of Contents

第13章 显式锁

Lock 与 RenentrantLock

ReentrantLock 实现了 Lock 接口, 并提供了与 synchronized 相同的互斥性和内存可见性.
在获取 ReentrantLock 时, 有着与进入同步代码块相同的内存语义, 在释放 ReentrantLock 时, 同样有着与退出同步代码块相同的内存语义.
此外, 与 synchronized 一样, ReentrantLock 还提供了可重入的加锁语义.

性能考虑因素

在 Java6 之后内置锁与 ReentrantLock 的性能基本差不多

性能是一个不断变化的指标, 如果在昨天的测试基准中发现 X 比 Y 更快, 那么在今天就可能已经过时了.

公平性

在 ReentrantLock 的构造函数中提供了两种公平性选择: 创建一个非公平的锁(默认)或者一个公平的锁.

  •  公平锁. 线程将按照他们发出请求的顺序来获得锁.
  • 非公平锁. 当一个线程请求非公平的锁时, 如果在发出请求的同时该锁的状态变为可用, 那么这个线程将跳过队列中所有的等待线程并获得这个锁.

公平性把性能降低了约两个数量级. 不必要的话, 不要为公平性付出代价.

在 synchronized 和 ReentrantLock 之间进行选择

在一些内置锁无法满足需求的情况下, RenentrantLock 可以作为一种高级工具. 当需要一些高级功能时才应该使用 ReentrantLock,
这些功能包括: 可定时的, 可轮询的与可中断的锁获取操作, 公平对列, 以及非块结构的锁. 否则应该优先使用 synchronized.

读 - 写锁

互斥是一种传统的加锁策略, 虽然可以避免“写/写”冲突和“写/读”冲突, 但同样也避免了“读/读”冲突.
如果大部分访问操作都是读操作, 可以使用读/写锁, 一个资源可以被多个读操作访问, 或者被一个写操作访问, 但两者不能同时进行.

ReadWriteLock中的一些可选实现包括:

  • 释放优先. 当一个写入操作释放写入锁时, 并且队列中同时存在读线程和写线程, 那么应该优先选择读线程, 写线程, 还是最先发出请求的线程?
  • 读线程插队. 如果锁是由读线程持有, 但有写线程正在等待, 那么新到达的读线程能否立即获得访问全, 还是应该在写线程后面等待?
  • 重入性. 读取锁和写入锁是否是可重入的?
  • 降级. 如果一个线程持有写入锁, 那么它能否在不释放该锁的情况下获取读取锁?
  • 升级. 读取锁能否优先于其他正常等待的读线程和写线程而升级为一个写入锁?

ReentrantReadWriteLock 为这两种锁都提供了可重入的加锁语义.
在公平锁中, 等待时间最长的线程将优先获得锁. 如果这个锁由读线程持有, 而另一个线程请求写入锁, 那么其他线程都不能获得读取锁, 直到写线程使用完并且释放了写入锁.
在非公平锁中, 线程获得访问许可的顺序是不确定的. 写线程降级为读线程是可以的, 但从读线程升级为写线程则是不可以的(这样做会导致死锁).

第14章 构建自定义的同步工具

TODO

第15章 原子变量与非阻塞同步机制

TODO

Author: Saul Lawliet

Created: 2022-03-21 Mon 15:34