乐趣区

关于lock:排队打饭公平锁和非公平锁面试

简介


有个小伙伴最近征询我,前段时间他被 面试官 问了 synchronized偏心锁还是 非偏心锁 ?过后就蒙圈了,最初面试后果可想而知,明天咱们就用一个艰深的案例加上代码来阐明 偏心锁 非偏心锁 。其实偏心锁这个概念是 JUC 工具包才有的,比方 ReentrantLock 才有偏心锁的概念,这篇文章咱们联合生存中的实例用 2 段代码阐明ReentrantLock 偏心锁和非偏心锁,以及证实 synchronized 是非偏心锁的。 心愿对小伙伴有帮忙。

偏心锁、非偏心锁概念


  • 偏心锁:举一个简略例子,有五个同学每天必须排队去打饭,为了简略起见,咱们给这五名同学每人定义一个编号,别离为 编号 001 编号 005,这五名同学按 先来后到 的排队,打饭,先来的同学能先打到饭。每个同学都是一个线程,在这个过程中起初的同学是 不容许插队的,这就是偏心锁
  • 非偏心锁:起初到同学不肯定后打到饭,在打饭的过程中,是容许插队的,这种线程插入的行为人们认为是不偏心的。举个例子,比方编号为 001,002,003,004 的同学先到先排队了,005 最初来排队本应该排在 004 前面的,然而 005 看 001 正好打完饭来到,他就去插队了,也就是打饭的程序由 001->002->003->004->005 变为 001->005->002->003->004。其实你当初应该了解了,偏心锁就是失常排队,非偏心就是插队。当然你可能会有疑难?是不是 005 插到 001 的前面肯定会胜利,答案是不肯定,这要看机会的,咱们方才说了“005 看 001 正好打完饭来到”,上面应该是 002 了,可能打饭阿姨还没问 002 筹备吃什么,就看 005 曾经排到后面去了,那 005 就插队胜利了,这就是机会。上面咱们用程序代码来加深了解。

synchronized 非偏心锁


/**
 * @author:jiaolian
 * @date:Created in 2020-12-31 16:01
 * @description:食堂打饭:synchronized 不偏心
 * @modified By:* 公众号: 叫练
 */
public class SyncUnFairLockTest {

    // 食堂
    private static class DiningRoom {
        // 获取食物
        public void getFood() {System.out.println(Thread.currentThread().getName()+": 排队中");
            synchronized (this) {System.out.println(Thread.currentThread().getName()+":@@@@@@打饭中 @@@@@@@");
            }
        }
    }

    public static void main(String[] args) {DiningRoom diningRoom = new DiningRoom();
        // 让 5 个同学去打饭
        for (int i=0; i<5; i++) {new Thread(()->{diningRoom.getFood();
            },"同学编号:00"+(i+1)).start();}
    }
}

如上代码:咱们定义一个外部类 DiningRoom 示意食堂,getFood 办法外面用 synchronized 锁润饰 this 指向 DiningRoom 的实例对象(22 行中的 diningRoom 对象),主类中让编号 001 至 005 五个同学同时去打饭,用于测试先排队的同学是否能先打到饭?运行程序失去 其中 一种 执行后果如下图所示,002->004->001->003->005 同学先去排队,但打饭的程序是 002->003->001->004->005,阐明这里 003 和 001 两个同学插队了,插到 004 后面了,咱们详细分析执行过程,002 先抢到锁打饭了,开释了锁,原本应该是接下来是 004 抢到锁去打饭(因为 004 是比 003 先来排队),但 003 抢到锁,打饭了,开释了锁,这是第一次插队。当初还是来 004 抢锁,然而没抢到又被 001 抢到了,开释锁后才被 004 抢到,这是第二次插队,前面别离再是 004->005 抢到锁,开释锁,程序执行结束。因为 003 和 001 插队,咱们用代码 证实了 synchronized 非偏心锁。紧接着咱们来看下ReentrantLock 偏心锁和非偏心锁。

