欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

瓜娃之走马观花 (2) - Make me a Map as fast as you can

程序员文章站 2022-07-14 14:44:06
...
古人云 (无图无真相, 有美女走光图为证):
引用
Pat-a-map, Pat-a-map, maker's man,
Make me a map as fast as you can...


瓜娃之走马观花 (2) - Make me a Map as fast as you can
            
    
    博客分类: Guava Google多线程SVNCacheIDEA

这集要讲的工具叫MapMaker.

顾名思义, MapMaker就是帮你做Map滴, 而且是as fast as it can! 实在是居家旅行, 制作Map的必备工具啊!

可是啊, 这计算机一思考, 你就笑了: "小样儿, 做Map还用你做啊? 当我文盲?" 不就是:
new HashMap<姓名, 奖金>()
嘛. 如果用上回书提到的Maps就是:
Maps.newHashMap();

再不, 用ImmutableMap, 就是
ImmutableMap.of("ajoo", 10000000000000000);


计算机: 这个, 这个, 啊, (耳语: 老大, 别这么不给面子啊. 出来混都不容易), 我们这里讲的不是HashMap, 也不是ImmutableMap, 其实是代表着当代最最高科技的
java.util.concurrent.ConcurrentMap
, 它适合多线程操作, 高并发, 无阻塞, 无死锁, 无冲突, 无测漏, 无...

你: 等等, 你是在说:
new ConcurrentHashMap<姓名, 奖金>()
这么简单到离谱的东西么?

计算机: 这个, 别急, 别急. 其实不完全是. MapMaker提供了更多更强大的功能. 比如可以指定键是否用弱引用, 指定一个键多长时间过期之类的. 但是最最最有用的, 是那个makeComputingMap()函数.

画面一转, ajoo平易近人地微笑着说: 对啦! 这也就是我今天要讲MapMaker的主要原因.

大家想一想, 平时有没有需要做延迟初始化(所谓lazy initialization)和缓存的? 比如说, 我要对每一个需要用到的员工名字通过数据库查询她一年以来的加班多少, 加班是否自觉自愿, 漂亮程度, 身材和三围指标, 领会领导意图能力, 帮领导挡酒总加仑数等等等等各种数据, 最后经过一个NP问题的近似解决算法来得到员工的奖金数额.

这个东西算起来太费资源, 弄不好比奖金本身还多. 那么, 就要延迟计算, 只有员工主动跑来跟我要加薪, 我才去算. 而且要缓存, 如果这个员工对只发50元奖金心存不满, 反复来吵闹, 甚至投诉, 我们不用重新计算.

一般如果你自己写怎么写呢? 假设我那个算奖金的函数写好了, 就叫computeBonus(姓名). 这样么?

class BonusManager {
  private final Map<姓名, 奖金> cache = new HashMap<姓名, 奖金>();

  奖金 getBonus(姓名 name) {
    奖金 bonus = cache.get(name);
    if (bonus == null) {
      bonus = computeBonus(name); // 危险在这里!
    }
    cache.put(name, bonus);
    return bonus;
  }
}


其实主要逻辑是这样的. 问题是这个实现不是多线程安全的.
你可以在getBonus()上面加一个synchronized关键字, 然后它就线程安全了. 但是, 让我们假设我们是个跨国知名连锁大托拉斯, 需要极高极高的并发能力.

聪明如你一定会想到把HashMap改成ConcurrentHashMap.

近了一步了, 但是还不完全. 这是因为两个线程可能同时执行if (bonus == null)然后同时去运行computeBonus(). 结果是, 我们可能对某些名字重复运行computeBonus(). 让我们再假设老板遇到这种你给公司浪费了资源的情况会炒你鱿鱼的.

呵呵, 到这里, 如果你想到了双检查锁机制(所谓的double-checked locking), 那算你狠, 遇到你们这种聪明到煞风景的听众我真是无话可说, 卷帘朝散, 请假还宫!

而如果你没想到, 那么, 恭喜你, 你答对啦! 奖河南双汇火腿肠一根(94年珍藏版).

我想说的, 其实是, 俺也不知道怎么写最好. 所以呀, 最好是不写. 反正MapMaker都给咱写好了不是? 人生苦短, 当及时行乐啊, 同志! 有闲功夫多洗几块尿布好不好?

那么, 不卖关子了 (等不及已经看了javadoc文档的同志们都已经自己写出来了. 俺再摇头晃脑故作玄虚恐怕就要吃臭鸡蛋, 烂西红柿伺候咯). 请看. 当当当当 (贝多芬第九交响曲响起)!

ConcurrentMap<名字, 奖金> cache = new MapMaker()
    // 如果一个计算一年有效 (嗯, 明年奖金减半!), 那么设一个过期时间
    .expiration(365, TimeUnit.DAYS)
    .makeComputingMap(new Function<名字, 奖金>() {
      @Override public 奖金 apply(名字 name) {
        return computeBonus(name);
      }
    });


就是这么简单. 等等, 我先摆个pose先, 样子屌不屌?

这里面唯一需要解释的就是这个Function. 大家都知道, java是一门罗唆的语言艺术, 讲究说学逗唱, 不是, 说错了, 讲究类, 方法, 表达式, 匿名类等等等等, 反正是怎么写累人怎么来. 这里的目的其实就是告诉makeComputingMap()怎样"compute". 这个function是个匿名类, 它的apply()函数封装了怎么计算这么一个概念. (哎呀, 手都打酸了)

然后, makeComputingMap()就接手过去, 它内部怎么搞的咱不关心, 反正我上面想要的功能它全实现了, 这就够了呗.

行了, 洗尿布去了.

未完待续