Java 这个方法是线程安全的吗?

Java 这个方法是线程安全的吗?,java,Java,这些方法是线程安全的吗 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 noOfUserIdsInReserveC

这些方法是线程安全的吗

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;
    }

}
公共最终类IdManager{
私有静态final int NO_用户ID_到_KEEP_在_RESERVE=200;
私有静态最终AtomicInteger regstrdUserIdsCount_Cached=新的AtomicInteger(100);
私有静态int NoofuseridSinReserveCurrent=0;
公共静态int getNewId(){
已同步(IdManager.class){

如果(NoofuseridSinReserveCurrent您未从任何其他位置访问字段
NoofuseridSinReserveCurrent
,则是-访问该字段是线程安全的

请注意,如果仅从同步服务器内部调用
fetchIdsInReserve
getNewId
方法中设置块,则不必使该方法同步

更新:只要问题被编辑,现在它就不是线程安全的。您必须在同步块内的第一个方法中有return语句。它不必是原子整数,在这种情况下,它可以只是一个简单的
int

如果这里有21条线

      synchronized(IdManager.class){
          if (noOfUserIdsInReserveCurrently <= 20)
                  fetchIdsInReserve();    

          noOfUserIdsInReserveCurrently--;
      }
编辑:

下面是类加载的初始状态:

regstrdUserIdsCount_Cached = 100
noOfUserIdsInReserveCurrently = 0
让我们假设对数据库的写回总是成功的。如果没有成功,那么这段代码显然被破坏了,因为在这种情况下它仍然分配一个ID

第一个线程通过,并调用fetch,因为没有保留ID

regstrdUserIdsCount_Cached = 100
noOfUserIdsInReserveCurrently = 0
假设DB返回100作为初始ID,则在方法完成且没有争用之后

regstrdUserIdsCount_Cached = 101
noOfUserIdsInReserveCurrently = 199
regstrdUserIdsCount_Cached = 279
noOfUserIdsInReserveCurrently = 21
现在,让我们假设还有178个线程在没有争用的情况下通过

regstrdUserIdsCount_Cached = 101
noOfUserIdsInReserveCurrently = 199
regstrdUserIdsCount_Cached = 279
noOfUserIdsInReserveCurrently = 21
如果该线程在退出
synchronized
块后但在递减原子
int
之前被另一个线程抢占,则抢占线程将触发提取


由于
NoofuseridSinReserveCurrent
未被抢占的线程递减

(noOfUserIdsInReserveCurrently + regstrdUserIdsCount_Cached.get() != reservedInDBTill) 
这将是错误的

假设异常表示一种故障模式,则在一次交织期间出现故障,而在其他交织期间不会抛出该故障。因此,代码不是线程安全的


解决方案是一致地访问关键部分中缓存的
regstrdUserIdsCount\u
。在这种情况下,它不需要是原子int,而可以是非
最终的
int。

下面是一个示例测试,以显示它是线程安全的。用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);
  }
}
}
publicstaticvoidmain(字符串…arg)引发异常{
最终整数循环=1042;
for(int l=0;l!=循环;l++){
reset();//将此方法添加到类中以重置计数器
最终int maxThreads=113;
最终ArrayList检查器=新ArrayList();
最终倒计时闩锁cl1=新倒计时闩锁(maxThreads);
最终倒计时闩锁cl2=新倒计时闩锁(maxThreads);
对于(int x=0;x!=maxThreads;x++){
Thread Thread=新线程(new Runnable(){
公开募捐{
cl1.倒计时();
试一试{
cl1.await();//将所有线程堆叠在此处
}捕捉(中断异常e){
e、 printStackTrace();
}
int id=getNewId();
已同步(检查程序){
添加(“+id”);
}
cl2.倒计时();
}
});
thread.start();
}
cl2.wait();
对于(int x=0;x!=maxThreads;x++){
字符串键=”+(101+x);//第一个ID值
如果(!checker.contains(key)){
System.out.println(“检查程序1失败-缺少id=“+键”);
}否则{
删除(键);
if(检查程序包含(键)){
System.out.println(“检查器2失败-额外id=“+键”);
}
}
}
对于(int x=0;x!=checker.size();x++){
字符串键=”+(101+x);
System.out.println(“检查器3失败-额外id=“+键”);
}
}
}

这里的
//fetch from DB…
代码容易被其他线程阻止吗?特别是,其他线程已经开始做一些DB工作,然后想要获得一个新的ID?如果是这样,你就有可能打开自己的死锁。因为对该方法的调用只是通过
getNewId()中的同步块进行的
我相信这个方法一次只能被一个线程调用,对吗?我假设这不是你拥有的唯一一个接触数据库的代码……其他代码也可能会获取锁等等。当然,我会同时调用其他方法来执行其他DB查询/写入,但不会更新数据库数据库中与此id计数器相关的数据。请考虑:
synchronized(DB){/*执行查询;*/int id=IdManager.getNewId();/*插入内容;*/}
。如果“从数据库获取”代码也是这样工作的,另一个线程在同一时间获得一个ID,后一个线程将被阻止等待DB解锁,第一个线程将被阻止等待IdManager锁。重点是,线程安全比看起来更难,有时这是一个相当全面的问题。如果我们看不到“从DB获取”代码,我们不能确定它是否做了会导致死锁的蠢事。
NoofuseridSinReserveCurrent
也可以在
fetchIdsInReserve()中访问
其中它是递增的,但我相信这也表现为同步的,因为它是从同步块调用的,对吗?对。如果该字段仅从内部同步访问(IdManager.class)块或此类的静态同步方法-它是线程安全的。很抱歉,我不能真正理解您的观点。但是您能提出一个正确的解决方案吗?非常感谢!不管这里有多少个线程。同步块中的代码一次由一个线程执行。@EugeneRetunsky,是的,但是syn中的代码计时块检查在原子整数更新之前是否有可用资源。原子整数更新由该块保护