阿里巴巴|阿里技术三面:哲学家进餐( 二 )


4个哲学家持有叉子?故最多只允许 4 个哲学家去持有叉子 , 可保证至少有 1个哲学家能吃上意大利面(即获得到 2 个叉子) 。
因为最差情况下是:4 个哲学家都各自持有1个叉子 , 此时还剩余 1个叉子可供使用 , 这 4个哲学家中必然有1人能获取到这个剩余的 1 个叉子 , 从而手持 2个叉子 , 可以吃意大利面 。
即:4 个人中 , 1 个人有 2 个叉子 , 3 个人各持 1 个叉子 , 共计 5 个叉子 。
3个哲学家持有叉子?既然最多只允许4个哲学家去持有叉子 , 那么如果只允许 3 个哲学家去持有叉子是否可行呢?
当然可行 , 3个哲学家可以先都各自持有 1 把叉子 , 此时还剩余 2 把叉子;
当这 3 个哲学家刚好都相邻(比如:编号为图中的 0 1 2 ) , 可能会造成只有 1 个哲学家能吃到意面的情况 , 具体而言即 0号哲学家拿到了其左侧的叉子(编号为 1 ) ,1号哲学家也拿到了其左侧的叉子(编号为 2 ) , 2 号哲学家也拿到了其左侧的叉子(编号为 3 ) , 此时只有 0号哲学家能拿到其右侧的叉子(编号为 0 ) , 因此只有 0 号哲学家能吃到意面 。
而其余情况下 , 3个哲学家中都能有 2人吃到意面 。
即:3 个人中 , 2 个人各自持有 2 个叉子 , 1 个人持有 1 个叉子 , 共计 5 个叉子 。
并且仔细想想 , 叉子的数目是固定的(个数为 5 ) , 直觉上来讲 3 个人去抢 5 个叉子比 4 个人去抢 5 个叉子效率高 。
方法一:信号量用Semaphore去实现上述的限制:Semaphore eatLimit = new Semaphore(4);
一共有5个叉子 , 视为5个ReentrantLock , 并将它们全放入1个数组中 。
给叉子编号 0 1 2 3 401234(对应数组下标) 。
代码具体实现:

改进:
位运算就可以表示5个叉子的使用状态 , 只需用1个volatile修饰的int变量即可 + CAS操作即可 , 即AtomicInteger类 。
代码具体实现:

方法二:只允许1个哲学家就餐设置 1 个临界区以实现 1 个哲学家 “同时”拿起左右 2 把叉子的效果 。
即进入临界区之后 , 保证成功获取到左右 2 把叉子并执行相关代码后 , 才退出临界区 。
方法2有一定的概率是“并行” , “只允许1个哲学家就餐”是严格的“串行” 。
代码如下:

改进:
位运算就可以表示5个叉子的使用状态 , 只需用1个volatile修饰的int变量即可 + CAS操作即可 , 即AtomicInteger类 。

方法一和方法二的区别2 者之间有细微的差别:
方法 2 是在成功拿起左右叉子之后就退出临界区 , 而“只让 1 个哲学家就餐”是在拿起左右叉子 + 吃意面 + 放下左右叉子一套流程走完之后才退出临界区 。
前者的情况可大概分为 2 种 , 举具体例子说明(可参照上面给出的图片):

  • 1 号哲学家拿起左右叉子( 1 号叉子 + 2 号叉子)后就退出临界区 , 此时 4 号哲学家成功挤进临界区 , 他也成功拿起了左右叉子( 0 号叉子和 4 号叉子) , 然后就退出临界区 。
  • 1 号哲学家拿起左右叉子( 1 号叉子 + 2 号叉子)后就退出临界区 , 此时 2 号哲学家成功挤进临界区 , 他需要拿起 2 号叉子和 3 号叉子 , 但 2 号叉子有一定的概率还被 1 号哲学家持有( 1 号哲学家意面还没吃完) , 因此 2 号哲学家进入临界区后还需要等待 2 号叉子 。 至于 3 号叉子 , 根本没其他人跟 2 号哲学家争夺 , 因此可以将该种情况视为“ 2 号哲学家只拿起了 1 只叉子 , 在等待另 1 只叉子”的情况 。
总之 , 第1种情况即先后进入临界区的 2 位哲学家的左右叉子不存在竞争情况 , 因此先后进入临界区的 2 位哲学家进入临界区后都不用等待叉子 , 直接就餐 。 此时可视为 2 个哲学家在同时就餐(当然前 1 个哲学家有可能已经吃完了 , 但姑且当作是 2 个人同时就餐) 。

相关经验推荐