这些方法是getNewId()
& fetchIdsInReserve()
线程安全吗?
public final class IdManager {
private static final int NO_OF_USERIDS_TO_KEEP_IN_RESERVE = 200;
private static final AtomicInteger regstrdUserIdsCount_Cached = new AtomicInteger(100);
private static int noOfUserIdsInReserveCurrently = 0;
public static int getNewId(){
synchronized(IdManager.class){
if (noOfUserIdsInReserveCurrently <= 20)
fetchIdsInReserve();
noOfUserIdsInReserveCurrently--;
}
return regstrdUserIdsCount_Cached.incrementAndGet();
}
private static synchronized void fetchIdsInReserve(){
int reservedInDBTill = DBCountersReader.readCounterFromDB(....); // read column from DB
if (noOfUserIdsInReserveCurrently + regstrdUserIdsCount_Cached.get() != reservedInDBTill) throw new Exception("Unreserved ids alloted by app before reserving from DB");
if (DBUpdater.incrementCounter(....)) //if write back to DB is successful
noOfUserIdsInReserveCurrently += NO_OF_USERIDS_TO_KEEP_IN_RESERVE;
}
}
答案 0 :(得分:1)
您不能从其他任何地方访问字段noOfUserIdsInReserveCurrently
,然后是 - 访问它是线程安全的。
P.S。如果fetchIdsInReserve
仅从同步内部调用
在getNewId
方法中阻止,然后您不必使方法同步。
更新:只要问题被编辑,现在它就不是线程安全的。您必须在同步块中的第一个方法中使用return语句。它不一定是AtomicInteger,在这种情况下它只是一个简单的int
。
答案 1 :(得分:1)
没有。
如果这里有21个帖子
synchronized(IdManager.class){
if (noOfUserIdsInReserveCurrently <= 20)
fetchIdsInReserve();
noOfUserIdsInReserveCurrently--;
}
并等待另外180个线程通过顶部并通过下面的行,然后当第21个线程到达下面的行时,当来自第一个组的第21个线程调用时,将没有用户ID保留
return regstrdUserIdsCount_Cached.incrementAndGet();
编辑:
这是类加载的初始状态:
regstrdUserIdsCount_Cached = 100
noOfUserIdsInReserveCurrently = 0
让我们假设写回DB总是成功的。如果不是,则此代码明显被破坏,因为在这种情况下它仍会分配ID。
第一个线程通过,并调用fetch,因为没有保留的ID。
regstrdUserIdsCount_Cached = 100
noOfUserIdsInReserveCurrently = 0
假设DB在方法完成且没有争用
后返回100作为初始IDregstrdUserIdsCount_Cached = 101
noOfUserIdsInReserveCurrently = 199
现在,我们假设有178个线程没有争用
regstrdUserIdsCount_Cached = 279
noOfUserIdsInReserveCurrently = 21
如果该线程在退出synchronized
块之后但在递减原子int
之前被另一个线程抢占,则抢占线程将触发获取。
由于noOfUserIdsInReserveCurrently
没有被先占的线程减少,
(noOfUserIdsInReserveCurrently + regstrdUserIdsCount_Cached.get() != reservedInDBTill)
将是假的。
假设异常表示失败模式,我们在一次交错期间出现故障,在其他交错期间没有抛出。因此,代码不是线程安全的。
解决方案是在关键部分内持续访问regstrdUserIdsCount_Cached
。在这种情况下,它不必是一个原子int,但可以只是一个非final
int。
答案 2 :(得分:0)
这是一个示例测试,表明它是线程安全的。用int替换AtomicInteger并删除同步,测试应该失败。
public static void main(String... arg) throws Exception {
final int loops = 1042;
for (int l = 0; l != loops; l++) {
reset(); // add this method to your class to reset the counters
final int maxThreads = 113;
final ArrayList<String> checker = new ArrayList<String>();
final CountDownLatch cl1 = new CountDownLatch(maxThreads);
final CountDownLatch cl2 = new CountDownLatch(maxThreads);
for (int x = 0; x != maxThreads; x++) {
Thread thread = new Thread(new Runnable() {
public void run() {
cl1.countDown();
try {
cl1.await(); // stack all threads here
} catch (InterruptedException e) {
e.printStackTrace();
}
int id = getNewId();
synchronized(checker) {
checker.add("" + id);
}
cl2.countDown();
}
});
thread.start();
}
cl2.await();
for (int x = 0; x != maxThreads; x++) {
String key = "" + (101 + x); // 1st ID value
if (!checker.contains(key)) {
System.out.println("Checker 1 FAIL - missing id=" + key);
} else {
checker.remove(key);
if (checker.contains(key)) {
System.out.println("Checker 2 FAIL - extra id=" + key);
}
}
}
for (int x = 0; x != checker.size(); x++) {
String key = "" + (101 + x);
System.out.println("Checker 3 FAIL - extra id=" + key);
}
}
}