手写mini版ReetrantLock(AQS公平锁)
package com.xiaozhou;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
import java.util.concurrent.locks.LockSupport;
/**
* 大前提: 此次模拟的是公平锁 所以每个线程抢锁需要遵循先来后到
*/
public class MiniReetrantLock implements Lock{
//AQS必要的字段
/**
* 用来标记该独占锁 是否被占用
* state > 0 时被占用 当state >1 时 表示重入
* state = 0 时 说明未被占用
*/
private volatile int state;
/**
* 记录当前持有AQS独占锁的线程
*/
private Thread exclusiveOwnerThread;
//当该独占锁被占用时,其他线程尝试Lock 则会被阻塞,这些线程都会被放入一个阻塞队列中
//而这个阻塞队列 使用双端链表的形式来维护
//链表的头节点指针
private Node head;
//链表的尾节点指针
private Node tail;
//一个Node就代表一个线程
final class Node{
//存放线程
private Thread thread;
private Node prev;
private Node next;
public Node(Thread thread) {
this.thread = thread;
}
public Node() {
}
}
public int getState() {
return state;
}
public void setHead(Node node) {
this.head = node;
node.prev = null;
node.thread = null;
}
/**
* 由于state head tail 会存在竞争因此使用CAS操作
*/
private static final Unsafe unsafe;
//state head tail 在主内存中的偏移地址
private static final long stateOffset;
private static final long headOffset;
private static final long tailOffset;
//获取Unsafe对象,并获取对应的static字段的偏移量
static {
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
unsafe = (Unsafe) f.get(null);
stateOffset = unsafe.objectFieldOffset(MiniReetrantLock.class.getDeclaredField("state"));
headOffset = unsafe.objectFieldOffset(MiniReetrantLock.class.getDeclaredField("head"));
tailOffset = unsafe.objectFieldOffset(MiniReetrantLock.class.getDeclaredField("tail"));
} catch (Exception e) {
throw new Error(e);
}
}
//CAS设置state head tail
private final boolean compareAndSetState(int expect,int update){
return unsafe.compareAndSwapInt(this,stateOffset,expect,update);
}
private final boolean compareAndSetHead(Node update){
return unsafe.compareAndSwapObject(this,headOffset,null,update);
}
private final boolean compareAndSetTail(Node expect, Node update){
return unsafe.compareAndSwapObject(this,headOffset,expect,update);
}
//上面都是准备工作
/**
* 抢占锁
*/
@Override
public void lock() {
//抢占锁 要做的事情说白了 就是设置state 和 exclusiveOwnerThread 的值
acquire(1);
}
/**
* 该方法的主要目的就是要设置state
* @param arg
*/
private void acquire(int arg) {
//这里要尝试去设置state 也就是获取锁
if (!tryAcquire(arg)){
//获取锁失败
//获取锁失败的线程需要做什么呢?
//1. 入队 进入阻塞队列中去排队
Node node = addWaiter();
//2. 第一次入队完成 会再试图获取锁,若获取不到就会park,等待被unpark唤醒后,再去获取锁
acquireQueued(node,arg);
}
}
/**
* 入队后的节点首先会尝试去获取锁 获取不到则park
* 等待被unpark后 在进入自旋获取锁 这样重复下去
* @param node
*/
private void acquireQueued(Node node,int arg) {
for (;;){
//这里的逻辑是判断该节点的前驱节点是不是头节点
Node pred = node.prev;
if (pred == head && tryAcquire(arg)){
//获取锁成功
//此处不存在并发 这时只需要让头节点出队 自己做头节点就行了
pred.next = null;
setHead(node);
return;
}
//没有抢到锁则会unpark
LockSupport.park();
}
}
/**
* 入队操作
* @return 返回入队后的节点
*/
private Node addWaiter() {
//创建当前线程的节点
Node newNode = new Node(Thread.currentThread());
//赋值尾节点指针 pre tail 都指向尾节点
Node pred = tail;
/**
* 这里为什么要判断 尾节点是否为空呢 而不去判断头节点呢?
* 因为方便,之后的入队都是来操作尾节点
*/
if (pred != null){
//该情况说明当前 已经形成了阻塞队列
//尾节点的后继指针指向 入队的节点
newNode.prev = pred;
//将尾指针tail 指向newNode 说明newNode是当前队列的尾部节点 ,而此时的pred仍指向原来的尾节点
//这里存在并发,可能多个线程抢着去入队
if (compareAndSetTail(pred,newNode)){
pred.next = newNode;
return newNode;
}
}
enq(newNode);
return newNode;
}
/**
* 来到enq方法的都是什么情况?
* 1.当前队列是空 还未构成阻塞队列
* 2.cas失败 其它线程先一步入队了
* @param newNode
*/
private void enq(Node newNode) {
for (;;){
//还未构成阻塞队列
if (tail == null){
//此时的场景: 该独占锁是一个初始状态 来了某个线程抢占了这把锁(而此时该线程并不会构成Node节点),
// 因此在后面的线程抢锁失败需要入队时,需要为第一个进来的线程创建Node 且该Node是用空构造创建(给他擦屁股)
//这里也会存在并发 这些线程会无脑为第一个占有锁的线程擦屁股,获取好感。但是恕不知 擦了屁股也不一定可以占有抢锁的有利位置
if (compareAndSetHead(new Node())){
tail = head;
}
}else {
Node pred = tail;
newNode.prev = pred;
if (compareAndSetTail(pred,newNode)){
pred.next = newNode;
return; //入队成功 退出 不需要自旋了。
}
}
}
}
/**
* 尝试去获取锁
*
* 通过state来判断。
* 返回true的情况
* 1.state == 0 考虑:该情况 当前线程一定能获取到锁吗?
* 2.state != 0 可重入
*
* 返回false的情况
* state!=0 不是可重入 已经有其他线程已经占有了锁
*
*
*
*
*
* @param arg
* @return 获取到锁返回 true 获取不到锁返回false
*/
private boolean tryAcquire(int arg) {
if (state == 0){
/**
* 考虑:该情况 当前线程一定能获取到锁吗?
* 不一定, 因为当前是公平锁
* 有两种情况:
* 1. 环境: 该独占锁此时还未有任何线程占有过(阻塞队列为空 ,state ==0), 一切都是最新的状态
* 此时多线程来争抢这把锁时,则是谁先修改了state的值 谁就抢占了这把锁
*
*
* 2.环境: 该独占锁此时已经被释放(state==0),但是当前尝试获取锁的线程在阻塞队列中排在了后面。
* 由于是公平锁,每个抢锁的线程要遵循先来后到,在阻塞队列中(head是当前正在持有锁的线程,而只有head.next 下一个线程才有权限去获取锁)
*
* 那么就需要hasQueuedPredecessor() 方法来判断当前线程前是否有排队的
* compareAndSetState(0,arg) 这个是确保环境1下 多线程并发下能够争抢锁
*
*/
if (!hasQueuedPredecessor() && compareAndSetState(0,arg)){
//条件成立 说明该线程已经修改了state值
//修改当前AQS的拥有线程为当前线程
this.exclusiveOwnerThread = Thread.currentThread();
return true; //抢锁成功
}
}/*此处体现的是锁的可重入*/ else if (this.exclusiveOwnerThread == Thread.currentThread()){
//由于这里是可重入 不存在并发所以可以直接修改state
this.state = this.state + arg;
return true;
}
return false;
}
/**
* 判断当前线程前面 是否有排队的
* true 该线程前面有人 说明没有权限争抢所
* 有哪些情况是没有权限争抢锁的呢?
* 1.阻塞队列形成了 且有大于等于2个节点 而当前线程节点不是头节点的下一个节点 head.next != currentNode
* 2.一种临界情况 已经存在第一个头节点了,此时有一个线程节点在加入队列的过程当中... (处于2 3步骤之间 此时head.next == null) 记住这里是加入队列的过程当中还没有加入。
* 入队的操作:
* 前提 tail == head
* 1. newNode.prev = tail
* 2.将尾指针指向要入队的节点 casTail(tail,newNode)
* 3.将head.next = newNode
*
*
* false 该线程前面没有人 说明有权限争抢锁
* 有哪些情况是有权限争抢锁呢?
* 1. 阻塞队列还未形成 head == tail == null
* 2. 阻塞队列形成 但只有一个节点 head == tail != null
* 3. 阻塞队列形成了 且有大于等于2个节点 而当前线程节点是头节点的下一个节点 head.next = currentNode
*
* @return
*/
private boolean hasQueuedPredecessor(){
Node h = this.head;
Node t = this.tail;
Node s;
return h != t && ((s = h.next) == null || s.thread != Thread.currentThread());
}
//释放锁
@Override
public void unlock() {
release(1);
}
private void release(int arg) {
if (tryRelease(arg)){
//完全释放锁成功 state = 0
Node head = this.head;
//需要唤醒下一个节点
if (head.next != null){
unParkSuccessor(head);
}
}
}
private void unParkSuccessor(Node head) {
Node s = head.next;
//单点唤醒头节点后面的 节点 公平锁
if (s != null && s.thread != null){
LockSupport.unpark(s.thread);
}
}
/**
*
* @param arg
* @return true 完全释放锁state-arg = 0 false 没有完全释放锁 还是重入状态下 state-arg > 0
*/
private boolean tryRelease(int arg) {
int c = this.state - arg;
if (this.exclusiveOwnerThread != Thread.currentThread()){
throw new RuntimeException("你不可以释放该锁");
}
if (c == 0){
//置为最初状态
this.exclusiveOwnerThread = null;
this.state = 0;
return true;
}
//重入的情况下
this.state = c;
return false;
}
}