如果 Http 连接失败(例如服务器宕机),我需要减少计数器。
我原本想说“当然可以”,但是在看到这句话后,我不太确定了。我猜你想做的事情是这样的:
def sendRequest(url)
request = new request to url
request.header["X-count"] = next serial
if request.send() != SUCCESS
rewind serial
如果是这样,我认为不应该允许两个线程同时发送请求,而是要使用序列化请求的方法,而不是使用 AtomicInteger,因为 AtomicInteger 只能让你原子地执行一些操作。如果两个线程同时调用 sendRequest,并且第一个失败了,那么会发生以下情况:
Thread | What happens?
--------+-------------------------
A | Creates new request
B | Creates new request
A | Set request["X-count"] = 0
A | Increment counter to 1
A | Send request
B | Set request["X-count"] = 1
B | Increment counter to 2
B | Send request
A | Request fails
B | Request succeeds
A | Rewind counter down to 1
C | Creates new request
C | Set request["X-count"] = 1
C | Increment counter to 2
现在,你已经发送了两个 X-count = 1 的请求。如果你想避免这种情况,你应该使用类似下面的代码(假设 Request 和 Response 是用于处理 URL 请求的类):
class SerialRequester {
private volatile int currentSerial = 0;
public synchronized Response sendRequest(URL url) throws SomeException {
Request request = new Request(url);
request.setHeader("X-count", currentSerial);
Response response = request.send();
if (response.isSuccess()) ++currentSerial;
return response;
}
}
这个类保证通过同一个SerialRequester
发送的请求,不会存在相同的 X-count 值。
编辑 许多人对上面的解决方案不支持并发执行感到担忧。确实如此。但是要解决提问者的问题,必须这样设计。如果不需要在请求失败时递减计数器,则AtomicInteger
非常完美,但在这种情况下会出现错误。
编辑2 我写了一个串行请求器(就像上面的请求器),不太容易出现冻结,并且如果请求等待时间过长(即在工作线程中排队但未启动),则会终止请求。因此,如果通道堵塞并且一个请求挂起很长时间,其他请求最多等待一定的时间,以便队列不会无限增长,直到通道畅通为止。
class SerialRequester {
private enum State { PENDING, STARTED, ABORTED }
private final ExecutorService executor =
Executors.newSingleThreadExecutor();
private int currentSerial = 0;
public Response sendRequest(final URL url)
throws SomeException, InterruptedException {
final AtomicReference<State> state =
new AtomicReference<State>(State.PENDING);
Future<Response> result = executor.submit(new Callable<Response>(){
@Override
public Result call() throws SomeException {
if (!state.compareAndSet(State.PENDING, State.STARTED))
return null;
Request request = new Request(url);
request.setHeader("X-count", currentSerial);
Response response = request.send();
if (response.isSuccess()) ++currentSerial;
return response;
}
});
try {
try {
return result.get(30, TimeUnit.SECONDS);
} catch (TimeoutException e){
if (state.compareAndSet(State.PENDING, State.ABORTED))
throw new SomeException("Request queued too long", e);
return result.get();
}
} catch (ExecutionException e) {
throw new SomeException("Request error", e);
}
}
}