【锁】自旋锁-MCS/CLH队列

8 篇文章 0 订阅
7 篇文章 0 订阅
1.自旋锁-MCS/CLH队列
2.自旋锁
  • 指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上线文切换的消耗,缺点是循环会消耗CPU

2.1 CAS

JUC包里的AtomicInteger类底层用CAS机制的实现就是一种自旋锁,以下是其底层核心类UnSafe的一段源码:
在这里插入图片描述
具体可查看:【JUC】 Java中的CAS

2.2 我们来自己实现一个简单的自旋锁,Demo:

/**
 * 手写一个自旋锁:循环比较知道成功为止
 *
 * @author wangjie
 * @version V1.0
 * @date 2019/12/18
 */
public class SpinLockDemo {

    /**
     *  原子引用线程
     */
    AtomicReference<Thread> atomicReference = new AtomicReference();

    public void myLock(){

        Thread thread = Thread.currentThread();

        System.out.println(Thread.currentThread().getName()+"\t come in");

        while (!atomicReference.compareAndSet(null,thread)){

        }
    }

    public void myUnLock(){

        Thread thread = Thread.currentThread();
        atomicReference.compareAndSet(thread,null);
        System.out.println(Thread.currentThread().getName()+"\t in myUnLock()");
    }

    public static void main(String[] args) {

        SpinLockDemo spinLockDemo = new SpinLockDemo();

        new Thread(()->{
            spinLockDemo.myLock();
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            spinLockDemo.myUnLock();
        },"AA").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            spinLockDemo.myLock();

            spinLockDemo.myUnLock();
        },"BB").start();

    }
}
  • 运行结果:
    在这里插入图片描述
3.MCS 自旋锁
  • MCS含义:

发明人的名字:John Mellor-Crummey和Michael Scot

  • 介绍:

MCS 队列基于链表实现,每个申请锁的线程都是链表上的一个节点,这些线程会一直轮询自己的本地变量,来知道它自己是否获得了锁。已经获得了锁的线程在释放锁的时候,负责通知其它线程,这样 CPU 之间缓存的同步操作就减少了很多,仅在线程通知另外一个线程的时候发生,降低了系统总线和内存的开销。

  • Demo:
/**
 * MCS自旋锁
 *
 * @author wangjie
 * @version V1.0
 * @date 2019/12/20
 */
public class MCSLock {

    /**
     * 链表节点
     */
    public static class MCSNode {
        volatile MCSNode next;
        volatile boolean isWaiting = true; // 默认是在等待锁
        MCSNode(){

        }
    }

    /**
     * 指向最后一个申请锁的MCSNode
     */
    volatile MCSNode queue;


    /**
     * 一个基于反射的工具类,它能对指定类的指定的volatile引用字段进行原子更新。(注意这个字段不能是private的)
     *
     * 通过调用AtomicReferenceFieldUpdater的静态方法newUpdater就能创建它的实例,该方法要接收三个参数:
     *     包含该字段的对象的类
     *     将被更新的对象的类
     *     将被更新的字段的名称
     */
    private static final AtomicReferenceFieldUpdater<MCSLock, MCSNode> UPDATER = AtomicReferenceFieldUpdater
            .newUpdater(MCSLock.class, MCSNode.class, "queue");


    public void lock(MCSNode currentThread) {

        System.out.println(Thread.currentThread().getName()+"\t come in");

        /**
         * 原子更新该更新器管理的指定对象的字段的值为newValue,返回旧值
         */
        MCSNode predecessor = UPDATER.getAndSet(this, currentThread);
        //前个节点不为null
        if (predecessor != null) {
            //设置前一个节点指向当前节点
            predecessor.next = currentThread;
            while (currentThread.isWaiting) {
                //自旋自己的isWaiting
                System.out.println(Thread.currentThread().getName()+"\t Waiting");
            }
        } else { // 只有一个线程在使用锁,没有前驱来通知它,所以得自己标记自己已获得锁
            currentThread.isWaiting = false;
            System.out.println(Thread.currentThread().getName()+"\t get lock");
        }
    }

