【发布时间】:2009-12-05 12:54:52
【问题描述】:
标题听起来前面有很多问题。这是我的具体情况:
这是一个旅游票销售系统。每条路线的车票数量有限,因此购买给定路线的最后一张车票不应该让两个人访问(标准场景)。但是,有“回程票”选项。所以,我使用唯一的路线 ID(数据库提供)来执行以下操作:
synchronized(bothRoutesUniqueString.intern()) {
synchronized (routeId.intern()) {
if (returnRouteId != null) {
synchronized (returnRouteId.intern()) {
return doPurchase(selectedRoute, selectedReturnRoute);
}
}
return doPurchase(selectedRoute, selectedReturnRoute);
}
}
两个内部synchronized 块是为了使线程仅在两个人同时购买此特定路线的票时才停止在那里,而不是在同时购买两条不同路线的票时.第二次同步当然是因为有人可能同时尝试购买返回路由作为出站路由。
最外面的synchronized 块是为了说明当两个人购买相同组合的票时,颠倒的情况。例如,一个订单伦敦-曼彻斯特,另一个订单曼彻斯特-伦敦。如果没有外部同步块,这种情况可能会导致死锁。
(如果没有可用的票证,doPurchase() 方法要么返回 Ticket 对象,要么抛出异常)
现在,我完全知道这是一个非常尴尬的解决方案,但是,如果它按预期工作,它会给出:
- 10 行代码来处理整个复杂场景(并且使用适当的 cmets,它不会那么难理解)
- 没有不必要的锁定 - 一切都只有在必须阻塞时才会阻塞。
- 数据库不可知论
我也知道这种情况是由悲观或乐观的数据库锁来处理的,而且由于我使用的是 Hibernate,所以这些也不难实现。
我认为可以通过上述代码使用 VM 集群来实现水平扩展。根据Teracotta documentation,它允许将单节点多线程应用程序转换为多节点,并且:
Terracotta 跟踪 String.intern() 调用并保证这些显式实习字符串的引用相等。由于对内部 String 对象的所有引用都指向规范值,因此即使对于分布式应用程序,引用相等检查也将按预期工作。
所以,现在开始讨论问题本身:
- 您是否发现上述代码的任何缺点(除了笨拙之外)?
- 是否有来自
java.util.concurrentAPI 的适用类来帮助解决这种情况? - 为什么数据库锁定比这更可取?
更新:
由于大部分答案都与OutOfMemoryError 相关,所以我为intern() 做了一个基准测试,内存并没有被吃掉。也许正在清除字符串表,但这在我的情况下并不重要,因为我需要对象在竞争条件下相等,并且此时不应清除最近的字符串:
System.out.println(Runtime.getRuntime().freeMemory());
for (int i = 0; i < 10000000; i ++) {
String.valueOf(i).intern();
}
System.out.println(Runtime.getRuntime().freeMemory());
附:环境是 JRE 1.6
【问题讨论】:
标签: java multithreading concurrency synchronization