在我们的日常开发中,我们很多时候会或多或少的遇到一些并发场景,比如在某个订单模块或者秒杀块,多个用
户在同一时间内点击抢单和下单,后台是如何处理线程安全问题呢?底层实现原理又是什么?
我们常规的处理方法可能是使用Sychronized和ReentrantLock进行处理,在资源竞争相对不是很激烈的时候
Synchronized同步方式要比ReentrantLock加锁的方式性能要好,随着并发量增大,竞争越来越激烈,Synchronized
性能下降很快,而ReentrantLock基本保持不变。所以ReetrentLock的lock()和unlock()方式比较适用于一些并发
量比较大的场景。
我们如果深入研究Synchronized和ReetrentLock的底层实现我们会发现,Synchronized是通过底层汇编语言实现同步的也就是monitor监视器,配合synchronized使用的wait()和notify()其本质上也是通过汇编monitor监视器实现,我们使用的时候可以javap -c SynchronizedDemo类 查看底层汇编执行顺序。在这里不在过多赘述。上图。
ReentrantLock深入底层源码实现我们可以看到和一个AbstractQueuedSynchronizer(AQS)还有一个抽象的静态内部类Sync,抽象静态内部类中有一些抽象方法。
其实AQS也是一个抽象类继承了AbstractOwnableSynchronizer这个类,在该类中有一个用于保存当前那个线程获得了锁的一个变量exclusiveOwerThread该变量记录了当前获得锁的线程。
我们返回AQS类中有三个比较重要的变量 分别为:
1.private transient volatile Node head; 线程CLH队列的队列头指针
2. private transient volatile Node tail; 线程CLH队列的队列的尾指针
3.private transient volatile Node tail; 当前线程的状态(0 为没有线程占有锁,1为有线程占有锁)
当我们使用lock()方法上锁的时候,会经过一下步骤。(以公平锁为例子:可以通过Reetrant(boolean flag)传入一个布尔值创建公平锁还是非公锁,默认为非公平锁)
//FariSync为公平锁类集成sync 静态的抽象内部类实现他的抽线lock()方法,
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);//尝试获得锁,将当前锁的状态改为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;
}
}
上述代码中的调用acquire()其实调用的是我们AQS类中的方法如下,当调用tryAcquire()方法转到我们FairSync中重写父类AQS的方法。
在ReetrantLock中的静态内部类中我们可以找到该方法,(公平锁和非公平锁实现有些差别)。从如下代码中可以看处
(1)首先获得当前线程
(2)获得线程状态
(3)如果当前队列中没有等待获得锁的线程并且线程锁的状态为0,那么我们使用CAS同步方法将锁的状态改为1,表示当前线程持有锁
(4)否则的话,如果是当前线程正在持有锁,那么将锁定的状态加上传入的acquires的值,并设置状态后返回true,表示获得锁成功
(5)以上是我在读源码中的理解,如果有不对的地方,欢迎各位指正。互相学习。
以上是锁的获得过程,理清楚ReetrantLock 中的 变量Sync 和 AQS类的关系 以及FairSync和NonFairSync和AQS类的关系,以及上锁和解锁过程中的流程和各个变量的作用,应该就很清楚了,以下是unlock()方法实现,用来释放对锁的占用。
/**
* Attempts to release this lock.
*
* <p>If the current thread is the holder of this lock then the hold
* count is decremented. If the hold count is now zero then the lock
* is released. If the current thread is not the holder of this
* lock then {@link IllegalMonitorStateException} is thrown.
*
* @throws IllegalMonitorStateException if the current thread does not
* hold this lock
*/
**//这是我们 ReetrantLock中的unlock方法 用来释放对锁的占用**
public void unlock() {
sync.release(1);
}
以下是AQS类中的方法============================================================
/**
* Wakes up node's successor, if one exists.
*
* @param node the node
*/
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
释放对锁定的占用,将当前线程从队列中删除,并且将当前持有锁的状态改为0;
以上是我对Synchronized和ReetrantLock的一些个人理解,以及学习过程中,查阅资料的总结,如果错误,请大家指出,互相学习,共同提高!!!