zoukankan      html  css  js  c++  java
  • 手写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;
        }
    
    
    }
    
    
    万般皆下品,唯有读书高!
  • 相关阅读:
    快速修改elementUI组件样式
    设置占位符颜色!
    缩放屏幕的时候banner被压缩变形!
    less一些用法整理
    CentOS 7 的目录结构介绍
    为什么需要VMware的 快照、克隆?
    虚拟机概述
    Linux的简单介绍
    VMware克隆已安装的虚拟机
    【vim使用】分屏命令、操作分屏
  • 原文地址:https://www.cnblogs.com/s686zhou/p/15072935.html
Copyright © 2011-2022 走看看