从银行窗口业务办理来看锁的实现

原创 2016年06月02日 00:13:55

今天咱们YY一个sitcom来讨论下几种锁的实现,欢迎列位看官批评指正^_^

你很着急,但是取号机叫号机都故障了

你打算办张招行信用卡,然后你决定利用午休的时间跑到公司楼下,文三路上的高新支行去(硬广强势植入^_^)。到了那边你发现,什么情况,取号机叫号机全都故障了,而且只剩一个窗口在服务。

怎么办呢?然后你发现大家用的最原始的方式,就靠抢,谁先跑到窗口前面谁就先办理业务(什么,你问他们为什么不排队?你见过挤公交排队的?)。因为你也很着急啊,待会还要赶回华星时代23楼上班呢^_^,咋办,抢呗。

代码描述

我们可以用自旋锁来描述上述的抢锁方式。

public class SpinLock {

    private AtomicReference<Thread> owner = new AtomicReference<Thread>();

    public void lock() {
        Thread current = Thread.currentThread();

        //// reentrant lock
        if(owner.compareAndSet(current, current)) {
            return;
        }

        while (!owner.compareAndSet(null, current)) {
            //// 蜂拥而上没抢到,继续抢!
        }
    }

    public void unlock() {
        Thread current = Thread.currentThread();
        owner.compareAndSet(current, null);
    }

}

主要问题

  • 无法保证公平性(野蛮人的社会有木有)
  • 系统开销大(每个人都很累啊有木有)

取号机修复了,但是叫号机还有点问题

当你还在努力抢窗口的时候,取号机修复了,但是叫号机还有点问题,现在只能按一下开关展示一下当前正在服务的号码(读取成本略高)。不过至少公平性的问题现在能解决了

于是乎,每个人开始去取号(取号还是抢,木有办法),然后每个人开始不耐烦的一直去按叫号机(大家都不熟,各管各的咯),看上去所有人都很着急。当然了你也按,也一直等着自己的号码。

代码描述

上述其实就是Ticket Lock的实现方式,

public class TicketLock {

    private AtomicInteger ticketNum = new AtomicInteger();
    private AtomicInteger serviceNum = new AtomicInteger();
    private static final ThreadLocal<Integer> LOCAL = new ThreadLocal<Integer>();

    public void lock() {
        //// reentrant lock
        if(LOCAL.get() != null) return;

        int iTicket = ticketNum.getAndIncrement();
        LOCAL.set(iTicket);

        while (iTicket != serviceNum.get()) {
            //// 狂按叫号机开关,直到看到是自己的号码
        }
    }

    public void unlock() {
        Integer iTicket = LOCAL.get();

        //// unlock before lock
        if(iTicket == null) return;

        serviceNum.compareAndSet(iTicket, iTicket + 1);
    }

}

主要问题

  • 系统开销还是大(叫号机表示自己要被按挂了。。)

阿西吧,叫号机彻底挂了

果了个然,不一会,叫号机就被按挂了。。于是乎现在只剩取号机供取号排队了。

怎么办呢?这时候大厅经理想了个办法。取号的时候,大厅经理会把排在你前一位的同学的手机号码给你。然后告诉你,你就打电话给他去询问他业务办理的状况,他如果办好了就换你去。

于是乎,你就开始一直给那位同学打电话。。没办法,你着急啊。。当然了,那位同学肯定是被你烦得不行。

代码描述

这其实就是CLH锁的实现方式,

public class CLHLock {

    private static class CLHNode {
        volatile boolean blocked = true;
    }

    @SuppressWarnings("unused")
    private volatile CLHNode tail;  // 最后一个申请锁的节点
    private static final AtomicReferenceFieldUpdater TAIL = AtomicReferenceFieldUpdater
            .newUpdater(CLHLock.class, CLHNode.class, "tail");

    private static final ThreadLocal<CLHNode> LOCAL = new ThreadLocal<CLHNode>();

    public void lock() {
        //// reentrant lock
        if(LOCAL.get() != null) return;

        CLHNode current = new CLHNode();
        LOCAL.set(current);

        CLHNode predecessor = (CLHNode) TAIL.getAndSet(this, current);

        if (predecessor != null) {

            while (predecessor.blocked) {
                //// 疯狂询问你的前一位同学是否已经结束
            }

        }
    }

