Skip to content

Instantly share code, notes, and snippets.

@Hervian
Created December 13, 2016 21:04
Show Gist options
  • Save Hervian/2987085c6ea5b5227ba7f5b506663432 to your computer and use it in GitHub Desktop.
Save Hervian/2987085c6ea5b5227ba7f5b506663432 to your computer and use it in GitHub Desktop.
A Java service class for creating and managing background threads as WeakReferences
package com.github.hervian.util;
import java.lang.ref.WeakReference;
import java.util.Hashtable;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
public enum PeriodicEventHelper {
INSTANCE;
private static final int CORE_THREAD_POOL_SIZE = 1;
private ScheduledThreadPoolExecutor daemonThreadBasedScheduler;
private final Map<WeakReference<PeriodicEventDefiner>, ScheduledFuture<?>> mapFromPeriodicEventsToScheduledFutures = new Hashtable<>();
/**
* This service wraps the argument provided {@link PeriodicEventDefiner} in a
* WeakReference, and starts a background job, as specified by the definer.
* The job will be terminated/removed once the PeriodicDefiner has been
* garbage collected. A typical job is one that refreshes a cache every X
* minutes. <br>
* Using the {@link PeriodicEventHelper} offers the following advantage over
* manually creating a new Thread for each job:
* <ul>
* <li>In essence, we only have one Thread which handles our background jobs.
* If need be (if the jobs are scheduled to run at the same time) new Threads
* are created on the fly, and destroyed afterwards.
* </ul>
*
* @param periodicEventDefiner
* A bean that defines a task, and an interval. The task is used in a
* recurring job and the time between each execution is defined by
* the interval. The interval is described as a unit type and a
* number of units.
*/
public void register(PeriodicEventDefiner periodicEventDefiner) {
if (periodicEventDefiner != null && periodicEventDefiner.getPeriodEventInterval() != null) {
WeakReference<PeriodicEventDefiner> weakReferenceToPeriodicEvent = new WeakReference<PeriodicEventDefiner>(
periodicEventDefiner);
ScheduledFuture<?> scheduledFuture = createAndScheduleRunnable(periodicEventDefiner,
weakReferenceToPeriodicEvent);
mapFromPeriodicEventsToScheduledFutures.put(weakReferenceToPeriodicEvent, scheduledFuture);
}
}
private ScheduledFuture<?> createAndScheduleRunnable(PeriodicEventDefiner periodicEventDefiner,
WeakReference<PeriodicEventDefiner> weakReferenceToPeriodicEvent) {
Runnable command = wrapEventInRunnable(weakReferenceToPeriodicEvent);
PeriodEventInterval interval = periodicEventDefiner.getPeriodEventInterval();
return scheduleRunnable(command, interval);
}
private ScheduledFuture<?> scheduleRunnable(Runnable command, PeriodEventInterval interval) {
ScheduledThreadPoolExecutor scheduler = getScheduler();
return scheduler.scheduleWithFixedDelay(command, interval.getInitialDelay(), interval.getDelay(),
interval.getTimeUnit());
}
private Runnable wrapEventInRunnable(final WeakReference<PeriodicEventDefiner> weakReferenceToPeriodicEvent) {
return new Runnable() {
@Override
public void run() {
PeriodicEventDefiner periodicEventDefiner = weakReferenceToPeriodicEvent.get();
if (periodicEventDefiner != null) {
periodicEventDefiner.executePeriodicEvent();
} else {
cancelAndRemovePeriodicEvent(weakReferenceToPeriodicEvent); //PeriodicEventDefiner has been garbage collected. Cancel thread and remove map entry.
}
}
};
}
private void cancelAndRemovePeriodicEvent(final WeakReference<PeriodicEventDefiner> weakReferenceToPeriodicEvent) {
ScheduledFuture<?> scheduledFuture = mapFromPeriodicEventsToScheduledFutures.get(weakReferenceToPeriodicEvent);
if (scheduledFuture != null) {
scheduledFuture.cancel(true);
mapFromPeriodicEventsToScheduledFutures.remove(weakReferenceToPeriodicEvent);
}
}
private ScheduledThreadPoolExecutor getScheduler() {
if (daemonThreadBasedScheduler == null) {
daemonThreadBasedScheduler = new ScheduledThreadPoolExecutor(CORE_THREAD_POOL_SIZE, new ThreadFactory() {
@Override
public Thread newThread(Runnable runnable) {
Thread daemonThread = Executors.defaultThreadFactory().newThread(runnable);
daemonThread.setDaemon(true);
return daemonThread;
}
});
daemonThreadBasedScheduler.setRemoveOnCancelPolicy(true);
}
return daemonThreadBasedScheduler;
}
public static interface PeriodicEventDefiner {
void executePeriodicEvent();
PeriodEventInterval getPeriodEventInterval();
}
public static class PeriodEventInterval {
private long initialDelay;
private TimeUnit timeUnit;
private long delay;
public PeriodEventInterval(long initialDelay, TimeUnit timeUnit, long delay) {
this.initialDelay = initialDelay;
this.timeUnit = timeUnit;
this.delay = delay;
}
public long getInitialDelay() {
return initialDelay;
}
public TimeUnit getTimeUnit() {
return timeUnit;
}
public long getDelay() {
return delay;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment