前言
目前主流的锁有两种,一种是synchronized,另外一种就是ReentrantLock,JDK优化到如今目前为止synchronized的性能已经和重入锁不分伯仲了,可是重入锁的功能和灵活性要比这个关键字多的多,因此重入锁是能够彻底替代synchronized关键字的。下面就来介绍这个重入锁。java
正文
ReentrantLock重入锁是Lock接口里最重要的实现,也是在实际开发中应用最多的一个,我这篇文章更接近实际开发的应用场景,为开发者提供直接上手应用。因此不是全部方法我都讲解,有些冷门的方法我不会介绍或一句带过。
1、首先先看声明一个重入锁须要使用到那几个构造方法面试
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
复制代码
推荐声明方式bash
private static ReentrantLock lock = new ReentrantLock(true);
private static ReentrantLock locka = new ReentrantLock();
复制代码
重点说明:并发
ReentrantLock提供了两个构造方法,对应两种声明方式。性能
第一种声明的是公平锁,所谓公平锁,就是按照时间前后顺序,使先等待的线程先获得锁,并且,公平锁不会产生饥饿锁,也就是只要排队等待,最终能等待到获取锁的机会。优化
第二种声明的是非公平锁,所谓非公平锁就和公平锁概念相反,线程等待的顺序并不必定是执行的顺序,也就是后来进来的线程可能先被执行。ui
ReentrantLock默认是非公平锁,由于:公平锁实现了先进先出的公平性,可是因为来一个线程就加入队列中,每每都须要阻塞,再由阻塞变为运行,这种上下文切换是很是好性能的。非公平锁因为容许插队因此,上下文切换少的多,性能比较好,保证的大的吞吐量,可是容易出现饥饿问题。因此实际生产也是较多的使用非公平锁。spa
非公平锁调用的是NonfairSync方法。线程
2、加入锁以后lock方法究竟是怎么处理的(只讲非公平锁)
刚才咱们说若是是非公平锁就调用NonfairSync方法,那咱们就来看看这个方法都作来什么。code
static final class NonfairSync extends 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);
}
}
复制代码
重点说明:
读前先知:ReentrantLock用state表示“持有锁的线程已经重复获取该锁的次数”。当state(下文用状态二子代替)等于0时,表示当前没有线程持有锁)。
第一步调用compareAndSetState方法,传了第一参数是指望值0,第二个参数是实际值1,当前这个方法实际是调用了unsafe.compareAndSwapInt实现CAS操做的,也就是上锁以前状态必须是0,若是是0调用setExclusiveOwnerThread方法
private transient Thread exclusiveOwnerThread;
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
复制代码
能够看出setExclusiveOwnerThread就是线程设置为当前线程,此时说明有一名线程已经拿到了锁。你们都是CAS有三个值,若是旧值等于预期值,就把新值赋予上,因此当前线程获得了锁就会把状态置为1。
第二步是compareAndSetState方法返回false时,此时调用的是acquire方法,参数传1
tryAcquire()方法实际是调用了nonfairTryAcquire()方法。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
复制代码
注释上说的很明白,请求独占锁,忽略全部中断,至少执行一次tryAcquire,若是成功就返回,不然线程进入阻塞--唤醒两种状态切换中,直到tryAcquire成功。详情见连接tryAcquire()、addWaiter()、acquireQueued()挨个分析。
好,到日前为止你们清楚了lock()方法到调用过程,清楚了,为何只有获得锁的当前线程才能够执行,没有获得的会在队列里不停的利用CAS原理试图获得锁,CAS很高效,也就是,为何ReentrantLock比synchronized高效的缘由,缺点是很浪费cpu资源。
3、全部线程都执行完毕后调用unlock()方法
unlock()方法是经过AQS的release(int)方法实现的,咱们能够看一下:
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
复制代码
tryRelease()是由子类实现的,咱们来看一下ReentrantLock中的Sync对它的实现:
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;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
复制代码
先经过getState得到状态标识,若是这个标识和要释放的数量相等,就会把当前占有锁的线程设置为null,实现锁的释放,而后返回true,不然把状态标识减去releases再返回false。
以上所述是小编给你们介绍的java并发之重入锁-ReentrantLock详解整合,但愿对你们有所帮助,若是你们有任何疑问请给我留言,小编会及时回复你们的。
但愿求职者在面试的时候,碰到这个问题须要聪明一点,回答得巧妙一些,不要太耿直,也不要弄虚做假。