There are many cases in which you may wish to retry an operation a certain number of times. Examples are database failures, network communication failures or file IO problems.
Approach 1
This is the traditional approach and involves a counter and a loop.
final
int
numberOfRetries =
5
;
final
long
timeToWait =
1000
;
for
(
int
i=
0
; i<numberOfRetries; i++) {
//perform the operation
try
{
break
;
}
catch
(Exception e) {
logger.error(
"Retrying..."
,e);
try
{
Thread.sleep(timeToWait);
}
catch
(InterruptedException i) {
}
}
}
|
Approach 2
In this approach, we hide the retry counter in a separate class called RetryStrategy
and call it like this:
public
class
RetryStrategy
{
public
static
final
int
DEFAULT_NUMBER_OF_RETRIES =
5
;
public
static
final
long
DEFAULT_WAIT_TIME =
1000
;
private
int
numberOfRetries;
//total number of tries
private
int
numberOfTriesLeft;
//number left
private
long
timeToWait;
//wait interval
public
RetryStrategy()
{
this
(DEFAULT_NUMBER_OF_RETRIES, DEFAULT_WAIT_TIME);
}
public
RetryStrategy(
int
numberOfRetries,
long
timeToWait)
{
this
.numberOfRetries = numberOfRetries;
numberOfTriesLeft = numberOfRetries;
this
.timeToWait = timeToWait;
}
/**
* @return true if there are tries left
*/
public
boolean
shouldRetry()
{
return
numberOfTriesLeft >
0
;
}
/**
* This method should be called if a try fails.
*
* @throws RetryException if there are no more tries left
*/
public
void
errorOccured()
throws
RetryException
{
numberOfTriesLeft --;
if
(!shouldRetry())
{
throw
new
RetryException(numberOfRetries +
" attempts to retry failed at "
+ getTimeToWait() +
"ms interval"
);
}
waitUntilNextTry();
}
/**
* @return time period between retries
*/
public
long
getTimeToWait()
{
return
timeToWait ;
}
/**
* Sleeps for the duration of the defined interval
*/
private
void
waitUntilNextTry()
{
try
{
Thread.sleep(getTimeToWait());
}
catch
(InterruptedException ignored) {}
}
public
static
void
main(String[] args) {
RetryStrategy retry =
new
RetryStrategy();
while
(retry.shouldRetry()) {
try
{
break
;
}
catch
(Exception e) {
try
{
retry.errorOccured();
}
catch
(RetryException e1) {
e.printStackTrace();
}
}
}
}
}
|
Approach 3
Approach 2, although cleaner, hasn't really reduced the number of lines of code we have to write. In the next approach, we hide the retry loop and all logic in a separate class called RetriableTask
. We make the operation that we are going to retry Callable
and wrap it in a RetriableTask
which then handles all the retrying for us, behind-the-scenes:
public
class
RetriableTask<T>
implements
Callable<T> {
private
Callable<T> task;
public
static
final
int
DEFAULT_NUMBER_OF_RETRIES =
5
;
public
static
final
long
DEFAULT_WAIT_TIME =
1000
;
private
int
numberOfRetries;
// total number of tries
private
int
numberOfTriesLeft;
// number left
private
long
timeToWait;
// wait interval
public
RetriableTask(Callable<T> task) {
this
(DEFAULT_NUMBER_OF_RETRIES, DEFAULT_WAIT_TIME, task);
}
public
RetriableTask(
int
numberOfRetries,
long
timeToWait,
Callable<T> task) {
this
.numberOfRetries = numberOfRetries;
numberOfTriesLeft = numberOfRetries;
this
.timeToWait = timeToWait;
this
.task = task;
}
public
T call()
throws
Exception {
while
(
true
) {
try
{
return
task.call();
}
catch
(InterruptedException e) {
throw
e;
}
catch
(CancellationException e) {
throw
e;
}
catch
(Exception e) {
numberOfTriesLeft--;
if
(numberOfTriesLeft ==
0
) {
throw
new
RetryException(numberOfRetries +
" attempts to retry failed at "
+ timeToWait +
"ms interval"
, e);
}
Thread.sleep(timeToWait);
}
}
}
public
static
void
main(String[] args) {
Callable<Remote> task =
new
Callable<Remote>() {
public
Remote call()
throws
Exception {
return
(Remote) Naming.lookup(url);
}
};
RetriableTask<Remote> r =
new
RetriableTask<Remote>(task);
try
{
r.call();
}
catch
(Exception e) {
e.printStackTrace();
}
}
}
|
Also see:
References: