package leetcode0606.JUC.AQS;
import leetcode0606.JUC.AQS.Mini.Lock;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;
public class MiniReetrantLock implements Lock {
/**
* 锁的是什么?
* 资源 -> state
* 0 表示未加锁状态
* >0 表示当前lock是加锁状态
*/
private volatile int state;
/**
* 独占模式?
* 同一时刻只有一个线程可以持有锁,其它的线程,在未获取到锁时,会被堵塞。
*/
// 占用锁的线程
private Thread exclusiveOwnerThread;
/** 需要有两个引用 去维护堵塞队列
* Head 指向队列的头节点,头节点也是获得锁的线程
* Tail
*/
private Node head;
private Node tail;
// 未获取到锁的线程 保留 在 堵塞FIFO队列中,堵塞的线程被封装为Node节点
static final class Node{
// FIFO 为双向链表,所以要有前置指针和后向指针
Node prev;
Node next;
// 被封装的线程
Thread thread;
public Node(Thread thread) {
this.thread = thread;
}
public Node() {
}
}
/**
* 获取锁
* 假设当前锁被占用,则会堵塞调用者线程,直到抢到锁为止
* 模拟公平锁,讲究先来后到。
*
* lock 的过程是怎么样的?
* 情景1:线程进来后发现,当前 state == 0 ,这个时候就很幸运,直接抢锁。
* 情景2:线程进来后发现,当前 state == 1 ,这个时候就需要将当前线程入队。
*/
@Override
public void lock() {
// 第一次获取到锁时,将state设置为1
// 重入锁为n
acquire(1);
}
/**
*竞争资源
* 1.尝试获取锁,成功则 占用锁 且 返回
* 2.抢占锁失败,堵塞当前线程
*/
private void acquire(int arg){
if(!tryAcquire(arg)){
// 更复杂的逻辑...
Node node = addWaiter();
acquireQueued(node,arg);
}
}
/*
尝试抢占锁失败,需要做什么?
1.需要将当前线程封装为node,加入到堵塞队列。
2.需要将当前线程park掉,使线程处于挂起状态
有挂起就要有唤醒,唤醒后。。
1.检查当前node节点,是否为head.next节点 (head.next是拥有抢占权限的线程,其它的node没有权限)
2.抢占
成功:将node 设置为head,将老的head出队,返回到业务层面
失败:继续park,等待被唤醒
综上, 将节点添加到堵塞队列的逻辑。 addWaiter()
竞争资源的逻辑。 acquireQueued()
*/
/*
?????
*/
private void acquireQueued(Node node,int arg){
// 只有当前node成功获取到锁,才会跳出自旋
for(;;){
// 什么情况,当前node被唤醒之后可以尝试去获取锁呢? 当前node为 head.next
Node pred = node.prev;
// 有权限去抢锁,然后尝试性地去抢一下
if(pred == head && tryAcquire(arg)){
// 抢到锁了
// 需要做点什么? 设置head为当前node,老head出队
setHead(node);
pred.next = null; // help GC
return;
}
// 走到这里
// 1.不是 head.next
// 2.是head.next节点,但是抢占锁失败
// 将当前线程挂起
System.out.println("线程:"+Thread.currentThread().getName()+".挂起");
LockSupport.park();
System.out.println("线程:"+Thread.currentThread().getName()+".唤醒");
// 什么时候唤醒被park的线程呢?
}
}
/**
* 当前线程入队
* 可以保证入队成功
*
*/
private Node addWaiter(){
Node newNode = new Node(Thread.currentThread());
/*
如何入队? 前置条件:队列已经有等待者node了,当前node不是第一个入队的nide
1.找到newNode的前置节点pred
2.更新newNode.prev = pred
3.CAS 更新 tail 为 newNode
4. 更新 pred.next = head;
*/
Node pred = tail;
if(pred != null){
newNode.prev = pred;
// compareAndSetTail(pred,newNode) true--> 成功入队
if(compareAndSetTail(pred,newNode)){
pred.next = newNode;
return newNode;
}
}
// 执行到这里的情况
// 1. 队列为空
// 2. CAS设置失败,竞争失败,进行循坏入队的逻辑
enq(newNode);
return newNode;
}
/*
自旋地去入队,只有成功后才返回
1.tail == null
2.CAS失败
*/
private void enq(Node node){
for(;;){
/*
CASE1 : 队列为空,当前线程应该是第二个来的线程,第一个线程正在持有锁,作为第一个线程的后继节点,需要给他擦屁股
给当前持有锁的线程补充一个node作为head节点,head节点任何时候,都代表当前占用锁的线程。
*/
if(tail == null){
// 条件成立,补充head成功
if(compareAndSetHead(new Node())){
tail = head;
// 注意:并没有返回,继续自旋
}
}else{
// CASE2 : 当前队列已经有node了,直接加
Node pred = tail;
if(pred != null){
node.prev = pred;
if(compareAndSetTail(pred,node)){
pred.next = node;
return;
}
}
}
}
}
/**
* 尝试获取锁,不会堵塞线程
* true --> 抢占成功
* false --> 抢占失败
*
*/
private boolean tryAcquire(int arg){
if(state == 0){
// 当前 state = 0 ,是否可以直接抢锁呢?
// 不可以哦,这是公平锁,讲究先来后到,要判断队列中是否有人在等,需要hasQueuedPredecessor()方法
// 条件一:!hasQueuedPredecessor() true--> 当前线程前面没有线程
// 条件二:compareAndSetState(0,arg) true--> 抢锁成功
if(!hasQueuedPredecessor() && compareAndSetState(0,arg)){
// 抢到锁了,需要干点啥?
// 1.需要将 exclusiveOwnerThread 设置为当前线程
this.exclusiveOwnerThread = Thread.currentThread();
return true;
}
/*
重入锁的逻辑
*/
}else if(Thread.currentThread() == this.exclusiveOwnerThread){
// 这里不存在并发,只有当前加锁的线程,才能有权限去修改state
int c = getState();
c = c + arg;
// 越界判断略
this.state = c;
return true;
}
/*
返回false的情况
1.CAS加锁失败
2.state >0,但是你表不是锁的持有者
*/
return false;
}
/**
* 判断当前node是否有前置node
* true --> 当前线程前面有等待者线程
* false --> 当前线程前面没有线程等待
*
* 调用链 lock() -> acquire() -> tryAcquire() -> hasQueuedPredecessor() (state==0时会调用)
*
* 什么时候返回false?
* 1.当前队列是空
* 2.当前线程为head.next节点,可以尝试去争取一下锁
*/
private boolean hasQueuedPredecessor(){
Node h = head;
Node t = tail;
Node s;
/*
条件1: h != t true->说明当前队列已经有node了;
false-> 说明h==t==null(未初始化队列) 或者 h == t == head(第一个抢占锁失败的线程,为持有锁的线程添加一个node)
条件2; ((s = h.next) == null || s.thread != Thread.currentThread())
前置条件:队列中已经有数据了
2.1 s = h.next == null 基本不可能,不用考虑
true 极端情况,第一个堵塞的线程(获取锁失败的线程)为 持有锁 的线程 补充创建 head,然后自旋入队
其实想表达的就是:已经有head.next节点了,其它线程再来这时,需要返回true
2.2 s.thread != Thread.currentThread() head.next就不是我所对应的线程
*/
// if(compareAndSetTail(pred,node)){
// pred.next = node; 这一步话没结束的时候调用hasQueuedPredecessor(),这时候还没赋值成功,此时h.next ==null
// return;
这个判断条件 是为了 使后面的条件可以用,存在某个时刻半初始化的状态,否则会报空指针异常!!!!!!!!!!!
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)){
// 走到这里,说明锁已经完全释放,需要 唤醒一个正在睡觉的线程
Node head = this.head;
// 确定有没有等待者
if(head.next != null){
//公平锁唤醒head.next节点
unparkSuccessor(head);
}
}
}
// 唤醒head.next节点
private void unparkSuccessor(Node node){
Node s = node.next;
if(s != null && s.thread != null){
LockSupport.unpark(s.thread);
}
}
/*
完全释放锁成功,则返回true
否则,说明当前state>0,则返回false
*/
private boolean tryRelease(int arg){
int c = getState() - arg;
if(getExclusiveOwnerThread() != Thread.currentThread()){
throw new RuntimeException("fuck you!must getLock!");
}
// 如果执行到这里,不存在并发
// 只有拿到锁的线程才会来到这里
// 锁已完成释放了
if(c == 0 ){
// 需要做什么?
// 1. exclusiveOwnerThread = null;
// 2. state = 0;
this.exclusiveOwnerThread = null;
this.state = c;
return true;
}
this.state = c;
return false;
}
// 设置头节点
private void setHead(Node node){
this.head = node;
// 为什么要设置为null?因为当前node已经是获取锁成功的线程了
node.thread = null;
node.prev = null;
}
public int getState() {
return state;
}
public Thread getExclusiveOwnerThread() {
return exclusiveOwnerThread;
}
public Node getHead() {
return head;
}
public Node getTail() {
return tail;
}
/*
利用CAS机制来实现 set , get 方法
*/
private static final Unsafe unsafe;
private static final long stateOffset;
private static final long headOffset;
private static final long tailOffset;
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 ex){
throw new Error(ex);
}
}
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,tailOffset,expect,update);
}
private final boolean compareAndSetState(int expect,int update){
return unsafe.compareAndSwapInt(this,stateOffset,expect,update);
}
}
手写MiniReentrantLock
最新推荐文章于 2024-10-01 18:51:18 发布