多线程开发的场景下我们不可避免的会用到锁,那么java中也有各种锁,我们应该怎么使用呢?以及它的原理?
首先synchronized关键字相信大家是不会陌生的,这个关键字要理解其原理的话就涉及到了jvm了,并且jdk现在也对它做了很大的优化,比如: 偏向锁,轻量锁,重量锁。。膨胀,撤销等等,这篇文章目前就不过多作介绍。
其实如果我们仔细想想,我们为什么要使用ReentrantLock,直接使用cas不是就行吗?
cas不是在多线程的情况下也可以用吗?1.8版本之前的ConcurrentHashMap之前不就是这样做的吗?
原因就是cas会发生自旋,cpu使率会急剧升高(cpu不停的在各线程间切换),严重影响系统性能,所以
java.util.concurrent.locks.ReentrantLock派上用场了
看源码前我们先把它的优势写出来,并且我们后面通过源码去验证查看,
AQS:AbstractQueuedSynchronizer,即队列同步器。它是构建锁或者其他同步组件的基础框架(如ReentrantLock、ReentrantReadWriteLock、Semaphore等),JUC并发包的作者(Doug Lea)期望它能够成为实现大部分同步需求的基础。它是JUC并发包中的核心基础组件。
ReentranLock是通过(AQS中)链表(多个Node连接)来管理我们对同一资源抢占的线程,并且当其它线程在获取锁的时候并不是所有线程都在竞争锁,而是除了头节点外的第二个线程在竞争锁,其它线程都处于休眠状态(我们暂且这么认为),当第二个线程获得锁后第三个线程就进入竞争获取锁得状态了。所以每时每刻都只有一个线程在竞争锁,减少了cpu的不停的在各线程间的切换
下面我们就分析下ReentrantLock加锁的源码,你就可以知道答案了。
ReentrantLock 实现了Lock接口,lock接口也可以说是jul包下一个牛逼哄哄的接口,下面列举一些他的一些方法:
//上锁: 这个会阻塞线程: 会一直去尝试获取锁,直到获得了锁
void lock();
//尝试去获取锁并且返回是否获取到锁,不会造成阻塞
boolean tryLock();
//尝试获取锁,如果在规定时间内获取不到就放弃
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
目前先介绍列这三种,具体的可以看看源码
首先我们获取锁后,必须要释放锁,否则锁一直不会释放,那就完蛋了。
lock.lock();
try{
System.out.println(Thread.currentThread().getName()+"获得了锁");
while(true){
}
}catch (Exception e){
}finally {
//释放锁
System.out.println(Thread.currentThread().getName()+"释放锁");
lock.unlock();
}
言归正传,要把ReentrantLock原理理解透,那就要把它的数据结构以及内部方法,实现搞懂,这样就知道如何去用,也就更容易理解它的原理了。
ReentrantLock实现锁功能是通过其内部的一个抽象静态内部类:
/** Synchronizer providing all implementation mechanics */
private final Sync 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.
*/
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.
*/
abstract void lock();
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
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;
}
}
但是ReentrantLock中又包含了公平锁(FairSync)和非公平锁(NonFairSync)
后面会讲到到底什么是公平和非公平
这两者都继承了Sync这个类,而Sync又继承了AbstractQueuedSynchronizer(AQS)
最中还是通过AQS来实现我们的锁,所以这才是最终的大BOSS
下面画一下这些类的大概关系:
通过源码可见我们所有的操作基本上都是和这个Node链表有关系,
Node 主要包含: pre 上一个节点, next 下一个节点,tail 尾节点,head 头结点,waitStatus 等待状态,thread 线程对象
AQS默认会创建一个空的Node如下图:
这也是Node的一个大体结构,下面画一个多个node的图
下面我们就一步一步的去探究究竟是怎么回事;
1 首先从创建一个ReentrantLock说起:
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
* 创建对象,从构造方法中可以看到默认创建的的是非公平锁
*/
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
* 创建对象是可以指定创建锁的类型
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
这也就是上面说的他们之前的关系;其实觉得ReentranLock就是一个空壳子,内部的一些操作都是依赖AQS,Sync及它的子类(FairSync和NonfairSync)实现
2 调用lock();方法:
以下为伪代码
//创建一个公平锁
1 public static Lock lock=new ReentrantLock(true);
//加锁
2 lock.lock();
2.1 去获取锁
/**
* Acquires the lock.
*
* <p>Acquires the lock if it is not held by another thread and returns
* immediately, setting the lock hold count to one.
*
* <p>If the current thread already holds the lock then the hold
* count is incremented by one and the method returns immediately.
*
* <p>If the lock is held by another thread then the
* current thread becomes disabled for thread scheduling
* purposes and lies dormant until the lock has been acquired,
* at which time the lock hold count is set to one.
*/
public void lock() {
sync.lock();
}
下面就sync.lock();这个方法做详细的解答,因为这也就是这本篇文章的核心
因为我创建的是一个公平锁,所以会调用FairSync中的加锁方法finl void lock();
下面试公平锁的代码:
//静态内部类: 公平锁
static final class FairSync extends 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.
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
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;
}
}
在lock的方法中调用了AQS类中的方法:
这个类中AbstractQueuedSynchronizer (AQS)
/**
* Acquires in exclusive mode, ignoring interrupts. Implemented
* by invoking at least once {@link #tryAcquire},
* returning on success. Otherwise the thread is queued, possibly
* repeatedly blocking and unblocking, invoking {@link
* #tryAcquire} until success. This method can be used
* to implement method {@link Lock#lock}.
*
* @param arg the acquire argument. This value is conveyed to
* {@link #tryAcquire} but is otherwise uninterpreted and
* can represent anything you like.
* 这个方法才是真正去调用加锁的方法
*/
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
1 tryAcquire(args)方法是去判断用户是否取得到了锁,下面我们具体看看这个方法的代码
//注意这个方法其实是上面FairSync类中的
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
//获得当前锁的一个状态 为0表示没有线程持有锁,是自由状态,可以去竞争
int c = getState();
if (c == 0) {
//判断队列中是否有线程在等待,
//如果没有线程在等待那么就可以通过cas去获得锁
//下面解释这个方法的源码
if (!hasQueuedPredecessors() &&
//通过cas去获得锁
compareAndSetState(0, acquires)) {
//获得锁成功后设置当前持有锁的线程
setExclusiveOwnerThread(current);
return true;
}
}
//判断当前线程是不是目前持有所得线程,如果是c(status)加1
// 表示可重入锁,再次获得锁,直接返回true
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
//否则返回false
return false;
}
下面介绍下hasQueuedPredcessors()方法的具体实现
/**
* Queries whether any threads have been waiting to acquire longer
* than the current thread.
*
* <p>An invocation of this method is equivalent to (but may be
* more efficient than):
* <pre> {@code
* getFirstQueuedThread() != Thread.currentThread() &&
* hasQueuedThreads()}</pre>
*
* <p>Note that because cancellations due to interrupts and
* timeouts may occur at any time, a {@code true} return does not
* guarantee that some other thread will acquire before the current
* thread. Likewise, it is possible for another thread to win a
* race to enqueue after this method has returned {@code false},
* due to the queue being empty.
*
* <p>This method is designed to be used by a fair synchronizer to
* avoid <a href="AbstractQueuedSynchronizer#barging">barging</a>.
* Such a synchronizer's {@link #tryAcquire} method should return
* {@code false}, and its {@link #tryAcquireShared} method should
* return a negative value, if this method returns {@code true}
* (unless this is a reentrant acquire). For example, the {@code
* tryAcquire} method for a fair, reentrant, exclusive mode
* synchronizer might look like this:
*
* <pre> {@code
* protected boolean tryAcquire(int arg) {
* if (isHeldExclusively()) {
* // A reentrant acquire; increment hold count
* return true;
* } else if (hasQueuedPredecessors()) {
* return false;
* } else {
* // try to acquire normally
* }
* }}</pre>
*
* @return {@code true} if there is a queued thread preceding the
* current thread, and {@code false} if the current thread
* is at the head of the queue or the queue is empty
* @since 1.7
* 总结来讲就是判断当前队列中是否有线程在排队(等待锁的状态),如果有则返回true
* 返回false表示当前线程有资格去获取锁,为true表示当前线程无资格获取锁
*/
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;
//h != t 表示头结点和尾节点不是同一个Node 如果条件为真表示队列中有在等待的线程
//否则表示队列目前无在等待的线程,返回false
return h != t &&
//下一个为空||当前节点的线程不等于头结点下一个节点的线程(作用是判断两个线程是否相同-
-可重入)
((s = h.next) == null || s.thread != Thread.currentThread());
}
通过上面的源码解析tryAcquire()方法所做的是也就明了: 在此总结下这个方法的作用
当一个相乘尝试去获取锁时,首先判断当前锁是不是自由态(即无线程持有),如果是自由态那么就去判断AQS队列(由Node节点形成的一个链表)中是否有线程在等待获取锁,如果有当前相线程就没有资格获取锁;
其中如果所不是自由态(被线程持有),会判断当前线程是不是持有这把锁的线程,如果是直接获得锁------可重入锁
h != t && ((s = h.next) == null || s.thread != Thread.currentThread());
这段比较好奇 为什么说(s = h.next) == null 表示队列中有在排队呢?以及是怎么出现这种状况的
看前面:h != t 出现的情况
当前队列中说明有线程在排队
h有两种状态: 不为空和为空
h和t组合三种情况
1 两者都为空,排除这种状态
2 两者都不为空 目前场景也排出这种情况,因为如果是这样 s = h.next) == null 就不可能成立
3 一者为空一者不为空 出现这种情况 就是因为当前线程前一个线程在初始化队列并且 只给h = new Node(), 没有对t进行赋值,这样就会出现 h!=t && s = h.next) == null 的情况,也就是在addWriter(Node.EXCLUSIVE) 初始化节点这个方法中
tryAcquire(args) 返回true表示当前线程已经获得了锁, 返回false表示没获得锁,没获得锁的话就需要直线下面的方法了
2 acquireQueued(addWriter(Node.EXCLUSIVE),args);
addWriter(Node.EXCLUSIVE) 初始化节点,并加入队列中
/**
* Creates and enqueues node for current thread and given mode.
*
* @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
* @return the new node
*/
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//将节点加入队列,也会进行队列初始化
enq(node);
return node;
}
接下来就是重头戏了: 当前节点根据上一个节点判断有无竞争资格,话不多说直接上代码:
/*
* Various flavors of acquire, varying in exclusive/shared and
* control modes. Each is mostly the same, but annoyingly
* different. Only a little bit of factoring is possible due to
* interactions of exception mechanics (including ensuring that we
* cancel if tryAcquire throws exception) and other control, at
* least not without hurting performance too much.
*/
/**
* Acquires in exclusive uninterruptible mode for thread already in
* queue. Used by condition wait methods as well as acquire.
*
* @param node the node
* @param arg the acquire argument
* @return {@code true} if interrupted while waiting
* 使第二个节点处于竞争状态,其余节点处于等待状态: waitStatus在此处作用很大
*/
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//获得当前节点的上一个节点
final Node p = node.predecessor();
//p==head成立的话表示当前节点是第二个节点然后可以去竞争锁,
//否则的话通过下面的方法将当前节点的上一个节点的状态更改
if (p == head && tryAcquire(arg)) {
//竞争到锁了,将当前节点删除
setHead(node);
//将当前节点的引用去掉,让GC回收
p.next = null; // help GC
failed = false;
//结束
return interrupted;
}
//这个方法主要是获取当前节点上一个节点的状态,并且将它值为-1(waitStatus)待竞争
状态
//当前节点为0 (可理解为沉睡状态),其实两者都可理解为沉睡状态
//-1 类似于就绪,0表示刚创建,
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
解读下shouldParkAfterFailedAcquire(Node pre,Node node)方法:
这个方法主要是获取当前节点上一个节点的状态,并且将它值为-1(waitStatus)待竞争
状态
/**
* Checks and updates status for a node that failed to acquire.
* Returns true if thread should block. This is the main signal
* control in all acquire loops. Requires that pred == node.prev.
*
* @param pred node's predecessor holding status
* @param node the node
* @return {@code true} if thread should block
* 更改当前节点上一个节点的状态,以及将已经取消的线程移除队列
*
/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//初始值为0
int ws = pred.waitStatus;
//-1 表示处于待竞争状态
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
//线程已经取消,并且将它移除
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
//将上一个节点的waitStatus更新为-1
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
到这里ReentranLock加锁的原理分析也就结束了,从上面也可以看出AQS的原理。
以上是一公平锁加锁来分析的,其实非公平锁和公平锁大同小异,
公平和非公平主要区别在于公平锁加锁时如果锁处于自由态,公平锁并不能直接去竞争锁,而是应该去判断队列中是否有在等待或者说处于竞争锁状态的线程,如果有那么当前线程就加入队列并且处于等待状态,
但是非公平锁就不一样,线程一开始就通过cas去获取锁(无论锁是否是自由态),获取不到的话就判断当前锁是否是自由态,如果是再次通过cas去获取锁,如果还获取不到就加入等待队列中。
非公平锁有两次竞争锁的机会,如果没竞争到就和公平锁一样了,等待
所以不管是公平锁还是非公平锁都是: 一朝排队,永久排队。
好了aqs加锁过程接这样结束了,有不对的地方欢迎指正!!!
后面更新aqs解锁