并发编程(5)-CLH与MCS队列锁

自旋锁

    一般解决多线程共享资源逻辑一致性问题有两种方式:

    互斥锁:发现资源被占用时,阻塞自己直到资源解除占用,然后再次尝试获取锁;

    自旋锁:发现资源占用时,一直尝试获取锁,线程不会被挂起,即没有线程调度切换的消耗;

SMP

    对称多处理(Symmetrical Multi-Processing),是指在一个计算机上汇集了一组处理器(多CPU),各CPU之间共享内存子系统以及总线结构。SMP能够保证内存一致性,但这些共享的资源很可能成为性能瓶颈,随着CPU数量的增加,每个CPU都要访问相同的内存资源,可能导致内存访问冲突,可能会导致CPU资源的浪费。常用的PC机就属于这种。

NUMA

    非统一内存访问(Non Uniform Memory Access Architecture)技术可以使众多服务器像单一系统那样运转,同时保留小系统便于编程和管理的优点。它将CPU分为CPU模块,每个CPU模块由多个CPU组成,并且具有独立的本地内存、I/O槽口等,模块之间可以通过互联模块相互访问,因为访问远程内存的延时远远超过本地内存,系统性能无法线性增加。

CLH队列锁

    (Craig, Landin, Hagersten三位作者的缩写)CLH 队列锁是一种基于链表可扩展高性能公平的自旋锁,申请线程仅仅在本地变量上自旋,它不断轮询前驱的状态,假设发现前驱释放了锁就结束自旋。CLH在SMP系统结构下非常有效。但在NUMA系统结构下,每个线程有自己的内存,如果前趋结点的内存位置比较远,自旋判断前趋结点的locked域,性能将大打折扣。

    当一个线程需要获取锁时,会创建一个QNode,将其中的locked设置为true,表示需要获取锁,myPred表示对其前驱结点的引用。线程通过调用tail域的getAndSet方法,使自己成为队列尾部,同时获取一个指向其前驱结点的引用myPred。然后线程就在前驱结点的locked字段上自旋。直到前驱结点释放锁[前驱节点的locked == false]。当一个线程需要释放锁时,将当前结点的locked域设置为false,同时回收前驱结点。

    Java中的AQS是CLH队列锁的一种变体实现。

public class CLHLockTest implements Lock {
    private AtomicReference<QNode> tail = new AtomicReference<>(new QNode());
    private ThreadLocal<QNode> preNode = new ThreadLocal<QNode>() {
        @Override
        protected QNode initialValue() {
            return null;
        }
    };
    private ThreadLocal<QNode> node = new ThreadLocal<QNode>() {
        @Override
        protected QNode initialValue() {
            return new QNode();
        }
    };

    @Override
    public void lock() {
        QNode qNode = node.get();
        qNode.locked = true;
        QNode pred = tail.getAndSet(qNode);
        preNode.set(pred);
        while (pred.locked){

        }
    }

    @Override
    public void unlock() {
        QNode qNode = node.get();
        qNode.locked = false;
        node.set(preNode.get());
    }

    @Override
    public boolean tryLock() {
        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public Condition newCondition() {
        return null;
    }

    private class QNode {
        private volatile boolean locked;
    }

    private static class Chd implements Runnable {
        CLHLockTest c = null;
        public Chd(CLHLockTest test) {
            this.c = test;
        }
        @Override
        public void run() {
            try {
                c.lock();
                SleepTools.ms(100);
                System.out.println("线程名称:"+Thread.currentThread().getName());
            } finally {
                c.unlock();
            }
        }
    }

    public static void main(String[] args) {
        CLHLockTest lock = new CLHLockTest();
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(new Chd(lock));
            thread.setName("第" + i + "个线程");
            thread.start();
        }
    }
}

MCS队列锁

    与CLH是在前趋结点的locked域上自旋等待不同的是,MCS是在自己的结点的locked域上自旋等待。因此,它解决了CLH在NUMA系统架构中获取locked域状态内存过远的问题。

    MCS队列初始化时没有结点:tail=null。当线程A想要获取锁,它的locked域为false。当线程B和C相继加入队列:A->B->C。这时B和C处于等待状态,所以他们的locked域为true。当线程A释放锁后,顺着NEXT指针找到线程B,并把B的locked域设置为false,这时会触发线程B获取锁。

public class MCSLockTest implements Lock {
    private AtomicReference<QNode> tail = new AtomicReference<>();
    private ThreadLocal<QNode> node = new ThreadLocal<QNode>(){
        @Override
        protected QNode initialValue() {
            return new QNode();
        }
    };

    @Override
    public void lock() {
        QNode qNode = node.get();
        qNode.locked = true;
        QNode pre = tail.getAndSet(qNode);
        if (null != pre) {
            pre.next = qNode;
            while (qNode.locked) {

            }
        }
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public boolean tryLock() {
        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    @Override
    public void unlock() {
        QNode qNode = node.get();
        if (null == qNode.next) {
            if (tail.compareAndSet(qNode, null)) {
                return;
            }
            while (null == qNode.next) {

            }
        }
        qNode.next.locked = false;
        qNode.next = null;
    }

    @Override
    public Condition newCondition() {
        return null;
    }

    private class QNode {
        private volatile boolean locked;
        QNode next = null;
    }

    private static class Chd implements Runnable {
        MCSLockTest c = null;
        public Chd(MCSLockTest test) {
            this.c = test;
        }
        @Override
        public void run() {
            try {
                c.lock();
                SleepTools.ms(100);
                System.out.println("线程名称:"+Thread.currentThread().getName());
            } finally {
                c.unlock();
            }
        }
    }

    public static void main(String[] args) {
        MCSLockTest lock = new MCSLockTest();
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(new Chd(lock));
            thread.setName("第" + i + "个线程");
            thread.start();
        }
    }
}

 

 

_____个人笔记_____((≡^⚲͜^≡))_____欢迎指正_____

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值