写点什么

Java Core 「9」J.U.C 同步工具类 -1

作者:Samson
  • 2022 年 6 月 16 日
  • 本文字数:2630 字

    阅读完需:约 9 分钟

摘要

java.util.concurrent 包是并发编程常用的包,在之前的文章《Java Core 「4」java.util.concurrent 包简介》中我们介绍了该包中的基本组成。今天的文章中,我们将继续深入学习其中的一部分工具类。

01-LockSupport

Basic thread blocking primitives for creating locks and other synchronization classes.

LockSupport 是 java.util.concurrent 包中的一个基础类。它包含一个许可(Permit,可以理解为最多为 1 的信号量)。该工具类中包含三类接口:

  • unpark(Thread),标记许可为可用状态。如果线程 t 因调用 park 而阻塞,那么其他线程调用此接口,会使线程 t 从阻塞状态恢复;如果线程 t 尚未调用 park 而阻塞,那么等 t 执行到 park 时,则会直接返回而不会阻塞。

  • park,如果许可可用,则直接返回;否则,阻塞当前线程,直到以下三种情况之一发生:1)其他线程调用 unpark(c),c 为当前线程;2)其他线程调用 c.interrupt() 中断当前线程,c 为当前线程;3)park 方法 spuriously(不合逻辑地,无故)返回。park(),不带时间参数 parkNanos(long)、parkUntil(long),带时间参数的上述三种还分别有带 Blocker 对象的版本:park(Object)、parkNanos(Object, long)、parkUntil(Object, long)

  • getBlocker(Thread),能后获得带 Bloker 参数版本 park 方法设置的 Bloker 对象。多用于监控和诊断程序识别线程阻塞的原因。

LockSupport 中的 park 和 unpark 皆是通过 jdk.internal.misc.Unsafe 类实现。

02-ReentrantLock

A reentrant mutual exclusion Lock with the same basic behavior and semantics as the implicit monitor lock accessed using synchronized methods and statements, but with extended capabilities.

ReentrantLock 是 java.util.concurrent 包中提供的一个排他锁实现,也是开发者最常使用的同步锁之一。它与 synchrinized 底层使用的 monitor 监视器锁有基本相同的行为和语义。

ReentrantLock 也是基于 AQS 实现,内部类 ReentrantLock#Sync 实现了 AQS 机制,并派生出两个子类 NonfairSync 和 FairSync 两个类。



ReentrantLock 的关键 API:

  • lock,如果锁未被任何线程获得,则当前线程获得锁;否则,分两种情况:如果持锁线程为当前线程,则立即返回;如果持锁线程不是当前线程,则阻塞当前线程,直至锁被释放。

  • unlock,尝试释放锁,如果当前线程非持锁线程,则抛 IllegalMonitorStateException 异常;否则,持锁数量(hold count)减一。当值为 0 时,锁被释放掉。

推荐写法:

lock.lock();try {} finally {	lock.unlock();}复制代码
复制代码

上述的 lock/unlock 过程与 monitor 锁语义相同;但 ReentrantLock 可以提供更多、更灵活的同步控制:

  • lockInterruptibly(),与 lock 基本无异,只是当持锁线程非当前线程时,当前线程阻塞,直至当前线程获锁或当前线程被其他线程中断。而且,如果当前线程在调用 lockInterruptibly 方法之前,其已处于中断状态 c.isInterrupted() == true,或在请求锁期间被其他线程中断,则会抛 InterruptedException 异常。

  • tryLock() / tryLock(long, TimeUnit),尝试请求锁,如果锁尚未被其他线程获得或已被当前线程获得,则将持锁数量递增一,并立即返回 true;否则,立即返回 false。当前线程通过此两种方法获取锁,若不能获取锁,并不会阻塞当前线程。两个线程 a 和 b 执行 synchronized 代码块时,若线程 b 不能获得对象 obj 的监视器锁,线程 b 会被挂起,直到线程 a 退出 synchronized 代码块。此外,tryLock 不带参数的版本并不会遵循公平、非公平的配置,它使用的是非公平策略,从其源码中可以看出:

    public boolean tryLock() { return sync.nonfairTryAcquire(1); } 复制代码

  • newCondition(),返回 Condition 对象,通过 await / signal / signalAll 对线程进行更精确的同步控制。Condition 接口中方法是对标 Object 类中 wait / notify / notifyAll 方法的。

03-ReentrantReadWriteLock

ReentrantReadWriteLock 是 JDK 提供的 ReadWriteLock 接口实现类。ReadWriteLock 接口定义了获取一组互相关联锁的方法:

  • readLock,获得读锁,只读操作,在没有线程独占写锁时,多个线程可同时获取读锁。

  • writeLock,获得写锁,排他锁,只能有一个线程持有。

ReadWriteLock 是针对多读、少写场景,提高并发效率的优化。假设不去分读写操作,一律使用上节介绍的 ReentrantLock,虽然可以实现并发线程的线程安全性。但是多个线程的读操作(不修改数据)也需要互斥锁,显然降低了并发效率。

ReentrantReadWriteLock 也是基于 AQS 实现,内部类 ReentrantReadWriteLock#Sync 实现了 AQS 机制,并派生出两个子类 NonfairSync 和 FairSync 两个类。内部类 ReadLock 和 WriteLock 都是 Lock 的实现类。



ReadLock 和 WriteLock 是两个互相关联的锁:

  • ReadLock#lock(),若写锁未被其他线程持有,则立即获得读锁;否则,当前线程被阻塞,直至能够获取到读锁。与 ReentrantLock 一样,ReadLock 实现了 tryLock() / tryLock(long, TimeUnit) / lockInterruptibly() 方法,无参数的 tryLock() 不遵循公平、非公平设置,若可获取锁则立即抢占。

  • ReadLock#unlock(),尝试释放读锁,若当前线程并未持读锁,则抛 IllegalMonitorStateException 异常;否则,持读锁线程数量递减一。当持读锁线程数量为 0 时,读锁被释放,其他线程可获取写锁

  • WriteLock#lock(),若读锁、写锁皆未被其他线程持有,则当前线程获得写锁;否则,如读锁、写锁已被其他线程持有,则阻塞当前线程直至其可以获得写锁;如当前线程已经持有写锁,则写锁持有数量递增一(重入)。同样,WriteLock 实现了 tryLock / tryLock(long, TimeUnit) / lockInterruptibly() 方法。

  • WriteLock#unlock(),尝试释放锁,若当前线程并未持此写锁,则抛 IllegalMonitorStateException 异常;否则,持写锁数量递减一。当持写锁数量为 0 时,写锁被释放。

从上面方法的语义中可以推测出,读锁与读锁可并发,写锁与读锁、写锁与写锁不可并发,需同步。这里还有一点需要注意:若当前线程持有写锁,当其请求读锁时,也可获得。若当前线程持有读锁,当其请求写锁时,无法获取。原因是,读锁持有者(reader)数量为零时,才允许获取写锁。


历史文章推荐

Java Core 「8」字节码增强技术

Java Core 「7」各种不同类型的锁

Java Core「6」反射与 SPI 机制

Java Core「5」自定义注解编程

Java Core「4」java.util.concurrent 包简介

Java Core「3」volatile 关键字

Java Core「2」synchronized 关键字

Java Core「1」JUC- 线程基础

发布于: 刚刚阅读数: 3
用户头像

Samson

关注

还未添加个人签名 2019.07.22 加入

还未添加个人简介

评论

发布
暂无评论
Java Core 「9」J.U.C 同步工具类-1_学习笔记_Samson_InfoQ写作社区