锁是用来控制多个线程访问共享资源的方式,一般来说,一个锁能够防止多个线程同时访问共享资源(但是有些锁是可以允许多个线程并发的访问共享资源,比如读写锁)。在 Lock 接口出现之前,Java 程序是靠 synchronized 关键字实现锁的功能的,而 JavaSE 5 之后,并发包中新增了 Lock 接口(以及相关类),只是在使用时需要显式地获取和释放锁。虽然它缺少了 (通过 synchronized 块或者方法所提供的)隐式获取和释放锁的便捷性,但是却拥有了锁获取与释放的可操作性、可中断的获取锁以及超时获取锁等多种 synchronized 关键字所不具备的同步特性。
使用 synchronized 关键字将会隐式地获取锁,但是它将锁的获取和释放固化了,也就是先获取再释放。当然,这种方法简化了同步的管理,可是扩展性没有显式的锁获取和释放来的好。例如,针对一个场景,手把手进行锁获取和释放,先获得锁 A,然后再获取锁 B,当锁 B 获得后释放锁 A 同时获取锁 C,当锁 C 获得后再释放锁 B同时获取锁 D,以此类推。这种场景下,synchronized 关键字就不那么容易实现了,而使用 Lock 却容易许多。
Lock 的使用也很简单,以下代码是 Lock 的使用方式。
1 | Lock lock = new ReentrantLock(); |
在 finally 块中释放锁,目的是保证在获取到锁之后,最终能够被释放。
不要将获取锁的过程写在 try 块中,因为如果在获取锁(自定义锁的实现)时发生了异常,异常抛出的同时,也会导致锁无故释放。
Lock 接口提供的 synchronized 关键字所不具备的主要特性如下表。
特性 | 描述 |
---|---|
尝试非阻塞地获取锁 | 当前线程尝试获取锁,如果这一时刻锁没有被其他线程获取到,则成功获取并持有锁 |
能被中断地获取锁 | 与 synchronized 不同,获取锁的线程能够响应中断,当获取到锁的线程被中断时,中断异常将会被抛出,同时释放锁 |
超时获取锁 | 在指定的截止时间之前获取锁,如果截止时间到了仍旧无法获取锁,则返回 |
Lock 是一个接口,它定义了锁获取和释放的基本操作,Lock 的 API 如下表所示。
方法名称 | 描述 |
---|---|
void lock() | 获取锁,调用该方法当前线程将获取锁,当锁获得后,从该方法返回 |
void lockInterruptibly() | 可中断地获取锁,和 lock() 方法的不同之处在于该方法会响应中断,即在锁的获取中可以中断当前线程 |
boolean tryLock() | 尝试非阻塞的获取锁,调用该方法后立刻返回,如果能够获取则返回 true,否则返回 flase |
boolean tryLock(long time, TimeUnit unit) throws InterruptedException | 超时的获取锁,当前线程在以下3中情况下会返回:①当前线程在超时时间内获得了锁 ②当前线程在超时时间内被中断 ③超时时间结束,返回 false |
void unlock() | 释放锁 |
Condition newCondition() | 获取等待通知组件,该组件和当前的锁绑定,当前线程只有获得了锁,才能调用该组件的 wait() 方法,而调用后,当前线程将释放锁 |
这里先简单介绍一下 Lock 接口的 API,随后的章节会详细介绍同步器 AbstractQueuedSynchronizer 以及常用 Lock 接口的实现 ReentrantLock。Lock 接口的实现基本都是通过聚合一个同步器的子类来完成线程访问控制的。
参考: 方腾飞,魏鹏,程晓明 著. 《Java并发编程的艺术 》(Java核心技术系列)