All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.dm.jrt.runner.PriorityRunner Maven / Gradle / Ivy

There is a newer version: 5.9.0
Show newest version
/*
 * 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; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy