重入锁(ReentrantLock)
锁是用来控制多个线程访问共享资源的方式。
一般来说,一个锁能够防止多个线程同时访问共享资源(但是有些锁可以允许多个线程并发的访问共享资源,比如读写锁)。在Lock接口出现之前,Java程序是靠synchronized关键字实现锁功能的,而Java SE 5之后,并发包中新增了Lock接口(以及相关实现类)用来实现锁功能,它提供了与synchronized关键字类似的同步功能,只是在使用时需要显式地获取和释放锁。虽然它缺少了(通过synchronized块或者方法所提供的)隐式获取释放锁的便捷性,但是却拥有了锁获取与释放的可操作性、可中断的获取锁以及超时获取锁等多种synchronized关键字所不具备的同步特性。
-
重入锁:
支持重进入的锁,它表示该锁能够支持一个线程对资源的重复加锁。除此之外,该锁还支持获取锁时的公平和非公平性选择。
- 源码探究:
public class ReentrantLock implements Lock, java.io.Serializable
- 方法:
void lock():加锁
void lockInterruptibly() throws InterruptedException:可中断锁
boolean tryLock():尝试性的进行加锁
boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException: 在有限时间内进行尝试性加锁
void unlock():释放锁
Condition newCondition():通信类
- 构造函数:
无参构造,默认采用非公平性锁
public ReentrantLock() {
sync = new NonfairSync();
}
有参构造,参数为Boolean,true:公平性锁,false:非公平性锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
AbstractQueuedSynchronizer
基于FIFO队列,可以用于构建锁或者其他相关同步装置的基础框架。
该同步器利用了一个int来表示状态。
同步器的开始提到了其实现依赖于一个FIFO队列,
那么队列中的元素Node就是保存着线程引用和线程状态的容器,
每个线程对同步器的访问,都可以看做是队列中的一个节点。
Node的主要包含以下成员变量:
Node {
int waitStatus;//节点的状态
Node prev;//前驱节点
Node next;//后继节点
Node nextWaiter;//存储condition队列中的后继节点
Thread thread;//入队列时的当前线程
}
表示节点的状态。其中包含的状态有:
CANCELLED,值为1,表示当前的线程被取消;
SIGNAL,值为-1,表示当前节点的后继节点包含的线程需要运行,也就是unpark;
CONDITION,值为-2,表示当前节点在等待condition,也就是在condition队列中;
PROPAGATE,值为-3,表示当前场景下后续的acquireShared能够得以执行;
值为0,表示当前节点在sync队列中,等待着获取锁。
AQS(AbstractQueuedSychronizer—-Node组成的链表)中的state:
state=0:表示锁是空闲状态;
state>0:表示锁被占用;
state<0:表示溢出;
state > 1 : 当前获取锁的次数。
重入锁的实现:
当前线程每获取一次锁就进行+1操作,每次释放锁对state减一,当state=0时才是真正的释放锁。
- ReentrantLock方法列举:
ReentrantLock方法列举:
int getHoldCount():返回当前锁获取的次数
boolean isHeldByCurrentThread():
boolean isLocked()
Thread getOwner()
boolean hasQueuedThreads()
boolean hasQueuedThread(Thread thread):判断传入的线程是否处于阻塞对列中
int getQueueLength()
Collection<Thread> getQueuedThreads()
boolean hasWaiters(Condition condition)
int getWaitQueueLength(Condition condition)
ReentrantLock底层实现有三个内部类:FairSync、NonfairSync、Sync
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
abstract void lock();
获取锁相关方法:
final boolean nonfairTryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
//获取锁的状态
int c = getState();
if (c == 0) {
//表示当前锁空闲,通过CAS获取锁,获取成功则将state置为1 ,并且将当前线程记录下来,直接返回
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);//若没有到达上限,则直接更新state,直接返回,加一操作
return true;
}
//当前获取锁的线程不是当前线程
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) {
//当state减为1时,才真正释放锁,将持有锁的线程置为null
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
- 简单实现方法:
public class ReentrantLock0416 {
public static void main(String[] args) {
//ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); //读写锁
ReentrantLock reentrantLock = new ReentrantLock();
/* *//**默认不公平性锁
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*//*
public ReentrantLock() {
sync = new ReentrantLock.NonfairSync();
}*/
ReentrantLock reentrantLock1 = new ReentrantLock(true);//公平性锁
reentrantLock.lock();
System.out.println("重入锁");
reentrantLock.unlock();//加锁更细 显性实现加锁
//线程通信
Condition condition = reentrantLock.newCondition();
condition.signal();//类似notify()
condition.signalAll();//notifyAll()
try {
condition.await();//wait()
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
reentrantLock.lockInterruptibly();//中断加锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
-
公平性锁和非公平性锁:
锁获取的公平性问题,如果在绝对时间上,先对锁进行获取的请求一定先被满足,那么这个锁是公平的,反之,是不公平的。公平的获取锁,也就是等待时间最长的线程最优先获取锁,也可以说锁获取是顺序的。ReentrantLock提供了一个构造函数,能够控制锁是否是公平的。
**NonFairSync:**非公平性锁
第一次通过CAS强制抢锁,抢锁失败则再次尝试性抢锁,
尝试性抢锁:首先判断锁是否被占用(state=0),是则直接CAS来获取锁,
获取成功则修改 state,且将当前线程计入AQS中,成功返回。
在判断占用的情况下,占用锁的线程是否是当前线程,则直接修改state,成功返回。
以上都不满足,失败返回,若失败返回,则当前线程未获取到锁,则将线程加入到AQS中。
/**
* Sync object for non-fair locks
*/
static final class NonfairSync extends Sync {
final void lock() {
//CAS强制获取锁
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
//尝试性抢锁
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
//获取锁的操作
final boolean nonfairTryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
//获取锁的状态
int c = getState();
if (c == 0) {
//表示当前锁空闲,通过CAS获取锁,获取成功则将state置为1 ,并且将当前线程记录下来,直接返回
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);//若没有到达上限,则直接更新state,直接返回,加一操作
return true;
}
//当前获取锁的线程不是当前线程
return false;
}
**FairSync:**公平性锁
尝试性获取锁,若锁未被占用,判断当前现场是否满足条件(AQS队列为空且当前线程处于AQS队列的队头),修改state,若锁被占用,且是当前线程占用锁,修改state,成功返回。
若以上都不满足,则失败返回。
若未获取到锁,则即将线程加入到AQS中的队列。
static final class FairSync extends Sync {
final void lock() {
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//队列为空并且当前线程 处于队列的first的node
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
//队列不为空
//且队列里只有一个结点或者头结点所在的线程不等于当前的线程
}
- 公平性锁和不公平性锁的区别:
- 锁的获取顺序:
- 公平性与否是针对获取锁而言的,如果一个锁是公平的,那么锁的获取顺序就应该符合请求的绝对时间顺序,也就是FIFO。
- 而非公平性锁获取锁是抢占式的,当一个线程获取锁后再释放,有很大的概率会再次获取到锁,因为刚释放锁的线程会再次获取到同步状态的几率会非常大,使得其他线程只能在同步队列中等待。
- 主要在Lock()方法实现上不同:
- 抢锁次数不同;非公平性锁进行两次抢夺,公平性锁进行一次抢夺;
- 公平性锁在抢锁时判断是否处于队头;