• Synchronized和Lock, 以及自旋锁 Spin Lock, Ticket Spin Lock, MCS Spin Lock, CLH Spin Lock


    Synchronized和Lock

    synchronized是一个关键字, Lock是一个接口, 对应有多种实现. 使用synchronized进行同步和使用Lock进行同步的区别

    • 使用synchronized同步时, 未获得锁的进程只能等待. 而使用Lock进行同步时, 有多种选择: 例如用读写锁区分不同的同步需求, 用tryLock使未获得锁的线程立即返回或在一段时间后返回, 或者在等待时可以随时响应中断后返回.
    • 使用synchronized无法知道线程是否成功获取到锁, 使用Lock可以
    • synchronized不需要手动释放锁, 在代码块执行结束或发生异常时, jvm会让线程自动释放锁. 而Lock需要手动释放锁, 如果有未释放的情况, 就会出现死锁.

    Spin Lock

    自旋锁是指当一个线程尝试获取某个锁时,如果该锁已被其他线程占用,就一直循环检测锁是否被释放,而不是进入线程挂起或睡眠状态。自旋锁适用于锁保护的临界区很小的情况,临界区很小的话,锁占用的时间就很短。

    public class Spinlock {
        private AtomicReference<Thread> owner = new AtomicReference<>();
    
        private void lock() {
            Thread current = Thread.currentThread();
            while (!owner.compareAndSet(null, current)) {
            }
        }
        private void unlock() {
            Thread current = Thread.currentThread();
            owner.compareAndSet(current, null);
        }
    }

    Ticket Spin Lock

    为了解决Spin Lock中随机不公平的问题, 使用排队自旋锁

    public class TicketSpinlock {
        private AtomicInteger ticket = new AtomicInteger();
        private AtomicInteger inService = new AtomicInteger();
    
        public int lock() {
            int myTicket = ticket.getAndIncrement();
            while (myTicket != inService.get()) {
            }
            return myTicket;
        }
        // 只有持有锁的才能释放锁
        public void unlock(int ticket) {
            int next = ticket + 1;
            inService.compareAndSet(ticket, next);
        }
    }

    MCS Spin Lock

    MCS锁是基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,直接前驱负责通知其结束自旋, 减少不必要的处理器缓存同步的次数,降低总线和内存的开销.

    public class McsSpinlock {
        public static class McsNode {
            volatile McsNode next;
            volatile boolean isBlock = true; // 默认是在等待锁
        }
    
        volatile McsNode queue;// 指向最后一个申请锁的MCSNode
        private static final AtomicReferenceFieldUpdater<McsSpinlock, McsNode> UPDATER =
                AtomicReferenceFieldUpdater.newUpdater(McsSpinlock.class, McsNode.class, "queue");
    
        public void lock(McsNode currentThread) {
            McsNode predecessor = UPDATER.getAndSet(this, currentThread);// step 1
            if (predecessor != null) {
                predecessor.next = currentThread;// step 2
    
                while (currentThread.isBlock) {// step 3
                }
            } else { // 只有一个线程在使用锁,没有前驱来通知它,所以得自己标记自己为非阻塞
                currentThread.isBlock = false;
            }
        }
    
        public void unlock(McsNode currentThread) {
            if (currentThread.isBlock) {// 锁拥有者进行释放锁才有意义
                return;
            }
            if (currentThread.next == null) {// 检查是否有人排在自己后面
                if (UPDATER.compareAndSet(this, currentThread, null)) {// step 4
                    // compareAndSet返回true表示确实没有人排在自己后面
                    return;
                } else {
                    // 突然有人排在自己后面了,可能还不知道是谁,下面是等待后续者
                    // 这里之所以要忙等是因为:step 1执行完后,step 2可能还没执行完
                    while (currentThread.next == null) { // step 5
                    }
                }
            }
            currentThread.next.isBlock = false;
            currentThread.next = null;// for GC
        }
    }

    CLH Spin Lock

    基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,它不断轮询前驱的状态,如果发现前驱释放了锁就结束自旋。

    public class ClhSpinlock {
        public static class ClhNode {
            private volatile boolean isLocked = true; // 默认在等待
        }
        private volatile ClhNode tail ;
        private static final AtomicReferenceFieldUpdater<ClhSpinlock, ClhNode> UPDATER = 
                AtomicReferenceFieldUpdater. newUpdater(ClhSpinlock.class, ClhNode.class , "tail" );
    
        public void lock(ClhNode currentThread) {
            ClhNode preNode = UPDATER.getAndSet( this, currentThread);
            if(preNode != null) {//已有线程占用了锁,进入自旋
                while(preNode.isLocked ) {
                }
            }
        }
    
        public void unlock(ClhNode currentThread) {
            // 如果队列里只有当前线程,则释放对当前线程的引用(for GC)。
            if (!UPDATER .compareAndSet(this, currentThread, null)) {
                // 还有后续线程
                currentThread. isLocked = false ;// 改变状态,让后续线程结束自旋
            }
        }
    }

    JDK Locks: ReentrantLock

    JDK的concurrent.locks包下的ReentrantLock, 用于实现响应中断的非阻塞锁, 里面模仿CLH Lock的机制, 实现了公平队列和非公平队列. 官方文档里使用了一个互相鞠躬的例子来举例ReentrantLock的使用

    public class Safelock {
    
        static class Friend {
            private final String name;
            private final Lock lock = new ReentrantLock();
    
            public Friend(String name) {this.name = name;}
            public String getName() {return this.name;}
    
            public boolean impendingBow(Friend bower) {
                // 非阻塞, 当两个锁都拿到时, 才bowback
                Boolean myLock = false;
                Boolean yourLock = false;
                try {
                    myLock = lock.tryLock();
                    yourLock = bower.lock.tryLock();
                } finally {
                    if (!(myLock && yourLock)) {
                        // 此步拿到的锁, 不用了要立即释放
                        if (myLock) {
                            lock.unlock();
                        }
                        if (yourLock) {
                            bower.lock.unlock();
                        }
                    }
                }
                return myLock && yourLock;
            }
    
            public void bow(Friend bower) {
                if (impendingBow(bower)) {
                    try {
                        System.out.format("%s: %s has bowed to me!%n", bower.getName(), this.name);
                        bower.bowBack(this);
                    } finally {
                        // 用完后及时释放锁
                        lock.unlock();
                        bower.lock.unlock();
                    }
                } else {
                    System.out.format(
                            "%s: %s wanted to bow to me, but saw that I was bowing.%n",
                            bower.getName(), this.name);
                }
            }
    
            public void bowBack(Friend bower) {
                System.out.format("%s: %s has bowed back to me!%n", bower.getName(), this.name);
            }
        }
    
        static class BowLoop implements Runnable {
            private Friend bower;
            private Friend bowee;
    
            public BowLoop(Friend bower, Friend bowee) {
                this.bower = bower;
                this.bowee = bowee;
            }
    
            public void run() {
                Random random = new Random();
                for (; ; ) {
                    try {
                        Thread.sleep(random.nextInt(10));
                    } catch (InterruptedException e) {}
                    bowee.bow(bower);
                }
            }
        }
    
        public static void main(String[] args) {
            final Friend alphonse =
                    new Friend("Alphonse");
            final Friend gaston =
                    new Friend("Gaston");
            final Friend hudson =
                    new Friend("Hudson");
            new Thread(new BowLoop(alphonse, hudson)).start();
            new Thread(new BowLoop(hudson, gaston)).start();
            new Thread(new BowLoop(gaston, alphonse)).start();
        }
    }
  • 相关阅读:
    CSS布局之浮动原理
    img中的alt和title的区别
    重绘+重排+项目优化
    事件冒泡、事件捕获和事件委托
    js数组方法总结
    flex布局
    冒泡排序
    函数节流和函数防抖
    从一个url地址最终到页面渲染完成,发生了什么
    如何让网站变灰
  • 原文地址:https://www.cnblogs.com/milton/p/6710792.html
Copyright © 2020-2023  润新知