GWT模拟的AbstractHashMap中出现意外的java.util.ConcurrentModificationException
在gwt模拟的AbstractHashMap类中调用iter.next()时,我们在使用gwt 2.8.0时遇到了java.util.ConcurrentModificationException(请参见下面的堆栈跟踪和回调计时器类)。涉及我们代码的跟踪的最低点在第118行,在方法GWT模拟的AbstractHashMap中出现意外的java.util.ConcurrentModificationException,gwt,Gwt,在gwt模拟的AbstractHashMap类中调用iter.next()时,我们在使用gwt 2.8.0时遇到了java.util.ConcurrentModificationException(请参见下面的堆栈跟踪和回调计时器类)。涉及我们代码的跟踪的最低点在第118行,在方法private void tick()中,是对iter.next()的调用 深入跟踪,我看到AbstractHashMap: @Override public Entry<K, V> next() {
private void tick()
中,是对iter.next()的调用
深入跟踪,我看到AbstractHashMap:
@Override
public Entry<K, V> next() {
checkStructuralChange(AbstractHashMap.this, this);
checkElement(hasNext());
last = current;
Entry<K, V> rv = current.next();
hasNext = computeHasNext();
return rv;
}
CallbackTimer.java的源代码如下:
package com.XXXXX.common.gwt.timer;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.google.common.base.Optional;
import com.google.gwt.user.client.Timer;
/**
* A {@link Timer} wrapper which allows for the registration of callbacks to be invoked after a given number of ticks.
* The timer will only run if at least one {@link TickCallback} is currently registered and will stop running when all
* callbacks have been unregistered.
*
* The intent of this class is to reduce overhead by allowing all callbacks in a GWT application to use the same
* Javascript timer.
*/
public class CallbackTimer
{
private static final Logger LOGGER = Logger.getLogger(CallbackTimer.class.getName());
private static final int MILLIS_IN_SEC = 1000;
private Timer timer;
private Map<Object, TickCallback> callbackRegistry = new HashMap<>();
public CallbackTimer()
{
timer = new Timer()
{
@Override
public void run()
{
try
{
tick();
}
catch(ConcurrentModificationException concurrentModificationException)
{
LOGGER.log(Level.WARNING, "Concurrent Modification Exception in " +
"CallbackTimer.tick()", concurrentModificationException);
}
}
};
}
public void registerCallback(Object key, TickCallback callback)
{
if (callbackRegistry.containsKey(key))
{
LOGGER.fine("Key " + key.toString() + " is being overwritten with a new callback.");
}
callbackRegistry.put(key, callback);
callback.markStartTime();
LOGGER.finer("Key " + key.toString() + " registered.");
if (!timer.isRunning())
{
startTimer();
}
}
public void unregisterCallback(Object key)
{
if (callbackRegistry.containsKey(key))
{
callbackRegistry.remove(key);
LOGGER.finer("Key " + key.toString() + " unregistered.");
if (callbackRegistry.isEmpty())
{
stopTimer();
}
}
else
{
LOGGER.info("Attempted to unregister key " + key.toString() + ", but this key has not been registered.");
}
}
private void unregisterCallback(Iterator<Object> iter, Object key)
{
iter.remove();
LOGGER.finer("Key " + key.toString() + " unregistered.");
if (callbackRegistry.isEmpty())
{
stopTimer();
}
}
public boolean keyIsRegistered(Object key)
{
return callbackRegistry.containsKey(key);
}
public TickCallback getCallback(Object key)
{
if (keyIsRegistered(key))
{
return callbackRegistry.get(key);
}
else
{
LOGGER.fine("Key " + key.toString() + " is not registered; returning null.");
return null;
}
}
private void tick()
{
long fireTimeMillis = System.currentTimeMillis();
Iterator<Object> iter = callbackRegistry.keySet().iterator();
while (iter.hasNext())
{
Object key = iter.next();//Lowest point in stack for our code
TickCallback callback = callbackRegistry.get(key);
if (callback.isFireTime(fireTimeMillis))
{
if (Level.FINEST.equals(LOGGER.getLevel()))
{
LOGGER.finest("Firing callback for key " + key.toString());
}
callback.onTick();
callback.markLastFireTime();
}
if (callback.shouldTerminate())
{
LOGGER.finer("Callback for key " + key.toString() +
" has reached its specified run-for-seconds and will now be unregistered.");
unregisterCallback(iter, key);
}
}
}
private void startTimer()
{
timer.scheduleRepeating(MILLIS_IN_SEC);
LOGGER.finer(this + " started.");
}
private void stopTimer()
{
timer.cancel();
LOGGER.finer(this + " stopped.");
}
/**
* A task to run on a given interval, with the option to specify a maximum number of seconds to run.
*/
public static abstract class TickCallback
{
private long intervalMillis;
private long startedAtMillis;
private long millisRunningAtLastFire;
private Optional<Long> runForMillis;
/**
* @param intervalSeconds
* The number of seconds which must elapse between each invocation of {@link #onTick()}.
* @param runForSeconds
* An optional maximum number of seconds to run for, after which the TickCallback will be eligible
* to be automatically unregistered. Pass {@link Optional#absent()} to specify that the TickCallback
* must be manually unregistered. Make this value the same as {@param intervalSeconds} to run the
* callback only once.
*/
public TickCallback(int intervalSeconds, Optional<Integer> runForSeconds)
{
this.intervalMillis = intervalSeconds * MILLIS_IN_SEC;
this.runForMillis = runForSeconds.isPresent() ?
Optional.of((long)runForSeconds.get() * MILLIS_IN_SEC) : Optional.<Long>absent();
}
private void markStartTime()
{
millisRunningAtLastFire = 0;
startedAtMillis = System.currentTimeMillis();
}
private void markLastFireTime()
{
millisRunningAtLastFire += intervalMillis;
}
private boolean isFireTime(long nowMillis)
{
return nowMillis - (startedAtMillis + millisRunningAtLastFire) >= intervalMillis;
}
private boolean shouldTerminate()
{
return runForMillis.isPresent() && System.currentTimeMillis() - startedAtMillis >= runForMillis.get();
}
/**
* A callback to be run every time intervalSeconds seconds have past since this callback was registered.
*/
public abstract void onTick();
}
}
package com.XXXXX.common.gwt.timer;
导入java.util.ConcurrentModificationException;
导入java.util.HashMap;
导入java.util.Iterator;
导入java.util.Map;
导入java.util.logging.Level;
导入java.util.logging.Logger;
导入com.google.common.base.Optional;
导入com.google.gwt.user.client.Timer;
/**
*一种{@link Timer}包装器,允许在给定的节拍数之后调用回调的注册。
*计时器将仅在当前至少注册了一个{@link TickCallback}时运行,并且在所有
*回调已取消注册。
*
*此类的目的是通过允许GWT应用程序中的所有回调使用相同的方法来减少开销
*Javascript定时器。
*/
公共类回调计时器
{
私有静态最终记录器Logger=Logger.getLogger(CallbackTimer.class.getName());
专用静态最终整数毫微秒=1000;
私人定时器;
private-Map callbackRegistry=new HashMap();
公共回调计时器()
{
计时器=新计时器()
{
@凌驾
公开募捐
{
尝试
{
勾选();
}
捕获(ConcurrentModificationException ConcurrentModificationException)
{
LOGGER.log(Level.WARNING,“中的并发修改异常”+
“CallbackTimer.tick()”,concurrentModificationException);
}
}
};
}
公共无效注册表回调(对象键,回拨)
{
if(callbackRegistry.containsKey(键))
{
LOGGER.fine(“Key”+Key.toString()+”正在被新回调覆盖。”);
}
callbackRegistry.put(键,callback);
callback.markStartTime();
LOGGER.finer(“Key”+Key.toString()+“registed”);
如果(!timer.isRunning())
{
startTimer();
}
}
public void unregisterCallback(对象键)
{
if(callbackRegistry.containsKey(键))
{
callbackRegistry.remove(键);
LOGGER.finer(“Key”+Key.toString()+“unregistered”);
if(callbackRegistry.isEmpty())
{
停止计时器();
}
}
其他的
{
LOGGER.info(“试图注销键“+key.toString()+”,但此键尚未注册。”);
}
}
私有void unregisterCallback(迭代器iter,对象键)
{
iter.remove();
LOGGER.finer(“Key”+Key.toString()+“unregistered”);
if(callbackRegistry.isEmpty())
{
停止计时器();
}
}
公共布尔键已注册(对象键)
{
返回callbackRegistry.containsKey(键);
}
公共TickCallback getCallback(对象键)
{
如果(键已注册(键))
{
返回callbackRegistry.get(key);
}
其他的
{
LOGGER.fine(“Key”+Key.toString()+”未注册;返回null.);
返回null;
}
}
私人空白勾号()
{
long fireTimeMillis=System.currentTimeMillis();
迭代器iter=callbackRegistry.keySet().Iterator();
while(iter.hasNext())
{
Object key=iter.next();//代码堆栈中的最低点
TickCallback=callbackRegistry.get(key);
if(callback.isFireTime(fireTimeMillis))
{
if(Level.FINEST.equals(LOGGER.getLevel()))
{
LOGGER.finest(“为键触发回调”+key.toString());
}
callback.onTick();
callback.markLastFireTime();
}
if(callback.shouldTerminate())
{
LOGGER.finer(“键的回调”+key.toString()+
“已达到指定的运行时间达秒,现在将取消注册。”);
取消注册回调(iter,密钥);
}
}
}
私有void startTimer()
{
计时器计划重复(毫秒);
LOGGER.finer(此+“已启动”);
}
私有void stopTimer()
{
timer.cancel();
LOGGER.finer(此+“已停止”);
}
/**
*在给定时间间隔内运行的任务,可选择指定运行的最大秒数。
*/
公共静态抽象类回调
{
私人长间隔;
私人长启动数毫秒;
私人长枪射击;
专用可选runForMillis;
/**
*@param intervalSeconds
*每次调用{@link#onTick()}之间必须经过的秒数。
*@param runForSeconds
*可选的最大运行秒数,超过该秒后,将有资格执行TickCallback
*要自动取消注册,请传递{@link Optional#缺席()}以指定
*必须手动注销。请使此值与{@param intervalSeconds}相同,以运行
*只回一次电话。
*/
公共回调(int intervalSeconds,可选runForSeconds)
{
this.intervalMillis=intervalSeconds*MILLIS_英寸秒;
this.runForMillis=runForSeconds
java.util.ConcurrentModificationException
at Unknown.Throwable_1_g$(Throwable.java:61)
at Unknown.Exception_1_g$(Exception.java:25)
at Unknown.RuntimeException_1_g$(RuntimeException.java:25)
at Unknown.ConcurrentModificationException_1_g$(ConcurrentModificationException.java:25)
at Unknown.checkStructuralChange_0_g$(ConcurrentModificationDetector.java:54)
at Unknown.next_79_g$(AbstractHashMap.java:106)
at Unknown.next_78_g$(AbstractHashMap.java:105)
at Unknown.next_81_g$(AbstractMap.java:217)
at Unknown.tick_0_g$(CallbackTimer.java:118)
at Unknown.run_47_g$(CallbackTimer.java:41)
at Unknown.fire_0_g$(Timer.java:135)
at Unknown.anonymous(Timer.java:139)
at Unknown.apply_65_g$(Impl.java:239)
at Unknown.entry0_0_g$(Impl.java:291)
at Unknown.anonymous(Impl.java:77)
package com.XXXXX.common.gwt.timer;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.google.common.base.Optional;
import com.google.gwt.user.client.Timer;
/**
* A {@link Timer} wrapper which allows for the registration of callbacks to be invoked after a given number of ticks.
* The timer will only run if at least one {@link TickCallback} is currently registered and will stop running when all
* callbacks have been unregistered.
*
* The intent of this class is to reduce overhead by allowing all callbacks in a GWT application to use the same
* Javascript timer.
*/
public class CallbackTimer
{
private static final Logger LOGGER = Logger.getLogger(CallbackTimer.class.getName());
private static final int MILLIS_IN_SEC = 1000;
private Timer timer;
private Map<Object, TickCallback> callbackRegistry = new HashMap<>();
public CallbackTimer()
{
timer = new Timer()
{
@Override
public void run()
{
try
{
tick();
}
catch(ConcurrentModificationException concurrentModificationException)
{
LOGGER.log(Level.WARNING, "Concurrent Modification Exception in " +
"CallbackTimer.tick()", concurrentModificationException);
}
}
};
}
public void registerCallback(Object key, TickCallback callback)
{
if (callbackRegistry.containsKey(key))
{
LOGGER.fine("Key " + key.toString() + " is being overwritten with a new callback.");
}
callbackRegistry.put(key, callback);
callback.markStartTime();
LOGGER.finer("Key " + key.toString() + " registered.");
if (!timer.isRunning())
{
startTimer();
}
}
public void unregisterCallback(Object key)
{
if (callbackRegistry.containsKey(key))
{
callbackRegistry.remove(key);
LOGGER.finer("Key " + key.toString() + " unregistered.");
if (callbackRegistry.isEmpty())
{
stopTimer();
}
}
else
{
LOGGER.info("Attempted to unregister key " + key.toString() + ", but this key has not been registered.");
}
}
private void unregisterCallback(Iterator<Object> iter, Object key)
{
iter.remove();
LOGGER.finer("Key " + key.toString() + " unregistered.");
if (callbackRegistry.isEmpty())
{
stopTimer();
}
}
public boolean keyIsRegistered(Object key)
{
return callbackRegistry.containsKey(key);
}
public TickCallback getCallback(Object key)
{
if (keyIsRegistered(key))
{
return callbackRegistry.get(key);
}
else
{
LOGGER.fine("Key " + key.toString() + " is not registered; returning null.");
return null;
}
}
private void tick()
{
long fireTimeMillis = System.currentTimeMillis();
Iterator<Object> iter = callbackRegistry.keySet().iterator();
while (iter.hasNext())
{
Object key = iter.next();//Lowest point in stack for our code
TickCallback callback = callbackRegistry.get(key);
if (callback.isFireTime(fireTimeMillis))
{
if (Level.FINEST.equals(LOGGER.getLevel()))
{
LOGGER.finest("Firing callback for key " + key.toString());
}
callback.onTick();
callback.markLastFireTime();
}
if (callback.shouldTerminate())
{
LOGGER.finer("Callback for key " + key.toString() +
" has reached its specified run-for-seconds and will now be unregistered.");
unregisterCallback(iter, key);
}
}
}
private void startTimer()
{
timer.scheduleRepeating(MILLIS_IN_SEC);
LOGGER.finer(this + " started.");
}
private void stopTimer()
{
timer.cancel();
LOGGER.finer(this + " stopped.");
}
/**
* A task to run on a given interval, with the option to specify a maximum number of seconds to run.
*/
public static abstract class TickCallback
{
private long intervalMillis;
private long startedAtMillis;
private long millisRunningAtLastFire;
private Optional<Long> runForMillis;
/**
* @param intervalSeconds
* The number of seconds which must elapse between each invocation of {@link #onTick()}.
* @param runForSeconds
* An optional maximum number of seconds to run for, after which the TickCallback will be eligible
* to be automatically unregistered. Pass {@link Optional#absent()} to specify that the TickCallback
* must be manually unregistered. Make this value the same as {@param intervalSeconds} to run the
* callback only once.
*/
public TickCallback(int intervalSeconds, Optional<Integer> runForSeconds)
{
this.intervalMillis = intervalSeconds * MILLIS_IN_SEC;
this.runForMillis = runForSeconds.isPresent() ?
Optional.of((long)runForSeconds.get() * MILLIS_IN_SEC) : Optional.<Long>absent();
}
private void markStartTime()
{
millisRunningAtLastFire = 0;
startedAtMillis = System.currentTimeMillis();
}
private void markLastFireTime()
{
millisRunningAtLastFire += intervalMillis;
}
private boolean isFireTime(long nowMillis)
{
return nowMillis - (startedAtMillis + millisRunningAtLastFire) >= intervalMillis;
}
private boolean shouldTerminate()
{
return runForMillis.isPresent() && System.currentTimeMillis() - startedAtMillis >= runForMillis.get();
}
/**
* A callback to be run every time intervalSeconds seconds have past since this callback was registered.
*/
public abstract void onTick();
}
}
package com.XXXXX.common.gwt.timer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.google.common.base.Optional;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.Timer;
/**
* A {@link Timer} wrapper which allows for the registration of callbacks to be invoked after a given number of ticks.
* The timer will only run if at least one {@link TickCallback} is currently registered and will stop running when all
* callbacks have been unregistered.
*
* The intent of this class is to reduce overhead by allowing all callbacks in a GWT application to use the same
* Javascript timer.
*/
public class CallbackTimer
{
private static final Logger LOGGER = Logger.getLogger(CallbackTimer.class.getName());
private static final int MILLIS_IN_SEC = 1000;
private Timer timer;
private Map<Object, TickCallback> callbackRegistry = new HashMap<>();
private List<Command> deferredDeltas = new ArrayList<>();
public CallbackTimer()
{
timer = new Timer()
{
@Override
public void run()
{
tick();
}
};
}
public void registerCallback(final Object key, final TickCallback callback)
{
deferredDeltas.add(new Command()
{
@Override
public void execute()
{
activateCallback(key, callback);
}
});
if (!timer.isRunning())
{
startTimer();
}
}
private void activateCallback(Object key, TickCallback callback)
{
if (callbackRegistry.containsKey(key))
{
LOGGER.fine("Key " + key.toString() + " is being overwritten with a new callback.");
}
callbackRegistry.put(key, callback);
callback.markStartTime();
LOGGER.finer("Key " + key.toString() + " registered.");
}
public void unregisterCallback(final Object key)
{
deferredDeltas.add(new Command()
{
@Override
public void execute()
{
deactivateCallback(key);
}
});
}
private void deactivateCallback(Object key)
{
if (callbackRegistry.containsKey(key))
{
callbackRegistry.remove(key);
LOGGER.fine("Key " + key.toString() + " unregistered.");
if (callbackRegistry.isEmpty())
{
stopTimer();
}
}
else
{
LOGGER.info("Attempted to unregister key " + key.toString() + ", but this key has not been registered.");
}
}
private void handleQueuedAddsAndRemoves()
{
for (Command c : deferredDeltas)
{
c.execute();
}
deferredDeltas.clear();
}
public boolean keyIsRegistered(Object key)
{
return callbackRegistry.containsKey(key);
}
private void tick()
{
handleQueuedAddsAndRemoves();
long fireTimeMillis = System.currentTimeMillis();
for (Map.Entry<Object, TickCallback> objectTickCallbackEntry : callbackRegistry.entrySet())
{
Object key = objectTickCallbackEntry.getKey();
TickCallback callback = objectTickCallbackEntry.getValue();
if (callback.isFireTime(fireTimeMillis))
{
if (Level.FINEST.equals(LOGGER.getLevel()))
{
LOGGER.finest("Firing callback for key " + key.toString());
}
callback.onTick();
callback.markLastFireTime();
}
if (callback.shouldTerminate())
{
LOGGER.finer("Callback for key " + key.toString() +
" has reached its specified run-for-seconds and will now be unregistered.");
unregisterCallback(key);
}
}
}
private void startTimer()
{
timer.scheduleRepeating(MILLIS_IN_SEC);
LOGGER.finer(this + " started.");
}
private void stopTimer()
{
timer.cancel();
LOGGER.finer(this + " stopped.");
}
/**
* A task to run on a given interval, with the option to specify a maximum number of seconds to run.
*/
public static abstract class TickCallback
{
private long intervalMillis;
private long startedAtMillis;
private long millisRunningAtLastFire;
private Optional<Long> runForMillis;
/**
* @param intervalSeconds The number of seconds which must elapse between each invocation of {@link #onTick()}.
* @param runForSeconds An optional maximum number of seconds to run for, after which the TickCallback will be
* eligible
* to be automatically unregistered. Pass {@link Optional#absent()} to specify that the TickCallback
* must be manually unregistered. Make this value the same as {@param intervalSeconds} to run the
* callback only once.
*/
protected TickCallback(int intervalSeconds, Optional<Integer> runForSeconds)
{
this.intervalMillis = intervalSeconds * MILLIS_IN_SEC;
this.runForMillis = runForSeconds.isPresent() ?
Optional.of((long) runForSeconds.get() * MILLIS_IN_SEC) : Optional.<Long>absent();
}
private void markStartTime()
{
millisRunningAtLastFire = 0;
startedAtMillis = System.currentTimeMillis();
}
private void markLastFireTime()
{
millisRunningAtLastFire += intervalMillis;
}
private boolean isFireTime(long nowMillis)
{
return nowMillis - (startedAtMillis + millisRunningAtLastFire) >= intervalMillis;
}
private boolean shouldTerminate()
{
return runForMillis.isPresent() && System.currentTimeMillis() - startedAtMillis >= runForMillis.get();
}
/**
* A callback to be run every time intervalSeconds seconds have past since this callback was registered.
*/
public abstract void onTick();
}
}