前言
ReentrantLock(可重入锁),它提供了与synchronized关键字类似的同步功能,但比synchronized更加灵活。
从类图上可以看到,它实现了Lock接口与Serializable接口,这意味着它有锁的特性,并且可以被序列化。同时它有三个内部类,而且其中的两个子类FairSync和NofairSync又继承了Sync,它俩的区别是FairSync会先检查同步队列中是否有在等待的线程,有就将当前线程加入队列的尾部;NofairSync则是直接尝试竞争锁,得到锁就执行自己的逻辑。
ReentrantLock的一些主要特性:
- 可重入性:和synchronized一样,ReentrantLock也支持可重入性。也就是说,一个线程可以多次获取同一个锁,而不会造成死锁
- 公平性和非公平性:ReentrantLock可以在创建时指定公平性。如果设置为公平锁,那么等待时间最长的线程将优先获取锁。如果设置为非公平锁(默认),那么哪个线程获取锁是不确定的。
- 条件变量:ReentrantLock提供了一个Condition类,可以用于多线程之间的精确唤醒。这是synchronized关键字所不具备的
- 锁状态的查询:ReentrantLock提供了一些方法,可以查询锁的状态,例如查询是否有线程正在等待获取此锁
一个关于锁的小demo
这里我们创建5000个线程对count字段进行累加,如果在未加锁的情况下,结果可能不是5000。如果使用了ReentrantLock那么结果必定为5000。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| private static ReentrantLock lock = new ReentrantLock(); private static int count = 0;
public static void main(String[] args) { for (int i = 0; i < 5000; i++) { new Thread(() -> { lock.lock(); try { count++; } finally { lock.unlock(); } }).start(); } try { Thread.sleep(5000); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("结果" + count); }
|
构造器
1 2 3 4 5 6 7 8
| public ReentrantLock() { sync = new NonfairSync(); }
public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
|
关键方法
加锁
lock()
尝试加锁
1 2 3 4
| public void lock() { sync.lock(); }
|
继续跟进,initialTryLock()为尝试加锁的逻辑,这里公平所和非公平锁的实现逻辑基本一致,只是公平锁多了一段检查队列是否有等待线程的逻辑。如果没有获取到锁,则调用AQS的acquire(1)方法,里面会再次尝试获取锁的逻辑,如果还获取不到,就将当前线程加入到同步队列中。
1 2 3 4
| final void lock() { if (!initialTryLock()) acquire(1); }
|
以公平锁为例,看下initialTryLock()方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| final boolean initialTryLock() { Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (!hasQueuedThreads() && compareAndSetState(0, 1)) { setExclusiveOwnerThread(current); return true; } } else if (getExclusiveOwnerThread() == current) { if (++c < 0) throw new Error("Maximum lock count exceeded"); setState(c); return true; } return false; }
|
tryLock()
尝试竞争锁,竞争到返回true,没竞争到直接返回false,并且当前线程不会进入同步队列中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public boolean tryLock() { return sync.tryLock(); }
final boolean tryLock() { Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (compareAndSetState(0, 1)) { setExclusiveOwnerThread(current); return true; } } else if (getExclusiveOwnerThread() == current) { if (++c < 0) throw new Error("Maximum lock count exceeded"); setState(c); return true; } return false; }
|
lockInterruptibly()
如果加锁的过程中线程被中断,则抛出InterruptedException。
释放锁
unlock()
尝试去释放锁,调用的是AQS中的release方法
1 2 3
| public void unlock() { sync.release(1); }
|
点进AQS类,它会调用子类的tryRelease(arg)去释放锁,如果释放锁成功就唤醒下个线程就竞争锁。如果释放成功就返回true。
1 2 3 4 5 6 7
| public final boolean release(int arg) { if (tryRelease(arg)) { signalNext(head); return true; } return false; }
|
现在看下Sync类中的释放锁逻辑。因为ReetrantLock是可重入锁,每当线程释放锁时state都会减1,所以在释放锁的时候它会检测state是否为0,为0就表示当前线程已经不持有锁了,此时就返回true。
1 2 3 4 5 6 7 8 9 10
| protected final boolean tryRelease(int releases) { int c = getState() - releases; if (getExclusiveOwnerThread() != Thread.currentThread()) throw new IllegalMonitorStateException(); boolean free = (c == 0); if (free) setExclusiveOwnerThread(null); setState(c); return free; }
|
Condition
条件等待,ReetrantLock的Condition功能都是基于AQS的,可以参考《JAVA源码解读之AbstractQueuedSynchronizer》。