    public void unlock() {
        CLHNode current = LOCAL.get();

        //// unlock before lock
        if(current == null) return;

        TAIL.compareAndSet(this, current, null);

        //// 业务办理结束
        current.blocked = false;

//        LOCAL.remove();
    }
}

主要问题

  • 依旧还是系统开销的问题,特别是在NUMA架构下(电话费很贵的啊喂)

纳尼,你被拉黑了

终于,你打了若干次电话后,人家把你拉黑了。然后你就跟大厅经理抱怨,于是乎他就想了另一个方法。在取号的时候,排在你前一位的同学将会收到你的手机号码,等他业务办理完之后会主动通知你。

于是乎你现在要做的就是一直盯着你的手机了。。

代码描述

这其实就是MCS锁的实现方式了,

public class MCSLock {

    private static class MCSNode {
        volatile MCSNode next;
        volatile boolean blocked = true;
    }

    @SuppressWarnings("unused")
    private volatile MCSNode tail;
    private static final AtomicReferenceFieldUpdater TAIL = AtomicReferenceFieldUpdater
            .newUpdater(MCSLock.class, MCSNode.class, "tail");

    private static final ThreadLocal<MCSNode> LOCAL = new ThreadLocal<MCSNode>();

    public void lock() {
        //// reentrant lock
        if(LOCAL.get() != null) return;

        MCSNode current = new MCSNode();
        LOCAL.set(current);

        //// step1
        MCSNode predecessor = (MCSNode) TAIL.getAndSet(this, current);

        if (predecessor != null) {

            //// step2
            predecessor.next = current;

            while (current.blocked) {
                //// 一直盯着自己的手机,等待通知
            }

        } else {
            current.blocked = false;
        }
    }

    public void unlock() {
        MCSNode current = LOCAL.get();

        //// unlock before lock
        if(current == null) return;

        if (current.next == null) {
            //// 你是最后一位了
            if (TAIL.compareAndSet(this, current, null)) {
                LOCAL.remove();
                return;
            }
            //// step1+step2并非原子操作
            else {
                while (current.next == null) {}
            }
        }

        //// 主动通知下一位同学
        current.next.blocked = false;

        current.next = null;
        LOCAL.remove();
    }

}

现在你不着急了

等着等着你发现,我去,已经两点了,你观察了一下,按照目前窗口的办理速度,至少还得半小时(竞争激烈,锁持有时间长),于是你给老板发了消息说明情况,半小时后回,老板表示OK。

所以你现在也没有那么着急了,既来之则安之,并且中午不睡下午崩溃嘛,于是你决定先小憩一会(进入休眠状态),也不盯着手机了,就等着待会被叫醒了。

代码描述

将MCS锁实现稍微修改一下,就可以实现上述的阻塞锁了。

public class MCSBlockingLock {

    private static class MCSBlockingNode {
        volatile MCSBlockingNode next;
        volatile boolean blocked = true;
        volatile Thread thread = Thread.currentThread(); // 该节点所属的线程
    }

    @SuppressWarnings("unused")
    private volatile MCSBlockingNode tail;
    private static final AtomicReferenceFieldUpdater TAIL = AtomicReferenceFieldUpdater
            .newUpdater(MCSBlockingLock.class, MCSBlockingNode.class, "tail");

    private static final ThreadLocal<MCSBlockingNode> LOCAL = new ThreadLocal<MCSBlockingNode>();

    public void lock() {
        //// reentrant lock
        if(LOCAL.get() != null) return;

        MCSBlockingNode current = new MCSBlockingNode();
        LOCAL.set(current);

        //// step1
        MCSBlockingNode predecessor = (MCSBlockingNode) TAIL.getAndSet(this, current);

        if (predecessor != null) {

            //// step2
            predecessor.next = current;

            while (current.blocked) {
                //// 睡一觉,等待通知
                LockSupport.park(this);
            }

        } else {
            current.blocked = false;
        }
    }

    public void unlock() {
        MCSBlockingNode current = LOCAL.get();

        //// unlock before lock
        if(current == null) return;

        if (current.next == null) {
            if (TAIL.compareAndSet(this, current, null)) {
                LOCAL.remove();
                return;
            } else {
                while (current.next == null) {}
            }
        }

        //// 主动通知下一位同学
        current.next.blocked = false;
        LockSupport.unpark(current.next.thread);

        current.next = null;
        LOCAL.remove();
    }
}