ReentrantLock 非偏心锁


import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author:jiaolian
 * @date:Created in 2020-12-31 11:11
 * @description:非偏心锁测试  在获取锁的时候和再获取锁的程序不统一;
 * @modified By:* 公众号: 叫练
 */
public class UnFairLockTest {private static final Lock LOCK = new ReentrantLock(false);

    // 食堂
    private static class DiningRoom {
        // 获取食物
        public void getFood() {
            try {System.out.println(Thread.currentThread().getName()+": 正在排队");
                LOCK.lock();
                System.out.println(Thread.currentThread().getName()+":@@@@@@打饭中 @@@@@@@");
            } catch (Exception e) {e.printStackTrace();
            } finally {LOCK.unlock();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {DiningRoom diningRoom = new DiningRoom();
        // 让 5 个同学去打饭
        for (int i=0; i<5; i++) {new Thread(()->{diningRoom.getFood();
            },"同学编号:00"+(i+1)).start();}
    }
}

如上代码:咱们在代码第 13 行中定义了 Lock LOCK = new ReentrantLock(false);ReentrantLock 的参数是 false 示意非偏心锁,下面代码须要用 LOCK.lock()加锁,LOCK.unlock()解锁,须要放入 try,finally 代码块中,目标是如果 try 中加锁后代码产生异样锁最终执行 LOCK.unlock(),锁总能被开释。主类中让编号 001 至 005 五个同学同时去打饭,失去 其中一种 执行后果如下图所示,001->004->005->003->002 同学先去排队,但打饭的程序是 001->005->004->003->002,这里 005 同学插队了,插到 004 后面。咱们详细分析执行过程:001 先来抢到锁打饭了并开释了锁,接下来本应该是 004 抢到锁,因为它先排队,但 005 却在 004 之前抢到锁,打饭了,005 比 004 起初,却先打饭,这就是不偏心锁,前面的执行后果按先来后到执行,程序完结。咱们用代码 证实了 ReentrantLock非偏心的锁。紧接着咱们来看下ReentrantLock 另一种作为偏心锁的状况。

ReentrantLock 偏心锁


基于下面的案例,咱们不反复贴代码了,将上述代码中 13 行的 private static final Lock LOCK = new ReentrantLock(false); 参数由 false 改为 true,private static final Lock LOCK = new ReentrantLock(true);无论执行多少次能够得出一个论断:先排队的童鞋能先打饭,不容许插队体现的就是偏心锁。

ReentrantLock 底层原理


ReentrantLock 是基于 AbstractQueuedSynchronizer(形象队列同步器,简称 aqs)实现的,aqs 底层保护了一个带头的双向链表,用来 同步线程 ,链表每个节点用 Node 示意,每个 Node 会记录线程信息,高低节点,节点状态等信息,aqs 管制 Node 的生命周期。如下图所示,aqs 也蕴含条件队列,锁和条件队列(condition)是一对多的关系,也就是说一个锁能够对应多个条件队列,线程间的通信在条件队列里通过 await,single/singleAll 办法管制,synchronized 只有一个条件队列用 wait,notify/notifyAll 来实现,这里不开展说了,《 母鸡下蛋实例:多线程通信生产者和消费者 wait/notify 和 condition/await/signal 条件队列》和《Synchronized 用法原理和锁优化降级过程(面试)》能够看我文章,外面有大量清晰简略案例。条件队列也是以链表模式存在。Lock 是基于 juc 包实现,synchronized 是本地办法基于 c ++ 实现。

总结


明天用生存中的例子转化成代码,具体的介绍了偏心锁和非偏心锁,并简略的介绍了 aqs 实现原理,给您的倡议是认真把代码敲一遍,如果执行了一遍代码应该能看明确,喜爱的请点赞加关注哦。我是 叫练【公众号】,边叫边练。

退出移动版