Java显式锁对应于Java内置锁synchronized,它们实现的理论基础都是管程的MESA模型,通过管程解决了互斥和同步。那显式锁和内置锁相比带来了哪些好处呢?
先说获取锁的操作。使用synchronized获取锁的时候,如果没有成功,线程是阻塞的,进入BLOCKED状态(Java线程状态)。一旦发生死锁,阻塞的线程再也没有机会醒来。针对这种情况,使用显式锁可以拓展我们获取锁的方式:
- 能够响应中断:能够响应中断是指当线程阻塞地获取锁时,我们可以给它传递中断信号
Thread.interrupt(),这时线程可以抛出InterruptedException异常,跳出对锁的等待。 - 支持超时:除了让线程可以响应中断,我们还可以设置获取锁的最长等待时间,当超时时,报告超时,退出对锁的获取。
- 非阻塞地获取锁:如果尝试获取锁失败,并不进入阻塞状态,而是直接返回。
再说同步,synchronized的同步是靠wait、notify、notifyAll这三个方法实现,但是隐含的条件是只有一个条件变量,再一些需要多个条件变量的场景,比如BlockingQueue就不适用了。使用显式锁是可以支持多个条件变量的。
除了上述这些,显式锁还拓展了锁的策略以及公平性。synchronized是标准的互斥锁,属于抢占式加锁,一次只有一个线程可以获取锁,所有对共享资源的访问都需要先获取锁。而这种策略过于保守,在很多场景下应该允许并发访问共享资源,比如读写锁,显式锁是支持更宽松的锁策略的。公平性来说,synchronized仅支持非公平锁,而显式锁支持公平锁及非公平锁。
Lock
JDK1.5提供的Lock接口就是显式锁的定义。它的几个方法就涵盖了各种获取锁的方式:
void lock()
提供与内置锁一样的语义,阻塞式获取锁,不响应中断。不同的是当获取锁失败时,内置锁的线程的状态是BLOCKED,此方法的线程状态是WAITING。典型用法:
1 | Lock l = ...; |
void lockInterruptibly() throws InterruptedException
阻塞式获取锁,与上面方法不同的是可以响应中断,被中断时抛出InterruptedException。所以此方法执行后要么成功获取锁,要么走InterruptedException异常分支。
boolean tryLock()
非阻塞式获取锁,成功获取锁返回true,不成功立即返回false。经典用法:
1 | Lock lock = ...; |
boolean tryLock(long time, TimeUnit unit) throws InterruptedException
此方法虽然方法签名和非阻塞式获取锁,但它不完全是非阻塞地。如果线程没有成功获取锁,那么线程会进入阻塞状态(WAITING)直至以下三种情况之一(注意先后顺序):
- 获取锁
- 被中断
- 经过指定的等待时间
如果由于等待超时线程被唤醒,此方法返回false,而不是抛出TimeoutException之类的异常。
void unlock()
解锁操作,调用此方法前,要保证当前线程是锁的持有者,否则此方法可能会抛unchecked异常。
Condition newCondition()
返回绑定到此Lock实例的新Condition实例。支持一对多。
Condition
signal()/signalAll()
唤醒方面,Condition与内置锁的notify()和signalAll()没有啥区别。
void await() throws InterruptedException
在方法签名上可以看到,这个方法抛出InterruptedException异常,代表这个方法是支持中断的。因此此方法返回的一定是发生了以下几种情况:
- 被其他线程signal()/signalAll()
- 被中断
- 虚假唤醒(spurious wakeup),太底层不用考虑
基本对应于内置锁的wait()方法。注意!调用此方法时要确保调用线程持有锁,否则会抛出IllegalMonitorStateException异常,还会在AQS的条件等待队列上放一个“垃圾节点”。
void awaitUninterruptibly()
此方法与await方法的区别从方法签名上就可以看出来,这个方法不响应中断。如果在这过程中线程被中断了,它并不响应这个中断,只是在该方法返回的时候,该线程的中断标志位将是true, 调用者可以检测这个中断标志位以辅助判断在等待过程中是否发生了中断,以此决定要不要做额外的处理。
long awaitNanos(long nanosTimeout) throws InterruptedException
这个方法和await方法类似,不过它支持以纳秒为单位的超时支持。需要注意,这个方法是有返回值的!除了发生中断的情况,我们是无法知道awaitNanos是由于超时还是被其他线程唤醒的。这时需要通过返回值进行判断,返回值代表离约定的超时时间还有多久,如果为正值说明是被其他线程唤醒的,否则是超时造成的。
boolean await(long time, TimeUnit unit) throws InterruptedException
这个方法的返回值为boolean类型,如果是true说明是被其他线程唤醒的,否则是超时。
boolean awaitUntil(Date deadline) throws InterruptedException
此方法与await(long time, TimeUnit unit)方法类似,不同的是超时时间是绝对日期。