参考资料

【练习】银行排号程序

随着银行业务的扩展, 到银行办理业务的人越来越多,经常可以见到银行营业大厅中排着长队。为了改善服务,很多银行都设置了排号系统,顾客去办理业务时,通过排号机生成一个好吗,然后可坐在在休息区等候排号系统按...
  • cbwem
  • cbwem
  • 2017年11月19日 21:41
  • 90

银行窗口排队叫号系统实现

这是一个模拟银行窗口排队叫号调度系统,参考了网上两篇文章,一篇java的和一篇linux c++的,然后我在windows下实现了它,开发工具是vs2008.在文章最后我会给出直接可编译可执行代码。J...
  • luomoshusheng
  • luomoshusheng
  • 2015年07月23日 20:16
  • 6143

队列应用银行排队问题模拟:计算客户的平均停留时间和等待时间以及每个客户的时间信息,两种方法实现

一、数据类型 首先需要两个数据结构:一个是有序事件链表,一个是队列。 1、事件链表 存储客户事件,包括到达事件和离开事件,其中到达事件的事件类型为0,1号窗口的离开事件类型为1,二号窗口的离开事...
  • meiyubaihe
  • meiyubaihe
  • 2014年06月03日 15:47
  • 4364

银行排队模拟

【问题描述】 一个系统模仿另一个系统行为的技术称为模拟,如飞行模拟器。模拟可以用来进行方案论证、人员培训和改进服务。计算机技术常用于模拟系统中。 生产者-消费者(Server-Custom)是常见...
  • Jason_Ranger
  • Jason_Ranger
  • 2016年09月20日 13:24
  • 1581

银行线程---某银行有3业务受理窗口,每天办理业务100人次,输出业务办理过程。

1.假设有一个银行有三个业务受理窗口A.B.C 。 2.该银行每天只办理100人次业务(等同100人来办理业务每人每天1次)。 3.按业务受理窗口与业务办理人规则设计程序,模拟100人办理业务过程...
  • lhtisgood
  • lhtisgood
  • 2013年06月06日 21:33
  • 818

离散事件模拟--银行排队时间模拟

在数据结构中有个讲述如何模拟银行排队,最终算出每个人平均的逗留时间。 这是需要数据结构的知识。将银行的每个窗口看成是一个队列,那么对于每次来一个人,都需要从最短的队列进行排队。(其实更优秀的做法是从最...
  • yusiguyuan
  • yusiguyuan
  • 2015年04月30日 15:15
  • 1406

问题 A : 银行业务队列

问题 A : 银行业务队列 时间限制:1 秒 内存限制:32 兆 特殊判题: 否 提交:93 解决: 43 题目描述 一天,小明来银行办业务。这个银行有A、B两个业务窗口,且处...
  • artemisrj
  • artemisrj
  • 2014年03月15日 20:22
  • 1886

银行业务队列 oj150

银行业务队列 发布时间: 2017年7月27日 19:08   最后更新: 2017年7月27日 19:08   时间限制: 1000ms   内存限制: 128M 描述 一天,小明...
  • ZhangXiaoyu_sy
  • ZhangXiaoyu_sy
  • 2017年08月01日 00:00
  • 228

Java -- 队列(模拟银行的排队办理业务)

Java -- 队列(模拟银行的排队办理业务) 模拟银行的排队办理业务,特点是先进后出 首先生成一个interface Customer,其中包含一个deposit存款方法 在main中,对队列进行添...
  • qq_38105361
  • qq_38105361
  • 2017年05月18日 16:19
  • 696

5-18 银行业务队列简单模拟 (25分)

5-18 银行业务队列简单模拟   (25分) 设某银行有A、B两个业务窗口,且处理业务的速度不一样,其中A窗口处理速度是B窗口的2倍 —— 即当A窗口每处理完2个顾客时,B窗口处理完1...
  • qq_32511479
  • qq_32511479
  • 2016年12月05日 17:30
  • 1148
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:从银行窗口业务办理来看锁的实现
举报原因:
原因补充:

(最多只允许输入30个字)