concurrent包中Lock有关的知识点整理

concurrent包中Lock相关的知识点整理

之前看过一些多线程的东西,今天温习了一下,阅读了Java Concurrent Programming 第四节 锁 整理如下:


与锁相关的切入点是两个最基本的接口:Lock和Condition。

前者是对逻辑层面“锁”的抽象,所以专注于上锁(lock)和解锁(unlock)

后者则侧重表达操作系统底层对线程状态流转的控制,提供了await和signal。

顺便吐槽一句,大部分文章里都把condition翻译成“条件”,感觉生硬。condition也有情况、状况的意思,如race condition(注1);此处感觉是指锁定同一份资源时针对不同情况的调度。例如在生产者和消费者中间有个传送带BlockingQueue,每当写入Queue都要获得同一把写锁,然而锁定后可以有两种情况:一种是Queue满了无法入队,还有一种是Queue空了无法出队。线程因为不同的情况而进入wait状态,因此也等待对应Condition的信号来激活。

注1:race condition, wiki翻译为”竞争条件“,我觉得应该直译成竞争状态”(意在强调多线程互相竞争状态下带来的危害);意译为竞态危害(所以才会有个别名race hazard)或并发安全问题英文wiki1节就提到了什么情况下竞争状态可以无害(non-critical)。看了许多对race condition的解释,大部分讲的都是并发情况下由于未知的读写执行顺序导致变量值不稳定,即这篇文章里讲到的第1sequencing problems(一下就联想到了修正后的JMM里的happens-before法则,都是关于sequence)但罕有文章提到race condition的第2点, locking problems。(依稀记得以前看过老赵的一篇文章,讲尾递归写的不好导致live lock,属race condition的范畴。) 


几种抽象的锁:

ReadWriteLock:当一个线程申请读锁时,只要写入锁没有被其他线程持有,那么会立刻拥有读锁。当一个线程申请写入锁时,其他线程不能持有读锁和写入锁,否则会一直等待。写锁是排他锁,而读锁可以同时被多个线程持有。

ReentrantLock:考虑到递归的情况,有些锁需要被一个线程重复获取,就形成了“可重入锁”。

ReentrantReadWriteLock:同时具备上述两种功能。

几种涉及锁的场景:

实际编程中,对于某些常见的场景,直接面向锁编程仍然太罗嗦,所以诞生了以下几种工具类对各个场景予以直观的支持。

  • Semaphore(信号量):把有限个资源提供给多线程竞争,还提供不同的竞争策略(公平/不公平),类比停车场车位紧张的场景。
  • Mutex (互斥):即信号量为1的情况,类比只有一个洗手间的场景。
  • Latch(字面义:闭锁、门闩):等待直到n个目标线程触发了某个事件( CountDownLatch:A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.)对于CountDownLatch触发事件对应的动作是计数器减1,即CountDownLatch.countDown();注意它不能reset,因此是一次性的。类比百米冲刺的终点线,等待直到所有队员撞线后,再执行颁奖典礼。
  • Barrier(字面义:屏障、栅栏) :在几个切面上阻拦先抵达的线程使之等待,直到所有目标线程到达,以便所有线程继续从同一个切面开始并发执行(CyclicBarrier : A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point. )注意它可以reset,因此每个切面可以复用同一个CyclicBarrier。这篇帖子提到CyclicBarrier可以很方便的协助实现MapReduce这样的复杂的并行计算场景,真是一语中的,不过现实生活中有否形象的例子呢?我想到了诺曼底登陆。假设德军在诺曼底有n道防线,击溃每一道防线都需要不同数量的盟军士兵登陆集结后发起冲锋;因为当时盟军已经获得了海路的绝对控制权,士兵被源源不断地送上岸,同时也会在战斗中牺牲。上述例子中,每个盟军士兵都是一个线程,而防线即Barrier,每道防线等待特定数量的活着线程抵达集结,便被攻破,紧接着收缩形成下一道防线,相当于reset Barrier溃败的阈值。

粗读完一遍,有个问题,wait和await有什么区别?

这篇文章描述了传统的wait的作用,这篇文章试图描述两者的不同。

结论:本质上都是使线程释放锁并进入wait状态,主要的区别有二。一是相比synchronized,lock可以派生出多个condition触发await,语义上更直观;二是调用传统的wait后,要么通过notify随机激活某个线程,要么通过notifyAll激活所有线程允许其竞争锁;而await后支持更丰富的锁竞争策略,如公平的(按await的顺序排队)和不公平的等等。