手写mini版ReetrantLock(AQS公平锁)

手写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;
    }


}

万般皆下品,唯有读书高!
原文地址:https://www.cnblogs.com/s686zhou/p/15072935.html