How do I implement task prioritization using an ExecutorService in Java 5?

前端 未结 6 1887
粉色の甜心
粉色の甜心 2020-11-27 03:51

I am implementing a thread pooling mechanism in which I\'d like to execute tasks of varying priorities. I\'d like to have a nice mechanism whereby I can submit a high prior

相关标签:
6条回答
  • 2020-11-27 04:05

    I will try to explain this problem with a fully functional code. But before diving into the code I would like to explain about PriorityBlockingQueue

    PriorityBlockingQueue : PriorityBlockingQueue is an implementation of BlockingQueue. It accepts the tasks along with their priority and submits the task with the highest priority for execution first. If any two tasks have same priority, then we need to provide some custom logic to decide which task goes first.

    Now lets get into the code straightaway.

    Driver class : This class creates an executor which accepts tasks and later submits them for execution. Here we create two tasks one with LOW priority and the other with HIGH priority. Here we tell the executor to run a MAX of 1 threads and use the PriorityBlockingQueue.

         public static void main(String[] args) {
    
           /*
           Minimum number of threads that must be running : 0
           Maximium number of threads that can be created : 1
           If a thread is idle, then the minimum time to keep it alive : 1000
           Which queue to use : PriorityBlockingQueue
           */
        PriorityBlockingQueue queue = new PriorityBlockingQueue();
        ThreadPoolExecutor executor = new ThreadPoolExecutor(0,1,
            1000, TimeUnit.MILLISECONDS,queue);
    
    
        MyTask task = new MyTask(Priority.LOW,"Low");
        executor.execute(new MyFutureTask(task));
        task = new MyTask(Priority.HIGH,"High");
        executor.execute(new MyFutureTask(task));
        task = new MyTask(Priority.MEDIUM,"Medium");
        executor.execute(new MyFutureTask(task));
    
    }
    

    MyTask class : MyTask implements Runnable and accepts priority as an argument in the constructor. When this task runs, it prints a message and then puts the thread to sleep for 1 second.

       public class MyTask implements Runnable {
    
      public int getPriority() {
        return priority.getValue();
      }
    
      private Priority priority;
    
      public String getName() {
        return name;
      }
    
      private String name;
    
      public MyTask(Priority priority,String name){
        this.priority = priority;
        this.name = name;
      }
    
      @Override
      public void run() {
        System.out.println("The following Runnable is getting executed "+getName());
        try {
          Thread.sleep(1000);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    
    }
    

    MyFutureTask class : Since we are using PriorityBlocingQueue for holding our tasks, our tasks must be wrapped inside FutureTask and our implementation of FutureTask must implement Comparable interface. The Comparable interface compares the priority of 2 different tasks and submits the task with the highest priority for execution.

     public class MyFutureTask extends FutureTask<MyFutureTask>
          implements Comparable<MyFutureTask> {
    
        private  MyTask task = null;
    
        public  MyFutureTask(MyTask task){
          super(task,null);
          this.task = task;
        }
    
        @Override
        public int compareTo(MyFutureTask another) {
          return task.getPriority() - another.task.getPriority();
        }
      }
    

    Priority class : Self explanatory Priority class.

    public enum Priority {
    
      HIGHEST(0),
      HIGH(1),
      MEDIUM(2),
      LOW(3),
      LOWEST(4);
    
      int value;
    
      Priority(int val) {
        this.value = val;
      }
    
      public int getValue(){
        return value;
      }
    
    
    }
    

    Now when we run this example, we get the following output

    The following Runnable is getting executed High
    The following Runnable is getting executed Medium
    The following Runnable is getting executed Low
    

    Even though we submitted the LOW priority first, but HIGH priority task later, but since we are using a PriorityBlockingQueue, any task with a higher priority will execute first.

    0 讨论(0)
  • 2020-11-27 04:06

    My solution preserves submition order of tasks for same priorities. It's an improvement of this answer

    Task execution order is based on:

    1. Priority
    2. Submit order (within same priority)

    Tester class:

    public class Main {
    
        public static void main(String[] args) throws InterruptedException, ExecutionException {
    
            ExecutorService executorService = PriorityExecutors.newFixedThreadPool(1);
    
            //Priority=0
            executorService.submit(newCallable("A1", 200));     //Defaults to priority=0 
            executorService.execute(newRunnable("A2", 200));    //Defaults to priority=0
            executorService.submit(PriorityCallable.of(newCallable("A3", 200), 0));
            executorService.submit(PriorityRunnable.of(newRunnable("A4", 200), 0));
            executorService.execute(PriorityRunnable.of(newRunnable("A5", 200), 0));
            executorService.submit(PriorityRunnable.of(newRunnable("A6", 200), 0));
            executorService.execute(PriorityRunnable.of(newRunnable("A7", 200), 0));
            executorService.execute(PriorityRunnable.of(newRunnable("A8", 200), 0));
    
            //Priority=1
            executorService.submit(PriorityRunnable.of(newRunnable("B1", 200), 1));
            executorService.submit(PriorityRunnable.of(newRunnable("B2", 200), 1));
            executorService.submit(PriorityCallable.of(newCallable("B3", 200), 1));
            executorService.execute(PriorityRunnable.of(newRunnable("B4", 200), 1));
            executorService.submit(PriorityRunnable.of(newRunnable("B5", 200), 1));
    
            executorService.shutdown();
    
        }
    
        private static Runnable newRunnable(String name, int delay) {
            return new Runnable() {
                @Override
                public void run() {
                    System.out.println(name);
                    sleep(delay);
                }
            };
        }
    
        private static Callable<Integer> newCallable(String name, int delay) {
            return new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    System.out.println(name);
                    sleep(delay);
                    return 10;
                }
            };
        }
    
        private static void sleep(long millis) {
            try {
                Thread.sleep(millis);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new RuntimeException(e);
            }
        }
    
    }
    

    Result:

    A1 B1 B2 B3 B4 B5 A2 A3 A4 A5 A6 A7 A8

    First task is A1 because there were no higher priority in the queue when it was inserted. B tasks are 1 priority so executed earlier, A tasks are 0 priority so executed later, but execution order is follows submition order: B1, B2, B3, ... A2, A3, A4 ...

    The solution:

    public class PriorityExecutors {
    
        public static ExecutorService newFixedThreadPool(int nThreads) {
            return new PriorityExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS);
        }
    
        private static class PriorityExecutor extends ThreadPoolExecutor {
            private static final int DEFAULT_PRIORITY = 0;
            private static AtomicLong instanceCounter = new AtomicLong();
    
            @SuppressWarnings({"unchecked"})
            public PriorityExecutor(int corePoolSize, int maximumPoolSize,
                    long keepAliveTime, TimeUnit unit) {
                super(corePoolSize, maximumPoolSize, keepAliveTime, unit, (BlockingQueue) new PriorityBlockingQueue<ComparableTask>(10,
                        ComparableTask.comparatorByPriorityAndSequentialOrder()));
            }
    
            @Override
            public void execute(Runnable command) {
                // If this is ugly then delegator pattern needed
                if (command instanceof ComparableTask) //Already wrapped
                    super.execute(command);
                else {
                    super.execute(newComparableRunnableFor(command));
                }
            }
    
            private Runnable newComparableRunnableFor(Runnable runnable) {
                return new ComparableRunnable(ensurePriorityRunnable(runnable));
            }
    
            @Override
            protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
                return new ComparableFutureTask<>(ensurePriorityCallable(callable));
            }
    
            @Override
            protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
                return new ComparableFutureTask<>(ensurePriorityRunnable(runnable), value);
            }
    
            private <T> PriorityCallable<T> ensurePriorityCallable(Callable<T> callable) {
                return (callable instanceof PriorityCallable) ? (PriorityCallable<T>) callable
                        : PriorityCallable.of(callable, DEFAULT_PRIORITY);
            }
    
            private PriorityRunnable ensurePriorityRunnable(Runnable runnable) {
                return (runnable instanceof PriorityRunnable) ? (PriorityRunnable) runnable
                        : PriorityRunnable.of(runnable, DEFAULT_PRIORITY);
            }
    
            private class ComparableFutureTask<T> extends FutureTask<T> implements ComparableTask {
                private Long sequentialOrder = instanceCounter.getAndIncrement();
                private HasPriority hasPriority;
    
                public ComparableFutureTask(PriorityCallable<T> priorityCallable) {
                    super(priorityCallable);
                    this.hasPriority = priorityCallable;
                }
    
                public ComparableFutureTask(PriorityRunnable priorityRunnable, T result) {
                    super(priorityRunnable, result);
                    this.hasPriority = priorityRunnable;
                }
    
                @Override
                public long getInstanceCount() {
                    return sequentialOrder;
                }
    
                @Override
                public int getPriority() {
                    return hasPriority.getPriority();
                }
            }
    
            private static class ComparableRunnable implements Runnable, ComparableTask {
                private Long instanceCount = instanceCounter.getAndIncrement();
                private HasPriority hasPriority;
                private Runnable runnable;
    
                public ComparableRunnable(PriorityRunnable priorityRunnable) {
                    this.runnable = priorityRunnable;
                    this.hasPriority = priorityRunnable;
                }
    
                @Override
                public void run() {
                    runnable.run();
                }
    
                @Override
                public int getPriority() {
                    return hasPriority.getPriority();
                }
    
                @Override
                public long getInstanceCount() {
                    return instanceCount;
                }
            }
    
            private interface ComparableTask extends Runnable {
                int getPriority();
    
                long getInstanceCount();
    
                public static Comparator<ComparableTask> comparatorByPriorityAndSequentialOrder() {
                    return (o1, o2) -> {
                        int priorityResult = o2.getPriority() - o1.getPriority();
                        return priorityResult != 0 ? priorityResult
                                : (int) (o1.getInstanceCount() - o2.getInstanceCount());
                    };
                }
    
            }
    
        }
    
        private static interface HasPriority {
            int getPriority();
        }
    
        public interface PriorityCallable<V> extends Callable<V>, HasPriority {
    
            public static <V> PriorityCallable<V> of(Callable<V> callable, int priority) {
                return new PriorityCallable<V>() {
                    @Override
                    public V call() throws Exception {
                        return callable.call();
                    }
    
                    @Override
                    public int getPriority() {
                        return priority;
                    }
                };
            }
        }
    
        public interface PriorityRunnable extends Runnable, HasPriority {
    
            public static PriorityRunnable of(Runnable runnable, int priority) {
                return new PriorityRunnable() {
                    @Override
                    public void run() {
                        runnable.run();
                    }
    
                    @Override
                    public int getPriority() {
                        return priority;
                    }
                };
            }
        }
    
    }
    
    0 讨论(0)
  • 2020-11-27 04:08

    At first blush it would seem you could define an interface for your tasks that extends Runnable or Callable<T> and Comparable. Then wrap a ThreadPoolExecutor with a PriorityBlockingQueue as the queue, and only accept tasks that implement your interface.

    Taking your comment into account, it looks like one option is to extend ThreadPoolExecutor, and override the submit() methods. Refer to AbstractExecutorService to see what the default ones look like; all they do is wrap the Runnable or Callable in a FutureTask and execute() it. I'd probably do this by writing a wrapper class that implements ExecutorService and delegates to an anonymous inner ThreadPoolExecutor. Wrap them in something that has your priority, so that your Comparator can get at it.

    0 讨论(0)
  • 2020-11-27 04:10

    I have solved this problem in a reasonable fashion, and I'll describe it below for future reference to myself and anyone else who runs into this problem with the Java Concurrent libraries.

    Using a PriorityBlockingQueue as the means for holding onto tasks for later execution is indeed a movement in the correct direction. The problem is that the PriorityBlockingQueue must be generically instantiated to contain Runnable instances, and it is impossible to call compareTo (or similiar) on a Runnable interface.

    Onto solving the problem. When creating the Executor, it must be given a PriorityBlockingQueue. The queue should further be given a custom Comparator to do proper in place sorting:

    new PriorityBlockingQueue<Runnable>(size, new CustomTaskComparator());
    

    Now, a peek at CustomTaskComparator:

    public class CustomTaskComparator implements Comparator<MyType> {
    
        @Override
        public int compare(MyType first, MyType second) {
             return comparison;
        }
    
    }
    

    Everything looking pretty straight forward up to this point. It gets a bit sticky here. Our next problem is to deal with the creation of FutureTasks from the Executor. In the Executor, we must override newTaskFor as so:

    @Override
    protected <V> RunnableFuture<V> newTaskFor(Callable<V> c) {
        //Override the default FutureTask creation and retrofit it with
        //a custom task. This is done so that prioritization can be accomplished.
        return new CustomFutureTask(c);
    }
    

    Where c is the Callable task that we're trying to execute. Now, let's have a peek at CustomFutureTask:

    public class CustomFutureTask extends FutureTask {
    
        private CustomTask task;
    
        public CustomFutureTask(Callable callable) {
            super(callable);
            this.task = (CustomTask) callable;
        }
    
        public CustomTask getTask() {
            return task;
        }
    
    }
    

    Notice the getTask method. We're gonna use that later to grab the original task out of this CustomFutureTask that we've created.

    And finally, let's modify the original task that we were trying to execute:

    public class CustomTask implements Callable<MyType>, Comparable<CustomTask> {
    
        private final MyType myType;
    
        public CustomTask(MyType myType) {
            this.myType = myType;
        }
    
        @Override
        public MyType call() {
            //Do some things, return something for FutureTask implementation of `call`.
            return myType;
        }
    
        @Override
        public int compareTo(MyType task2) {
            return new CustomTaskComparator().compare(this.myType, task2.myType);
        }
    
    }
    

    You can see that we implement Comparable in the task to delegate to the actual Comparator for MyType.

    And there you have it, customized prioritization for an Executor using the Java libraries! It takes some bit of bending, but it's the cleanest that I've been able to come up with. I hope this is helpful to someone!

    0 讨论(0)
  • 2020-11-27 04:13

    Would it be possible to have one ThreadPoolExecutor for each level of priority? A ThreadPoolExecutor can be instanciated with a ThreadFactory and you could have your own implementation of a ThreadFactory to set the different priority levels.

     class MaxPriorityThreadFactory implements ThreadFactory {
         public Thread newThread(Runnable r) {
             Thread thread = new Thread(r);
             thread.setPriority(Thread.MAX_PRIORITY);
         }
     }
    
    0 讨论(0)
  • 2020-11-27 04:18

    You can use these helper classes:

    public class PriorityFuture<T> implements RunnableFuture<T> {
    
        private RunnableFuture<T> src;
        private int priority;
    
        public PriorityFuture(RunnableFuture<T> other, int priority) {
            this.src = other;
            this.priority = priority;
        }
    
        public int getPriority() {
            return priority;
        }
    
        public boolean cancel(boolean mayInterruptIfRunning) {
            return src.cancel(mayInterruptIfRunning);
        }
    
        public boolean isCancelled() {
            return src.isCancelled();
        }
    
        public boolean isDone() {
            return src.isDone();
        }
    
        public T get() throws InterruptedException, ExecutionException {
            return src.get();
        }
    
        public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
            return src.get(timeout, unit);
        }
    
        public void run() {
            src.run();
        }
    
        public static Comparator<Runnable> COMP = new Comparator<Runnable>() {
            public int compare(Runnable o1, Runnable o2) {
                if (o1 == null && o2 == null)
                    return 0;
                else if (o1 == null)
                    return -1;
                else if (o2 == null)
                    return 1;
                else {
                    int p1 = ((PriorityFuture<?>) o1).getPriority();
                    int p2 = ((PriorityFuture<?>) o2).getPriority();
    
                    return p1 > p2 ? 1 : (p1 == p2 ? 0 : -1);
                }
            }
        };
    }
    

    AND

    public interface PriorityCallable<T> extends Callable<T> {
    
        int getPriority();
    
    }
    

    AND this helper method:

    public static ThreadPoolExecutor getPriorityExecutor(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,
                new PriorityBlockingQueue<Runnable>(10, PriorityFuture.COMP)) {
    
            protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
                RunnableFuture<T> newTaskFor = super.newTaskFor(callable);
                return new PriorityFuture<T>(newTaskFor, ((PriorityCallable<T>) callable).getPriority());
            }
        };
    }
    

    AND then use it like this:

    class LenthyJob implements PriorityCallable<Long> {
        private int priority;
    
        public LenthyJob(int priority) {
            this.priority = priority;
        }
    
        public Long call() throws Exception {
            System.out.println("Executing: " + priority);
            long num = 1000000;
            for (int i = 0; i < 1000000; i++) {
                num *= Math.random() * 1000;
                num /= Math.random() * 1000;
                if (num == 0)
                    num = 1000000;
            }
            return num;
        }
    
        public int getPriority() {
            return priority;
        }
    }
    
    public class TestPQ {
    
        public static void main(String[] args) throws InterruptedException, ExecutionException {
            ThreadPoolExecutor exec = getPriorityExecutor(2);
    
            for (int i = 0; i < 20; i++) {
                int priority = (int) (Math.random() * 100);
                System.out.println("Scheduling: " + priority);
                LenthyJob job = new LenthyJob(priority);
                exec.submit(job);
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题