    public void unlock(MCSNode currentThread) {

        System.out.println(Thread.currentThread().getName()+"\t in UnLock()");

        if (currentThread.isWaiting) {
            return;
        }
         // 检查是否有人排在自己后面
        if (currentThread.next == null) {
            if (UPDATER.compareAndSet(this, currentThread, null)) {
                // compareAndSet返回true表示确实没有人排在自己后面
                return;
            } else {
                // 突然有人排在自己后面了,可能还不知道是谁,下面是等待后续者
                while (currentThread.next == null) {
                }
            }
        }
        currentThread.next.isWaiting = false;
        // 辅助GC
        currentThread.next = null;
    }

    public static void main(String[] args) {

        MCSLock mCSLock = new MCSLock();

        new Thread(()->{
            MCSNode node = new MCSNode();
            mCSLock.lock(node);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            mCSLock.unlock(node);
        },"AA").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            MCSNode node = new MCSNode();
            mCSLock.lock(node);
            mCSLock.unlock(node);
        },"BB").start();

    }
}
  • 运行结果:
    在这里插入图片描述
    在这里插入图片描述
4.CLH 自旋锁
  • CLH含义:

与他们的发明人的名字相关:Craig,Landin and Hagersten

  • 介绍:

CLH 锁与 MCS 锁的原理大致相同,都是各个线程轮询各自关注的变量,来避免多个线程对同一个变量的轮询,从而从 CPU 缓存一致性的角度上减少了系统的消耗。不同的是,MCS 轮询的是当前队列节点的变量,而 CLH 轮询的是当前节点的前驱节点的变量,来判断前一个线程是否释放了锁。

  • Demo:
/**
 * CLH队列自旋锁
 *
 * @author wangjie
 * @version V1.0
 * @date 2019/12/20
 */
public class CLHLock {

    public static class CLHNode {
        private volatile boolean isWaiting = true; // 默认是在等待锁
    }

    /**
     * 尾节点
     */
    private volatile CLHNode tail ;

    /**
     * 一个基于反射的工具类,它能对指定类的指定的volatile引用字段进行原子更新。(注意这个字段不能是private的)
     *
     * 通过调用AtomicReferenceFieldUpdater的静态方法newUpdater就能创建它的实例,该方法要接收三个参数:
     *     包含该字段的对象的类
     *     将被更新的对象的类
     *     将被更新的字段的名称
     */
    private static final AtomicReferenceFieldUpdater<CLHLock, CLHNode> UPDATER = AtomicReferenceFieldUpdater
            . newUpdater(CLHLock.class, CLHNode .class , "tail" );


    public void lock(CLHNode currentThread) {
        CLHNode preNode = UPDATER.getAndSet( this, currentThread);

        //已有线程占用了锁,进入自旋
        if(preNode != null) {
            while(preNode.isWaiting ) {
                System.out.println(Thread.currentThread().getName()+"\t Waiting");
            }
        }
    }

    public void unlock(CLHNode currentThread) {
        // 如果队列里只有当前线程,则释放对当前线程的引用(for GC)。
        if (!UPDATER .compareAndSet(this, currentThread, null)) {
            // 还有后续线程
            // 改变状态,让后续线程结束自旋
            System.out.println(Thread.currentThread().getName()+"\t unlock");
            currentThread.isWaiting = false ;
        }
    }
    public static void main(String[] args) {

        CLHLock cLHLock = new CLHLock();

        new Thread(()->{
            CLHNode node = new CLHNode();
            cLHLock.lock(node);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            cLHLock.unlock(node);
        },"AA").start();

        try {
            TimeUnit.MILLISECONDS.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            CLHNode node = new CLHNode();
            cLHLock.lock(node);
            cLHLock.unlock(node);
        },"BB").start();

    }
}

  • 运行结果:
    在这里插入图片描述
    总结

1,CLH是在前驱节点的属性上自旋,而MCS是在本地属性变量上自旋。

2,CLH的队列是隐式的,通过轮询关注上一个节点的某个变量,隐式地形成了链式的关系,但CLHNode并不实际持有下一个节点,MCS的队列是物理存在的,而 CLH 的队列是逻辑上存在的

3,CLH 锁释放时只需要改变自己的属性,MCS 锁释放则需要改变后继节点的属性。

【完】
:文章内所有测试用例源码:https://gitee.com/wjie2018/test-case.git

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值