手写MiniReentrantLock

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);
    }



}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值