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中的代码计时块检查在原子整数更新之前是否有可用资源。原子整数更新由该块保护