显式锁
- synchronized我们称之为隐式锁,这篇文章我们来看看显示锁Lock
一、锁的分类和概念
1.1 可重入锁
- 获取锁之后,还能够再次获取同一把锁,则为可重入锁
1.2 公平锁和非公平锁
- 在时间上,先对锁进行获取的请求,一定先被满足,这个锁就是公平的,反之就是非公平的,非公平的效率一般来讲更高。因为按照公平原则,需要被调度的线程可能被操作系统挂起,如果要调度它的话需要将该线程解挂,但是非公平锁的话,就会选择一个还在内存中的线程,后面的过程比前面的过程快很多,这也是两种锁效率差别的主要原因。在AQS中,公平锁的实现里面,需要判断自己是否有前驱节点,如果有,那么必须老老实实等待前面的线程先执行,而前面的线程可能被park了,唤醒需要消耗更多的时间,因此损失了效率。
PS:
挂起:一般是主动的,由系统或程序发出,甚至于辅存中去。(不释放CPU,可能释放内存,放在外存)
阻塞:一般是被动的,在抢占资源中得不到资源,被动的挂起在内存,等待某种资源或信号量(即有了资源)将他唤醒。(释放CPU,不释放内存)
1.3 读写锁
- 同一时间允许多个读线程访问,写线程访问时,需要阻塞其余全部的读写线程。简单表示就是读读共享,读写互斥,写写互斥,适合读多写少的应用场景。
1.4 排它锁
- 排它锁:同一时间锁只能被一个线程获取
二、Lock接口
- Lock接口定义了锁的基本API,如下:
lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
- Lock接口的主要实现类是ReentrantLock,它是可重入锁。除此之外还有一些实现类,比如ConcurrentHashMap里面的Segment,读写锁内部持有的读锁和写锁等,如下:
[外链图片转存失败(img-WOYwOlAj-1565595463130)(https://note.youdao.com/yws/api/personal/file/72F33B011531428F8EB6215E36AF41A1?method=download&shareKey=2b711d4864b319d8f7d7b8369ae9634a)]
[外链图片转存失败(img-c18GA32f-1565595463131)(https://note.youdao.com/yws/api/personal/file/107A00F084774496A6F06272003B6AF9?method=download&shareKey=09a04ab50ef5b3176eee5e5da44c426f)]
三、可重入锁ReentrantLock
- ReentrantLock实现了Lock接口,是可重入锁。我们先了解它,然后分析其源码,要点如下:
1.ReentrantLock实现了Lock接口,具备锁的功能,它支持公平锁和非公平锁2中模式,默认是非公平锁。
2.ReentrantLock内部的锁机制是基于AQS来实现的,内部的Sync类继承了AQS,然后公平和非公平2中模式对应着Sync的2个子类。
3.ReentrantLock的加锁和释放锁机制是基于AQS的state变量来做的,因为是可重入锁,还可以判断当前线程获取了多少次锁,实际上就是stste的值来代表加锁的次数
- PS:前面2点是不是和Semaphore非常类似,Semaphore内部也是这样的模式,其实Semaphore也是一种同步工具,不过没有锁那么灵活。在Semaphore中state代表许可的个数,在ReentrantLock中state代表加锁的次数,有相似之处的,这一切都是基于AQS来做的。可以参考并发工具类之Semaphore
3.1 Sync
- 源码
/**
* Base of synchronization control for this lock. Subclassed
* into fair and nonfair versions below. Uses AQS state to
* represent the number of holds on the lock.
* 控制锁的同步基础,子类包括公平和非公平2中版本,使用AQS的state来代表当前
* 锁的加锁次数
*/
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
/**
* Performs {@link Lock#lock}. The main reason for subclassing
* is to allow fast path for nonfair version.
* 加锁的方法,交给子类实现,因为2中模式下加锁的逻辑有所不同,交给子类实现的
* 主要原因是非公平锁可以快速尝试获取锁。
* 我们看到2中模式下lock方法的实现区别在于非公平模式会尝试获取一次锁,获取失败,
* 再去阻塞获取,因此Sync类里面没有实现这个方法,而是交给子类实现
*/
abstract void lock();
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
* 非公平模式下的nonfairTryAcquire方法实现
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//如果当前线程没有加锁,CAS直接加锁
if (c == 0) {
if (compareAndSetState(0, acquires)) {
//加锁成功就返回
setExclusiveOwnerThread(current);
return true;
}
}
//如果线程已经被加锁,并且当前线程就是尺有锁的线程,那就直接操作state,此时不需要CAS,
// 因为只有一个线程持有锁,直接操作即可
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
//如果已经加锁了,并且不是自己加锁的,那说明尝试获取state失败,返回false
return false;
}
//释放锁
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
//只有持有锁的线程才能释放锁,如果本线程不是持有锁的线程,那么释放锁的操作直接抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
//说明锁已经释放了,占据锁的线程置为null
setExclusiveOwnerThread(null);
}
setState(c);//设置state为0
//返回的布尔值代表当前的state是否为0,也就是说锁已经释放了返回true,如果没有完
// 全释放返回false(因为可重入,释放后state不为0,锁就没有完全释放)
return free;
}
//判断是不是当前线程持有锁
protected final boolean isHeldExclusively() {
// While we must in general read state before owner,
// we don't need to do so to check if current thread is owner
return getExclusiveOwnerThread() == Thread.currentThread();
}
final ConditionObject newCondition() {
return new ConditionObject();
}
// Methods relayed from outer class
//获取持有锁的线程,如果锁没有被持有返回null
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
//获取加锁次数
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
//是否已经被加锁,如果没有state是0
final boolean isLocked() {
return getState() != 0;
}
/**
* Reconstitutes the instance from a stream (that is, deserializes it).
* 从流重构实例,反序列化(什么场景下使用呢?)
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
}
3.2 NonfairSync
static final class NonfairSync extends java.util.concurrent.locks.ReentrantLock.Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
* 非公平模式先尝试加锁,失败之后再阻塞获取锁,因为不需要保证公平,只要能拿到,就直接获取锁
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
3.3 FairSync
static final class FairSync extends java.util.concurrent.locks.ReentrantLock.Sync {
private static final long serialVersionUID = -3000897897090466540L;
//公平模式阻塞获取锁,保证公平
final void lock() {
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
* 公平模式的tryAcquire
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//1.如果没有加锁,并且自己没有前驱节点,并且获取锁成功,那么就返回true
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
//设置当前线程为占有锁的线程
setExclusiveOwnerThread(current);
return true;
}
}
//2.如果当前线程就是持有锁的线程,那就直接操作state,注意操作后的state不能为负数
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
//修改state
setState(nextc);
return true;
}
//如果线程持有锁,在前面的else if已经返回
//如果线程不持有锁,并且c=0,并且设置锁状态成功,第一个if返回
//如果线程不持有锁,并且c>0,或者c=0但是设置锁失败,就会走到这个逻辑,直接返回false,表示获取锁失败了
return false;
}
}
3.4 构造方法
- 构造方法通过参数指定锁的公平模式
public ReentrantLock() {
sync = new java.util.concurrent.locks.ReentrantLock.NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new java.util.concurrent.locks.ReentrantLock.FairSync() : new java.util.concurrent.locks.ReentrantLock.NonfairSync();
}
3.5 加锁方法
3.5.1 lock()
- lock方法直接调用Sync实例的lock方法,这个方法是在NonfairSync和FairSync里面实现的
public void lock() {
sync.lock();
}
3.5.2 lockInterruptibly()
- 支持中断的加锁方法,这个方法是在AQS中实现的,AQS基于tryAcquire实现了支持中断的加锁方法
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
3.5.3 tryLock()
- 尝试获取锁。我们看到尝试获取锁是非公平的
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
3.5.4 tryLock(long timeout, TimeUnit unit)
- 支持超时的尝试获取
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
3.6 解锁方法
3.6.1 unlock()
- 使用Sync的release方法
public void unlock() {
sync.release(1);
}
3.7 其他方法
3.7.1 newCondition()
- 获取锁的Condition对象
public Condition newCondition() {
return sync.newCondition();
}
3.7.2 getHoldCount()
- 获取锁的加锁次数
public int getHoldCount() {
return sync.getHoldCount();
}
3.7.3 isHeldByCurrentThread()
- 判断锁是否被当前线程持有
public boolean isHeldByCurrentThread() {
return sync.isHeldExclusively();
}
3.7.4 isLocked()
- 判断是否被加锁
public boolean isLocked() {
return sync.isLocked();
}
3.7.5 isFair()
- 判断是否为公平模式
public final boolean isFair() {
return sync instanceof java.util.concurrent.locks.ReentrantLock.FairSync;
}
3.7.6 等待线程相关
- hasQueuedThreads():判断是否有线程在等待锁
- hasQueuedThread(Thread thread):判断指定线程是否在等待锁
- getQueueLength():获取排队线程的长度的估计值
- hasWaiters(Condition condition):判断是否有线程在等待某个Condition
- getWaitQueueLength(Condition condition):获取等待某个Condition的线程队列个数的估计值
四、小结
- 本文我们主要是了解ReentrantLock的整体实现原理和思想,它支持公平非公平2中模式,内部持有AQS的子类Sync,然后由Sync派生2个子类来对应这2中模式。不管那种模式,加锁解锁这些操作底层都是操作同步状态state变量,由于AQS已经将线程同步已经很多固定的逻辑流程实现了,因此锁中并没有过于复杂的代码。
- 我们看到AQS在很多线程同步的工具中出现,包括CountDownLatch,Semaphore和本文的ReentrantLock,我们后续还会看到很多基于AQS的线程工具类,也可以自己基于AQS实现一个锁,后面也会总结AQS在整个java并发体系中的核心地位。
- 后面我们会了解另一种显示锁,ReentrantReadWriteLock读写锁。