Notice System.out.println cross threads boundary will change the synchronization structure of your program, if you add any System.out inside the TimerTask, the observation will be a heisenbug (heisenberg) observation.
https://www.youtube.com/watch?v=yfZEdKljkYM
Concurrency>cat OutOfTime.java
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import static java.util.concurrent.TimeUnit.SECONDS;
/**
* OutOfTime
* <p/>
* Class illustrating confusing Timer behavior
*/
public class OutOfTime {
private static AtomicInteger marker = new AtomicInteger(0);
public static void main(String[] args) {
try{
Timer timer = new Timer();
System.out.println("schedule task1:" + marker.get());
timer.schedule(new ThrowTask(), 10);
System.out.println("schedule task2:" + marker.get());
timer.schedule(new ThrowTask(), 0);
SECONDS.sleep(1);
System.out.println("schedule task3:" + marker.get());
timer.schedule(new ThrowTask(), 0);
SECONDS.sleep(2);
System.out.println("schedule task4:" + marker.get());
timer.schedule(new ThrowTask(), 1);
SECONDS.sleep(2);
System.out.println("schedule task5:" + marker.get());
timer.schedule(new ThrowTask(), 1);
SECONDS.sleep(5);
} catch(Exception e) {
e.printStackTrace();
}
System.out.println("scheduled tasks:" + marker.get());
}
static class ThrowTask extends TimerTask {
public void run() {
//any System.out in Timer thread will add an extra happens-before relationship to main thread
try {
SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
marker.set(marker.get() + 1);
throw new RuntimeException();
}
}
}
Concurrency>javac OutOfTime.java
Concurrency>java OutOfTime
schedule task1:0
schedule task2:0
schedule task3:0
Exception in thread "Timer-0" java.lang.RuntimeException
at OutOfTime$ThrowTask.run(OutOfTime.java:46)
at java.util.TimerThread.mainLoop(Timer.java:555)
at java.util.TimerThread.run(Timer.java:505)
schedule task4:1
java.lang.IllegalStateException: Timer already cancelled.
at java.util.Timer.sched(Timer.java:397)
at java.util.Timer.schedule(Timer.java:193)
at OutOfTime.main(OutOfTime.java:25)
scheduled tasks:1
Concurrency>
ScheduledThreadPoolExecutor deals properly with ill-behaved tasks; there is little reason to use Timer in java 5.0 or later. In the following example, Executors.newScheduledThreadPool(2) returns a ScheduledExecutorService. ScheduledExecutorService extends ExecutorService with extra support for schedule capability. After main thread submitted 5 Runnable to ScheduledExecutorService, the main thread then shut down the service. At the time the 6th Runnable is submitted, there are 3 Runnable has finished executing, there are one Runnable that is active, another one (task1 with delay 10 seconds) waiting to be executed. After the ScheduledExecutorService shutdown, it has executed all the submitted Runnables, and rejected any new Runnable submitted after shutdown() is called.
Concurrency>cat OutOfTimeFix.java
import static java.util.concurrent.TimeUnit.SECONDS;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class OutOfTimeFix {
private static AtomicInteger marker = new AtomicInteger(0);
public static void main(String[] args) throws Exception {
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
System.out.println("schedule task1:" + marker.get());
service.schedule(new ThrowTask(), 10, TimeUnit.SECONDS);
System.out.println("schedule task2:" + marker.get());
service.schedule(new ThrowTask(), 0, TimeUnit.SECONDS);
SECONDS.sleep(1);
System.out.println("schedule task3:" + marker.get());
service.schedule(new ThrowTask(), 0, TimeUnit.SECONDS);
SECONDS.sleep(2);
System.out.println("schedule task4:" + marker.get());
service.schedule(new ThrowTask(), 1, TimeUnit.SECONDS);
SECONDS.sleep(2);
System.out.println("schedule task5:" + marker.get());
ScheduledFuture<?> fu = service.schedule(new ThrowTask(), 1, TimeUnit.SECONDS);
SECONDS.sleep(3);
service.shutdown();
System.out.println("schedule task6:" + marker.get());
try {
service.schedule(new ThrowTask(), 1, TimeUnit.SECONDS);
} catch (RejectedExecutionException e) {
e.printStackTrace();
}
service.awaitTermination(10, TimeUnit.SECONDS);
System.out.println("end of schedule tasks:" + marker.get());
System.out.println(fu.isDone());
}
static class ThrowTask implements Runnable {
public void run() {
//any System.out in Timer thread will add an extra happens-before relationship to main thread
try {
SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
marker.set(marker.get() + 1);
throw new RuntimeException();
}
}
}
Concurrency>javac OutOfTimeFix.java
Concurrency>java OutOfTimeFix
schedule task1:0
schedule task2:0
schedule task3:0
schedule task4:2
schedule task5:2
schedule task6:3
java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask@75b84c92 rejected from java.util.concurrent.ScheduledThreadPoolExecutor@6bc7c054[Shutting down, pool size = 2, active threads = 1, queued tasks = 1, completed tasks = 3]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
at java.util.concurrent.ScheduledThreadPoolExecutor.delayedExecute(ScheduledThreadPoolExecutor.java:326)
at java.util.concurrent.ScheduledThreadPoolExecutor.schedule(ScheduledThreadPoolExecutor.java:533)
at OutOfTimeFix.main(OutOfTimeFix.java:30)
end of schedule tasks:5
true
Concurrency>