不通过jdk提供的锁,自己要去实现
一个锁,应该如何考虑?
1、CAS+自旋:
缺点:一直让CPU去执行CAS操作,相当于空转,
十分耗费CPU资源,如果一个线程执行完同步区域
的时间是1分钟,那么其它线程就会占用着CPU的资源
进行空转1分钟。
2、CAS+自旋+yield:在自旋过程中去让出CPU资源(也就是CPU执行权)
缺点: 如果竞争的线程很多,那么就会导致执行特别多的无效的让出CPU资源
的情况,且极容易造成要执行的线程始终无法得到CPU资源
即:因为让出CPU资源的线程在下一次还是会继续参与竞争,
所以很可能导致每次得到CPU资源的还是上一次让出CPU资源的线程。
3、CAS+自旋+sleep:在自旋过程中通过sleep方法去让线程阻塞
缺点:sleep方法必须传入一个需要阻塞的时间,但是这个时间调用者
不能得到一个合理的高效的值且这个时间在不同业务场景下也是动态变化的,
不可估算,只能被动等到休眠时间达到时解除阻塞,如果用中断的方式,
那么要抛异常且是一种暴力的通知方式,不建议使用。
4、CAS+自旋+park(LockSupport)+队列:-----AQS
在自旋的过程中通过park方法去让线程阻塞,
并且利用队列去约束线程的执行顺序。
AQS(AbstractQueuedSynchronizer)本质上是什么?
AQS本质上是一个队列
那么既然是队列那么就会遵循先进先出的规则,那何来的公平与非公平呢?
答:虽然解锁的过程是公平的(解锁就是讲AQS中的第二个元素的线程进行唤醒),但是加锁过程中如果有不在队列中的线程参与竞争抢锁的情况,那么非公平锁是不做哦按段直接修改AQS的状态加锁成功,这样就会导致如果是非队列中的线程修改成功,那么原有队列中的第二个元素就会被一道末尾去排队
juc( java.utill.concurrent )下的所有的锁的加锁过程都是去获取AQS中的第二个元素然后再去加锁(修改状态)实现的
ReentrantLock(重入锁),包含公平锁和非公平锁,
1.公平锁:
加锁过程:
第一个线程获取锁时(lock())
获取当前state=0,通过CAS(compareAndSwapInt)将state由0改为1,并设置当前线程1为锁的持有者(exclusiveOwnerThread),至此线程1成功获取锁.
当第一个线程再次获取锁(lock())
获取当前state=1,则表示已经有线程获取锁了,判断,当前线程是否为持有锁的线程,在这里,是持有锁的线程,则重入锁即可以再次获取锁,并设置state+1.
第二个线程来获取锁(此时假设,第一个线程还未释放锁)
获取当前state=1,表示有线程已经获取锁了,并且,判断出当前线程并非拥有锁的线程,则不能重入锁.第二个线程将进入队列,将第二个线程封装成Node节点,目前队列里并没有节点,即首节点head,尾节点tail均为null,则创建一个默认head节点(),此时首节点也是尾节点tail.然后将第二线程的Node 的pre节点只想head节点,CAS设置tail节点为第二线程节点,将head节点的next节点指向第二线程节点,形成一个双向链表.
然后获取第二线程的节点的pre节点,目前是head节点,是head节点则尝试调用tryAcquire()再次获取锁,因为线程一还未释放锁,则获取锁失败.然后通过CAS将pre节点即head节点的waitStatus设置为-1.然后再次尝试获取锁,继续获取锁失败,之后通过LockSupport.park阻塞当前线程.
第二线程,在获取锁失败后,做了一个线程等待队列的初始化.
第三个线程,此时第一个线程仍然没有解锁.
获取当前state =1 ,说明已经有线程获取锁了,并且不属于当前线程,所以当前线程获取锁失败.
第三个线程将进入队列,将当前线程封装成一个NODE节点,并将该NODE的pre节点指向上一个节点,并CAS将当前NODE设为tail节点,设置成功后,将前一个节点的next指向当先节点,此过程相当于向队列中加了一个节点.然后将前一个节点的waitStatus设置为-1,然后是当前线程阻塞.
此时,第一线程执行结束,释放了锁.state=0
第四个线程,因为现在第一个线程已经释放锁了,所以此时获取到的state=0,然后第四线程尝试获取锁,但是因为第四个线程不在队列里,且更不是队列里的第二个节点,所以获取锁失败,之后加入队列进行排队.
解锁:
第一个线程调用unlock() 进行解锁,将state设置为0,然后获取队列中的第二个节点,通过SupportLock.unpack()唤醒第二个节点对应的线程.
//ReentrantLock的加锁过程
//公平锁
/*
* ReentrantLock:
* private final Sync sync;
*/
public void lock() {
sync.lock();
}
/*
*static final class FairSync extends Sync:
*/
final void lock() {
acquire(1);//要去把state从0通过CAS改为1,相当于获取锁
}
/*
* abstract class AbstractQueuedSynchronizer:
*/
public final void acquire(int arg) {
/*
* 首个线程为这把锁的唯一持有者时返回true
* 此时这个逻辑表达式就是false,那么该方法就执行结束
*
* 如果在首个线程还在执行同步代码区域过程中:
* 第二个线程调用这个方法:
* tryAcquire(arg)返回false,继续进行判断:
* 此时它要做的是将当前线程放入队列尾部,
* 所以此时调用acquireQueued方法,并且调用addWaiter方法将
* 这第二个线程包装成一个队列的Node对象,那么先看addWaiter方法
*
*
* 第三个线程调用这个方法:
* tryAcquire(arg)返回false,继续进行判断:
* 此时它要做的是将第三个线程放入队列尾部,
*/
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
我们先看公平锁的加锁过程:
/*
* static final class FairSync extends Sync:
* 这个方法是尝试去加锁:去看一下自己当前要不要排队
* 相当于是去竞争。
* Fair中重写后的方法:
* private volatile int state;
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
/*
* 获取当前AQS的状态
* 第二个线程进入时,这个状态为1
* 第三个线程进入时,这个状态依然为1
* 经过判断后不进入任何逻辑返回false
*/
int c = getState();
/*
* c==0:表示没有线程占用这把锁,然后当前线程要去加锁
* 此时锁是自由状态,分两种情况:
* 1、这个锁没有被任何线程持有过
* 2、上一个持有锁的线程已经释放锁,但是还没有下一个线程去持有锁的状态
*
*
*/
if (c == 0) {
if (!hasQueuedPredecessors() &&
//进行CAS将state由0改为1
compareAndSetState(0, acquires)) {
//将当前线程设置为这把锁的唯一持有者
setExclusiveOwnerThread(current);
return true;
}
}
/*
* 第二个线程进入时,这个状态为1就进行以下判断
* 判断出锁的持有线程不是当前线程,那么就不进入
* 不进入就直接返回false
*/
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
*
* abstract class AbstractQueuedSynchronizer:
*/
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
/*
* h != t : 判断头部和尾部是否不相等
* 没有线程占用锁(也就是第一次进入)时
* h=null,tail=null,所以相等
* 那么h != t 就为false,然后直接返回
*
*当第一个线程离开同步代码区域,释放锁时,
*此时锁处于在自由状态:
*那么h=空的Node,t是第三个线程,
*那么h != t 就为true,然后继续执行下一步判断:
*(s = h.next) == null || s.thread != Thread.currentThread()
*s=第二个线程的Node,所以不为null,那么表达式就是false,再继续判断
*s是不是当前执行到这里的线程:
*s.thread是第二个线程和第二个线程相等,所以是表达式是false,
*那么(s = h.next) == null || s.thread != Thread.currentThread()
*是false,那么整个返回false
*/
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
/*
* abstract class AbstractQueuedSynchronizer:
*/
private Node addWaiter(Node mode) {
//将当前线程封装成一个Node
Node node = new Node(Thread.currentThread(), mode);
//获取队列的尾部元素赋值给pred
Node pred = tail;
/*
* 判断这个元素是否为null,此时由于队列还未被初始化所以
* pred==null,不会进入这个判断
*
* 第三个线程进入时:
* pred==第二个线程的Node,pred!=null,所以进入这个判断
*/
if (pred != null) {
/*
* 将当前线程(第三个线程)的Node的上一个节点设置为
* 第二个线程的Node
*/
node.prev = pred;
/*
* 执行CAS将第三个线程的Node设置为队尾元素
* 如果设置成功,将第二个线程的Node的下一个节点
* 设置为当前线程(第三个线程)的Node
* 然后返回第三个线程的Node
*/
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//不进入判断就调用enq方法并将当前线程的Node传入,去看enq方法
enq(node);
//enq方法执行结束,返回当前线程的Node
return node;
}
/*
* abstract class AbstractQueuedSynchronizer:
*/
private Node enq(final Node node) {
for (;;) {
//进入循环,获取到尾部元素
Node t = tail;
/*
* 第一次循环,由于队列还没有被初始化,
* 所以尾部元素为null,所以进入;
* 第二次循环,由于第一次循环已经将队列初始化了,
* 所以尾部元素不为null,所以不进入:
* 那么就进入else中
*/
if (t == null) {
/*
* 创建一个空的Node,并通过CAS将其设置成头部
* 如果设置成功,那么将尾部元素也设置成头部元素
* 此时队列初始化完毕
*/
if (compareAndSetHead(new Node()))
tail = head;
} else {
/*
* 将封装了当前线程的Node的上一个节点设置为队列的尾部元素
* 也就是上一次循环创建出来的那个空的Node
*/
node.prev = t;
/*
* 执行CAS将尾部元素
* 也就是空的那个Node设置成封装了当前线程的Node
*/
if (compareAndSetTail(t, node)) {
/*
* 如果设置成功那么就将空的Node的下一个设置成当前线程的Node
* 此时头部元素为空Node,尾部元素为当前线程的Node
*/
t.next = node;
//然后返回空的Node,死循环跳出,方法执行结束
return t;
}
}
}
}
/*
* abstract class AbstractQueuedSynchronizer:
* 判断当前线程是否需要阻塞
* 1、如果队列中当前线程的Node的上一个Node阻塞了,
* 那么当前线程的Node就阻塞;
* 2、如果队列中当前线程的Node的上一个Node没有阻塞,
* 那么当前线程就竞争。
* 这里判断上一个Node是否阻塞就是去判断上一个节点
* 是不是头部,如果是,那么这个空Node的状态是不是-1,
* 也就是说判断当前线程的Node,是不是队列的第二个元素
*
*/
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
//死循环
for (;;) {
/*
* 获取到当前线程的Node的上一个节点
*
* 获取到第三个线程的上一个节点第二个线程的Node
*/
final Node p = node.predecessor();
/*
* 判断当前线程的Node的上一个节点是不是头部
* 此时当前线程的Node的上一个节点是头部,
* 那么调用 tryAcquire(arg)并将状态值1传入,对返回值继续判断
* 返回false,那么就不进入
*
* 判断第二个线程是否为头部元素,p!=head,
* 那么就不进入
*
*/
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null;
failed = false;
return interrupted;
}
/*
* 调用shouldParkAfterFailedAcquire方法
* 将当前线程的Node的上一个节点和当前线程的Node传入
* 判断在阻塞失败后去询问是否需要再阻塞,
* 此时返回false,本次循环结束,执行下一次循环
* 其它都没变,那么再次进入shouldParkAfterFailedAcquire
*
* 如果返回true,那么调用parkAndCheckInterrupt()
* 执行阻塞并且检查中断情况,对返回结果再判断
* 判断完后又进入下一次循环
*
* 第三个线程执行判断时:传入第二个线程和第三个线程
* 执行结束返回true,然后parkAndCheckInterrupt方法把第三个线程也阻塞
*/
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
/*
* abstract class AbstractQueuedSynchronizer:
* SIGNAL:-1
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
/*
* 获取到上一个Node的状态,此时状态为0
* 第二次进入时,状态为-1
*
*第三个线程执行时:
* 第一次进入获取到第二个线程的状态为0
* 第二次进入获取到第二个线程的状态为-1
*/
int ws = pred.waitStatus;
/*
* 判断这个状态是否为-1,此时为0,所以不进入
* 第二次进入时,状态为-1,所以进入后返回true
*
* 第三个线程执行时:第二个线程的状态为-1,那么直接返回true
*/
if (ws == Node.SIGNAL)
return true;
//判断这个状态是否大于0,此时为0,所以不进入
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* 执行CAS将当前线程的Node的上一个Node的
* state赋值为-1
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
//然后返回false
return false;
}
/*
* abstract class AbstractQueuedSynchronizer:
*/
private final boolean parkAndCheckInterrupt() {
/*
*第二个线程t2,在这里阻塞
*第三个线程t3,也在这里阻塞
*/
LockSupport.park(this);
return Thread.interrupted();
}
非公平锁的加锁过程:
final void lock() {
/*
* 直接进行CAS将state由0改为1
* 如果修改成功就将持有这个锁的线程设置为当前线程
*/
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
/*
* 如果修改失败,执行AQS的acquire方法
*/
else
acquire(1);
}
/*
* 这个方法就是在竞争
* Nonfair重写后的tryAcquire方法
*/
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
/*
* Nonfair中的方法
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
/*
* 和Fair的区别在于这里没有队列的一些方法
* 直接进行CAS将状态进行一个设置为1的操作
* 也就是说在竞争时非公平锁不会去判断当前线程
* 当前线程在队列中的位置进行判断后再决定要不要
* 执行加锁操作
* 整个队列每次去加锁的都是第二个元素,那哪里来的
* 公平和非公平一说呢?
*
*
* 操作成功后将这个锁持有线程设置为当前线程
* 然后返回true
*/
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中的Node对象:
static final class Node {
static final Node SHARED = new Node();
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
Node nextWaiter;
final boolean isShared() {
return nextWaiter == SHARED;
}
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() { // Used to establish initial head or SHARED marker
}
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}