com.github.dm.jrt.runner.PriorityRunner Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jroutine Show documentation
Show all versions of jroutine Show documentation
Parallel programming on the go
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.dm.jrt.runner;
import com.github.dm.jrt.util.WeakIdentityHashMap;
import java.io.Serializable;
import java.util.Collections;
import java.util.Comparator;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import javax.annotation.Nonnull;
/**
* Class providing ordering of executions based on priority.
* Each class instance wraps a supporting runner and then provides different runner instances, each
* one enqueuing executions with a specific priority.
*
* Each enqueued execution will age each time an higher priority one takes the precedence, so that
* older executions slowly increases their priority. Such mechanism has been implemented to avoid
* starvation of low priority executions. Hence, when assigning priority to different runners, it is
* important to keep in mind that the difference between two priorities corresponds to the maximum
* age the lower priority execution will have, before getting precedence over the higher priority
* one.
*
* Note that the queue is not shared between different instances of this class.
*
* Created by davide-maestroni on 04/28/2015.
*/
public class PriorityRunner {
private static final PriorityExecutionComparator PRIORITY_EXECUTION_COMPARATOR =
new PriorityExecutionComparator();
private static final WeakIdentityHashMap sRunners =
new WeakIdentityHashMap();
private final AtomicLong mAge = new AtomicLong(Long.MAX_VALUE - Integer.MAX_VALUE);
private final Map mDelayedExecutions =
Collections.synchronizedMap(
new WeakIdentityHashMap());
private final WeakIdentityHashMap> mExecutions =
new WeakIdentityHashMap>();
private final PriorityBlockingQueue mQueue;
private final TemplateExecution mExecution = new TemplateExecution() {
public void run() {
final PriorityExecution execution = mQueue.poll();
if (execution != null) {
execution.run();
}
}
};
private final Runner mRunner;
private final WeakHashMap mRunners =
new WeakHashMap();
/**
* Constructor.
*
* @param wrapped the wrapped instance.
*/
@SuppressWarnings("ConstantConditions")
private PriorityRunner(@Nonnull final Runner wrapped) {
if (wrapped == null) {
throw new NullPointerException("the wrapped runner must not be null");
}
mRunner = wrapped;
mQueue = new PriorityBlockingQueue(10, PRIORITY_EXECUTION_COMPARATOR);
}
@Nonnull
static PriorityRunner getInstance(@Nonnull final Runner wrapped) {
if (wrapped instanceof QueuingRunner) {
return ((QueuingRunner) wrapped).enclosingRunner();
}
synchronized (sRunners) {
final WeakIdentityHashMap runners = sRunners;
PriorityRunner runner = runners.get(wrapped);
if (runner == null) {
runner = new PriorityRunner(wrapped);
runners.put(wrapped, runner);
}
return runner;
}
}
private static int compareLong(long x, long y) {
return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
/**
* Returns a runner enqueuing executions with the specified priority.
*
* @param priority the execution priority.
* @return the runner instance.
*/
@Nonnull
public Runner getRunner(final int priority) {
synchronized (mRunners) {
final WeakHashMap runners = mRunners;
for (final QueuingRunner runner : runners.keySet()) {
if (runner.mPriority == priority) {
return runner;
}
}
final QueuingRunner runner = new QueuingRunner(priority);
runners.put(runner, null);
return runner;
}
}
/**
* Execution implementation delaying the enqueuing of the priority execution.
*/
private static class DelayedExecution implements Execution {
private final PriorityExecution mExecution;
private final PriorityBlockingQueue mQueue;
/**
* Constructor.
*
* @param queue the queue.
* @param execution the priority execution.
*/
private DelayedExecution(@Nonnull final PriorityBlockingQueue queue,
@Nonnull final PriorityExecution execution) {
mQueue = queue;
mExecution = execution;
}
public boolean mayBeCanceled() {
return mExecution.mayBeCanceled();
}
public void run() {
final PriorityBlockingQueue queue = mQueue;
queue.put(mExecution);
final PriorityExecution execution = queue.poll();
if (execution != null) {
execution.run();
}
}
}
/**
* Execution implementation providing a comparison based on priority and the wrapped execution
* age.
*/
private static class PriorityExecution implements Execution {
private final long mAge;
private final Execution mExecution;
private final int mPriority;
/**
* Constructor.
*
* @param execution the wrapped execution.
* @param priority the execution priority.
* @param age the execution age.
*/
private PriorityExecution(@Nonnull final Execution execution, final int priority,
final long age) {
mExecution = execution;
mPriority = priority;
mAge = age;
}
public boolean mayBeCanceled() {
return mExecution.mayBeCanceled();
}
public void run() {
mExecution.run();
}
}
/**
* Comparator of priority execution instances.
*/
private static class PriorityExecutionComparator
implements Comparator, Serializable {
// Just don't care...
private static final long serialVersionUID = -1;
public int compare(final PriorityExecution o1, final PriorityExecution o2) {
final int thisPriority = o1.mPriority;
final long thisAge = o1.mAge;
final int thatPriority = o2.mPriority;
final long thatAge = o2.mAge;
final int compare = compareLong(thatAge + thatPriority, thisAge + thisPriority);
return (compare == 0) ? compareLong(thatAge, thisAge) : compare;
}
}
/**
* Enqueuing runner implementation.
*/
private class QueuingRunner implements Runner {
private final int mPriority;
/**
* Constructor.
*
* @param priority the execution priority.
*/
private QueuingRunner(final int priority) {
mPriority = priority;
}
public void cancel(@Nonnull final Execution execution) {
synchronized (mExecutions) {
final WeakHashMap priorityExecutions =
mExecutions.remove(execution);
if (priorityExecutions != null) {
final Runner runner = mRunner;
final PriorityBlockingQueue queue = mQueue;
final Map delayedExecutions =
mDelayedExecutions;
for (final PriorityExecution priorityExecution : priorityExecutions.keySet()) {
if (!queue.remove(priorityExecution)) {
runner.cancel(delayedExecutions.remove(priorityExecution));
}
}
}
}
}
public boolean isExecutionThread() {
return mRunner.isExecutionThread();
}
public void run(@Nonnull final Execution execution, final long delay,
@Nonnull final TimeUnit timeUnit) {
final boolean mayBeCanceled = execution.mayBeCanceled();
final PriorityExecution priorityExecution =
new PriorityExecution(execution, mPriority, mAge.getAndDecrement());
if (mayBeCanceled) {
synchronized (mExecutions) {
final WeakIdentityHashMap>
executions = mExecutions;
WeakHashMap priorityExecutions =
executions.get(execution);
if (priorityExecutions == null) {
priorityExecutions = new WeakHashMap();
executions.put(execution, priorityExecutions);
}
priorityExecutions.put(priorityExecution, null);
}
}
if (delay == 0) {
mQueue.put(priorityExecution);
mRunner.run(mExecution, 0, timeUnit);
} else {
final DelayedExecution delayedExecution =
new DelayedExecution(mQueue, priorityExecution);
if (mayBeCanceled) {
mDelayedExecutions.put(priorityExecution, delayedExecution);
}
mRunner.run(delayedExecution, delay, timeUnit);
}
}
private PriorityRunner enclosingRunner() {
return PriorityRunner.this;
}
}
}