《公平与非公平锁的 “抢座大作战”:代码揭秘直观差异》

125 阅读5分钟

公众号:装睡鹿先生

嘿,各位编程江湖里的 “大侠” 们!今天咱要围观一场超级热闹、充满悬念的 “抢座大作战”,主角嘛,就是 Java 并发编程里的公平锁和非公平锁,它们就像两个风格迥异的 “场馆管理员”,管理着线程大侠们争夺宝贵的 “资源座位”,咱还会搬出代码这 “神奇放大镜”,把它们的行事风格差异看得真真切切,快跟着我瞧热闹去!

一、“公平锁场馆”:秩序井然排排坐

想象咱有个超华丽的 “编程大剧院”,里面正举办一场绝世音乐会,那为数不多的 “贵宾座”(代表珍贵的共享资源,比如数据库连接、共享变量的操作权啥的)可是人人眼馋。这 “公平锁场馆” 的管理员呐,是个铁面无私、严守规矩的 “老古板”,信奉 “先来后到” 的江湖道义。

且看代码搭建的这个 “公平锁舞台”(以 ReentrantLock 的公平锁模式为例):

import java.util.concurrent.locks.ReentrantLock;

public class FairLockShowcase {
    private static final ReentrantLock fairLock = new ReentrantLock(true);  // 注意这里,构造函数传 true,启用公平锁模式

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                fairLock.lock();
                try {
                    System.out.println(Thread.currentThread().getName() + " 喜提贵宾座,美滋滋欣赏音乐会啦!");
                    Thread.sleep(1000);  // 模拟大侠坐在座位上享受音乐会的时长,也就是占用资源的时间
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    fairLock.unlock();
                    System.out.println(Thread.currentThread().getName() + " 看完啦,让出贵宾座,礼貌退场。");
                }
            }, "线程" + i).start();
        }
    }
}

在这个 “大剧院” 里,五个 “线程大侠”(五个线程)风风火火赶来抢座。刚到门口,就碰上 “公平锁管理员” 板着脸站那儿,手里拿着个 “排队号码牌”。第一个到的大侠先领了 “1 号”,第二个拿 “2 号”,依次类推。然后管理员按照号码顺序,一个一个喊 “几号几号,进来坐贵宾座啦”。于是,大侠们规规矩矩,谁先来谁先入座,绝不插队、哄抢,坐在那儿安心欣赏音乐会(顺利获取并使用共享资源),用完了还自觉起身让座,后面的大侠再按顺序补上,整个场馆那叫一个秩序井然,就像一列整齐行驶的小火车,有条不紊地运行着。

二、“非公平锁场馆”:“机灵鬼” 们的混战

再瞅瞅隔壁的 “非公平锁场馆”,那场面可就截然不同咯!这儿的管理员是个有点 “鬼灵精”、信奉 “机会主义” 的家伙,虽说也大致知道要讲点秩序,但时不时就玩点 “小把戏”。

同样用代码搭出这个 “是非之地”(以 ReentrantLock 的非公平锁模式为例):

import java.util.concurrent.locks.ReentrantLock;

public class UnfairLockShowcase {
    private static final ReentandoLock unfairLock = new ReentrantLock(false);  // 构造函数传 false,启用非公平锁模式,其实不传默认就是非公平哦

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                unfairLock.lock();
                try {
                    System.out.println(Thread.currentThread().getName() + " 眼疾手快,抢到贵宾座咯,开嗨!");
                    Thread.sleep(1000);  // 模拟占用资源时长
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    unfairLock.unlock();
                    System.out.println(Thread.currentThread().getName() + " 玩够啦,起身让座咯。");
                }
            }, "线程" + i).start();
        }
    }
}

这回五个 “线程大侠” 一来,还没等反应过来呢,那 “非公平锁管理员” 也不按套路出牌,有时候正喊着 “排队排队”,突然瞅见个 “机灵鬼” 大侠身手特别敏捷,“嗖” 地一下就窜到贵宾座跟前,管理员心一软,嘿,还就真让他坐下了(线程直接抢到锁,获取资源),把后面老老实实排队的大侠气得直跺脚。不过呢,要是这 “机灵鬼” 大侠用完座起身了,管理员还是会尽量把座位优先给排着队的人,但偶尔还是会被新冒出来的 “快手大侠” 截胡,导致场馆里时不时一片 “混乱”,有插队的、有抱怨的,但神奇的是,总体上倒也能把音乐会这场 “资源利用大戏” 给演完,只是过程那是充满波折、跌宕起伏,像个热闹非凡却有点乱糟糟的集市。

三、“差异总结小课堂”

从这两场 “抢座大作战” 就能清晰看出公平锁和非公平锁的区别啦!公平锁就像个一板一眼的 “老学究”,确保每个线程大侠都按先来后到顺序获取资源,排队排得心服口服,在那些对资源获取顺序要求严苛、讲究绝对公平的场景下(比如银行柜台办理业务,得严格按号来),那是不二之选。

而非公平锁呢,像个有点狡黠的 “机灵鬼”,虽说大部分时候也知道排队的理儿,但就是忍不住给那些手脚麻利的大侠开点 “小灶”,让他们有机可乘先拿到资源。好处是在高并发、大家都争分夺秒抢资源的情况下,能减少线程上下文切换、排队等待的开销,提高整体运行效率,不过偶尔也会让老实排队的大侠委屈一下,要是你的场景对资源获取顺序没那么 “斤斤计较”,只追求快速周转、高效利用,非公平锁就能大显身手咯!

所以呐,各位大侠,以后在编程江湖里碰上要管理共享资源、协调多线程 “抢地盘” 的事儿,可得根据实际战况,明智地选择是请 “公平锁老古板” 还是 “非公平锁机灵鬼” 来当管理员,这一招一式之间,可藏着让代码高效稳定运行的大奥秘呢!