Executor Service with LIFO ordering

后端 未结 4 743
不知归路
不知归路 2020-12-24 09:43

I wrote a lazy image downloader for my app using an ExecutorService. It gives me great control about how many downloads are running in parallel at what time and so on.

相关标签:
4条回答
  • 2020-12-24 09:57

    The ThreadPoolExecutor has a constructor which allows to specify the queue type to use. You can plug any BlockingQueue in there, and possibly a priority queue might be a good fit for you. You can configure the priority queue to sort based on a (creation) time stamp which you add to you download jobs, and the executor will execute the jobs in the desired order.

    0 讨论(0)
  • 2020-12-24 10:03

    You will need to specify the queue type that the ExecutorService is using.

    Typically you might be retrieving an ExecutorService via the static methods in Executors. Instead you will need to instantiate one directly and pass in the Queue type that you want that provides LIFO.

    EG, to create a LIFO thread pool executor, you could use the following constructor.

    ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)
    

    and pass in a LIFO queue as the final parameter.

    There is no LIFO queue in the java collections that I am aware of (please correct me if wrong), but you could easily just create an anonymous inner class that extends LinkedBlockingQueue and overrides the appropriate methods.

    For example, (untested)

    ThreadPoolExecutor executor = new ThreadPoolExecutor(4, 16, 1, TimeUnit.MINUTES, new LinkedBlockingQueue() {
    
      @Override
      public void put(Object obj) { 
        // override to put objects at the front of the list
        super.addFirst(obj);
      }
    
    });
    

    UPDATE in response to comments.

    We can use a blocking queue that wraps a priority queue. We have to wrap because the Executor expects runnables but we need timestamps too.

    // the class that will wrap the runnables
    static class Pair {
    
         long   timestamp;
        Runnable    runnable;
    
        Pair(Runnable r) {
            this.timestamp = System.currentTimeMillis();
            this.runnable = r;
        }
    }
    
    
        ThreadPoolExecutor executor = new ThreadPoolExecutor(4, 16, 1, TimeUnit.MINUTES, new BlockingQueue<Runnable>() {
    
            private Comparator          comparator      = new Comparator<Pair>() {
    
                                                    @Override
                                                    public int compare(Pair arg0, Pair arg1) {
                                                        Long t1 = arg0.timestamp;
                                                        Long t2 = arg1.timestamp;
                                                        // compare in reverse to get oldest first. Could also do
                                                        // -t1.compareTo(t2);
                                                        return t2.compareTo(t1);
                                                    }
                                                };
    
            private PriorityBlockingQueue<Pair> backingQueue    = new PriorityBlockingQueue<Pair>(11, comparator);
    
            @Override
            public boolean add(Runnable r) {
                return backingQueue.add(new Pair(r));
            }
    
            @Override
            public boolean offer(Runnable r) {
                return backingQueue.offer(new Pair(r));
            }
    
            @Override
            public boolean offer(Runnable r, long timeout, TimeUnit unit) {
                return backingQueue.offer(new Pair(r), timeout, unit);
            }
    
            // implement / delegate rest of methods to the backing queue
        });
    
    0 讨论(0)
  • 2020-12-24 10:08

    I had the same requirements: Lazy loading and LIFO for a better user experience. So I have used the ThreadPoolExecutor with a wrapped BlockingQueue (like mentioned before).

    For easy backward compatibility I decided to go the easy way and for older devices I am simply using a fixed thread pool - wich means FIFO ordering. That's not perfect but for the first try okay. This looks like:

            try {
                sWorkQueue = new BlockingLifoQueue<Runnable>();
                sExecutor = (ThreadPoolExecutor) Class.forName("java.util.concurrent.ThreadPoolExecutor").getConstructor(int.class, int.class, long.class, TimeUnit.class, BlockingQueue.class).newInstance(3, DEFAULT_POOL_SIZE, 10, TimeUnit.MINUTES, sWorkQueue);
    
                if (BuildConfig.DEBUG) Log.d(LOG_TAG, "Thread pool with LIFO working queue created");
            } catch (Exception e) {
                if (BuildConfig.DEBUG) Log.d(LOG_TAG, "LIFO working queues are not available. Using default fixed thread pool");
    
                sExecutor = (ThreadPoolExecutor) Executors.newFixedThreadPool(DEFAULT_POOL_SIZE);
            }
    
    0 讨论(0)
  • 2020-12-24 10:18

    You can do it in two or three simple steps:

    1. Create a LifoBlockingDeque class:

      public class LifoBlockingDeque <E> extends LinkedBlockingDeque<E> {
      
      @Override
      public boolean offer(E e) { 
          // Override to put objects at the front of the list
          return super.offerFirst(e);
      }
      
      @Override
      public boolean offer(E e,long timeout, TimeUnit unit) throws InterruptedException { 
          // Override to put objects at the front of the list
          return super.offerFirst(e,timeout, unit);
      }
      
      
      @Override
      public boolean add(E e) { 
          // Override to put objects at the front of the list
          return super.offerFirst(e);
      }
      
      @Override
      public void put(E e) throws InterruptedException { 
          //Override to put objects at the front of the list
          super.putFirst(e);
          }
      }
      
    2. Create the executor:

      mThreadPool = new ThreadPoolExecutor(THREAD_POOL_SIZE, 
                                           THREAD_POOL_SIZE, 0L, 
                                           TimeUnit.MILLISECONDS, 
                                           new LifoBlockingDeque<Runnable>());
      
    3. LinkedBlockingDeque is supported only from API Level 9. To use it on earlier versions do the following:

      Use the Java 1.6 implementation - download it from here.

      Then change

      implements BlockingDeque<E>
      

      to

      implements BlockingQueue<E>
      

      To make it compile on Android. BlockingDeque is subtype of BlockingQueue, so no harm done.

    And you're done!

    0 讨论(0)
提交回复
热议问题