java中的显示锁Lock
什么是显示锁
在Java 1.5之前,协调对共享对象的访问可以使用的机制只有synchronized和volatile两种。Java1.5增加了一种新的机制,Lock,Lock是一个接口,提供了无条件的、可轮询的、定时的、可中断的锁获取操作,所有的加锁和解锁操作方法都是显示的,因而称为显示锁,Lock并不是替代内置加锁的方法,而是当内置加锁机制不适用时,作为一种可选择的高级功能。
Lock和synchronized的比较
synchronized代码更简洁
Lock可以在获取锁可以被中断,超时获取锁,尝试获取锁
synchronized在1.8以前是性能低下的,到了1.8之后经过改良,性能基本行和Lock相持平,如果不是特殊场景推荐使用synchronized。
Lock 的实现类
- ReentrantLock
- ReentrantReadWriteLock
Lock的API
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public interface Lock { void lock(); void lockInterruptibly() throws InterruptedException; boolean tryLock(); boolean tryLock(long time, TimeUnit unit) throws InterruptedException; void unlock(); Condition newCondition();
|
Lock 使用的标准范式
1 2 3 4 5 6 7 8
| lock.lock(); try { } finally { lock.unlock(); }
|
Condition接口
任意一个Java对象,都拥有一组监视器方法(定义在java.lang.Object上),主要包括wait()、wait(long timeout)、notify()以及notifyAll()方法,这些方法与synchronized同步关键字配合,可以实现等待/通知模式。Condition接口也提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public interface Condition {
void await() throws InterruptedException;
boolean await(long time, TimeUnit unit) throws InterruptedException; long awaitNanos(long nanosTimeout) throws InterruptedException; boolean awaitUntil(Date deadline) throws InterruptedException; void awaitUninterruptibly();
void signal(); void signalAll();
|
Condition 使用的标准范式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
|
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void conditionWait() throws InterruptedException { lock.lock(); try { condition.await(); } finally { lock.unlock(); } }
public void conditionSignal() throws InterruptedException { lock.lock(); try { condition.signal(); } finally { lock.unlock(); } }
|
ReentrantLock
锁可重入
简单地讲就是:“同一个线程对于已经获得到的锁,可以多次继续申请到该锁的使用权”。而synchronized关键字隐式的支持重进入,比如一个synchronized修饰的递归方法,在方法执行时,执行线程在获取了锁之后仍能连续多次地获得该锁。ReentrantLock在调用lock()方法时,已经获取到锁的线程,能够再次调用lock()方法获取锁而不被阻塞。
公平和非公平锁
如果在时间上,先对锁进行获取的请求一定先被满足,那么这个锁是公平的,反之,是不公平的。公平的获取锁,也就是等待时间最长的线程最优先获取锁,也可以说锁获取是顺序的。 ReentrantLock提供了一个构造函数,能够控制锁是否是公平的。事实上,公平的锁机制往往没有非公平的效率高。
在激烈竞争的情况下,非公平锁的性能高于公平锁的性能的一个原因是:在恢复一个被挂起的线程与该线程真正开始运行之间存在着严重的延迟。假设线程A持有一个锁,并且线程B请求这个锁。由于这个锁已被线程A持有,因此B将被挂起。当A释放锁时,B将被唤醒,因此会再次尝试获取锁。与此同时,如果C也请求这个锁,那么C很可能会在B被完全唤醒之前获得、使用以及释放这个锁。这样的情况是一种“双赢”的局面:B获得锁的时刻并没有推迟,C更早地获得了锁,并且吞吐量也获得了提高。
使用示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
|
private static ExecutorService executorService = Executors.newCachedThreadPool();
private static Lock lock = new ReentrantLock();
private static Condition condition = lock.newCondition();
private static void handel() { System.out.println("尝试获取锁,线程:" + Thread.currentThread().getId()); lock.lock(); System.out.println("获取锁成功,线程:" + Thread.currentThread().getId()); try { System.out.println("使线程进入等待状态,线程:" + Thread.currentThread().getId()); condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); System.out.println("释放锁,线程:" + Thread.currentThread().getId()); } }
private static void latch() { lock.lock(); try { condition.signalAll(); } finally { lock.unlock(); } }
public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 5; i++) { executorService.submit(() -> { handel(); }); } Thread.sleep(5000); latch(); executorService.shutdown(); }
|
输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 尝试获取锁,线程:13 尝试获取锁,线程:16 尝试获取锁,线程:15 尝试获取锁,线程:14 尝试获取锁,线程:12 获取锁成功,线程:13 使线程进入等待状态,线程:13 获取锁成功,线程:16 使线程进入等待状态,线程:16 获取锁成功,线程:15 使线程进入等待状态,线程:15 获取锁成功,线程:14 使线程进入等待状态,线程:14 获取锁成功,线程:12 使线程进入等待状态,线程:12 释放锁,线程:13 释放锁,线程:16 释放锁,线程:12 释放锁,线程:14 释放锁,线程:15
|