简介


有个小伙伴最近征询我,前段时间他被面试官问了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实现原理,给您的倡议是认真把代码敲一遍,如果执行了一遍代码应该能看明确,喜爱的请点赞加关注哦。我是叫练【公众号】,边叫边练。