这个方法线程安全吗?

时间:2012-03-31 21:23:11

标签: java

这些方法是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;
    }

}

3 个答案:

答案 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作为初始ID
regstrdUserIdsCount_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);
  }
}
}