Java ExecutorService: awaitTermination of all recursively created tasks

后端 未结 11 2087
太阳男子
太阳男子 2020-11-29 03:27

I use an ExecutorService to execute a task. This task can recursively create other tasks which are submitted to the same ExecutorService and those

相关标签:
11条回答
  • 2020-11-29 03:50

    You could create your own thread pool which extends ThreadPoolExecutor. You want to know when a task has been submitted and when it completes.

    public class MyThreadPoolExecutor extends ThreadPoolExecutor {
        private int counter = 0;
    
        public MyThreadPoolExecutor() {
            super(1, 1, 0, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
        }
    
        @Override
        public synchronized void execute(Runnable command) {
            counter++;
            super.execute(command);
        }
    
        @Override
        protected synchronized void afterExecute(Runnable r, Throwable t) {
            super.afterExecute(r, t);
            counter--;
            notifyAll();
        }
    
        public synchronized void waitForExecuted() throws InterruptedException {
            while (counter == 0)
                wait();
        }
    }
    
    0 讨论(0)
  • 2020-11-29 03:54

    Wow, you guys are quick:)

    Thank you for all the suggestions. Futures don't easily integrate with my model because I don't know how many runnables are scheduled beforehand. So if I keep a parent task alive just to wait for it's recursive child tasks to finish I have a lot of garbage laying around.

    I solved my problem using the AtomicInteger suggestion. Essentially, I subclassed ThreadPoolExecutor and increment the counter on calls to execute() and decrement on calls to afterExecute(). When the counter gets 0 I call shutdown(). This seems to work for my problems, not sure if that's a generally good way to do that. Especially, I assume that you only use execute() to add Runnables.

    As a side node: I first tried to check in afterExecute() the number of Runnables in the queue and the number of workers that are active and shutdown when those are 0; but that didn't work because not all Runnables showed up in the queue and the getActiveCount() didn't do what I expected either.

    Anyhow, here's my solution: (if anybody finds serious problems with this, please let me know:)

    public class MyThreadPoolExecutor extends ThreadPoolExecutor {
    
        private final AtomicInteger executing = new AtomicInteger(0);
    
        public MyThreadPoolExecutor(int coorPoolSize, int maxPoolSize, long keepAliveTime,
            TimeUnit seconds, BlockingQueue<Runnable> queue) {
            super(coorPoolSize, maxPoolSize, keepAliveTime, seconds, queue);
        }
    
    
        @Override
        public void execute(Runnable command) {
            //intercepting beforeExecute is too late!
            //execute() is called in the parent thread before it terminates
            executing.incrementAndGet();
            super.execute(command);
        }
    
    
        @Override
        protected void afterExecute(Runnable r, Throwable t) {
            super.afterExecute(r, t);
            int count = executing.decrementAndGet();
            if(count == 0) {
                this.shutdown();
            }
        }
    
    }
    
    0 讨论(0)
  • 2020-11-29 03:59

    (mea culpa: its a 'bit' past my bedtime ;) but here's a first attempt at a dynamic latch):

    package oss.alphazero.sto4958330;
    
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.AbstractQueuedSynchronizer;
    
    public class DynamicCountDownLatch {
        @SuppressWarnings("serial")
        private static final class Sync extends AbstractQueuedSynchronizer {
            private final CountDownLatch toplatch;
            public Sync() {
                setState(0);
                this.toplatch = new CountDownLatch(1);
            }
    
            @Override
            protected int tryAcquireShared(int acquires){
                try {
                    toplatch.await();
                } 
                catch (InterruptedException e) {
                    throw new RuntimeException("Interrupted", e);
                }
                return getState() == 0 ? 1 : -1;
            }
            public boolean tryReleaseShared(int releases) {
                for (;;) {
                    int c = getState();
                    if (c == 0)
                        return false;
                    int nextc = c-1;
                    if (compareAndSetState(c, nextc)) 
                        return nextc == 0;
                }
            }
            public boolean tryExtendState(int acquires) {
                for (;;) {
                    int s = getState();
                    int exts = s+1;
                    if (compareAndSetState(s, exts)) {
                        toplatch.countDown();
                        return exts > 0;
                    }
                }
            }
        }
        private final Sync sync;
        public DynamicCountDownLatch(){
            this.sync = new Sync();
        }
        public void await() 
            throws InterruptedException   
        {
            sync.acquireSharedInterruptibly(1);
        }
    
        public boolean await(long timeout, TimeUnit   unit) 
            throws InterruptedException   
        {
            return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
        }
        public void countDown() {
            sync.releaseShared(1);
        }
        public void join() {
            sync.tryExtendState(1);
        }
    }
    

    This latch introduces a new method join() to the existing (cloned) CountDownLatch API, which is used by tasks to signal their entry into the larger task group.

    The latch is pass around from parent Task to child Task. Each task would, per Suraj's pattern, first 'join()' the latch, do its task(), and then countDown().

    To address situations where the main thread launches the task group and then immediately awaits() -- before any of the task threads have had a chance to even join() -- the topLatch is used int inner Sync class. This is a latch that will get counted down on each join(); only the first countdown is of course significant, as all subsequent ones are nops.

    The initial implementation above does introduce a semantic wrinkle of sorts since the tryAcquiredShared(int) is not supposed to be throwing an InterruptedException but then we do need to deal with the interrupt on the wait on the topLatch.

    Is this an improvement over OP's own solution using Atomic counters? I would say probably not IFF he is insistent upon using Executors, but it is, I believe, an equally valid alternative approach using the AQS in that case, and, is usable with generic threads as well.

    Crit away fellow hackers.

    0 讨论(0)
  • 2020-11-29 03:59

    The only inelegant solution I could come up with is to directly use a ThreadPoolExecutor and query its getPoolSize() every once in a while. Is there really no better way do do that?

    You have to use shutdown() ,awaitTermination()and shutdownNow() methods in a proper sequence.

    shutdown(): Initiates an orderly shutdown in which previously submitted tasks are executed, but no new tasks will be accepted.

    awaitTermination():Blocks until all tasks have completed execution after a shutdown request, or the timeout occurs, or the current thread is interrupted, whichever happens first.

    shutdownNow(): Attempts to stop all actively executing tasks, halts the processing of waiting tasks, and returns a list of the tasks that were awaiting execution.

    Recommended way from oracle documentation page of ExecutorService:

     void shutdownAndAwaitTermination(ExecutorService pool) {
       pool.shutdown(); // Disable new tasks from being submitted
       try {
         // Wait a while for existing tasks to terminate
         if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
           pool.shutdownNow(); // Cancel currently executing tasks
           // Wait a while for tasks to respond to being cancelled
           if (!pool.awaitTermination(60, TimeUnit.SECONDS))
               System.err.println("Pool did not terminate");
         }
       } catch (InterruptedException ie) {
         // (Re-)Cancel if current thread also interrupted
         pool.shutdownNow();
         // Preserve interrupt status
         Thread.currentThread().interrupt();
       }
    

    You can replace if condition with while condition in case of long duration in completion of tasks as below:

    Change

    if (!pool.awaitTermination(60, TimeUnit.SECONDS))
    

    To

     while(!pool.awaitTermination(60, TimeUnit.SECONDS)) {
         Thread.sleep(60000);
     }  
    

    You can refer to other alternatives (except join(), which can be used with standalone thread ) in :

    wait until all threads finish their work in java

    0 讨论(0)
  • 2020-11-29 04:02

    If you want to use JSR166y classes - e.g. Phaser or Fork/Join - either of which might work for you, you can always download the Java 6 backport of them from: http://gee.cs.oswego.edu/dl/concurrency-interest/ and use that as a basis rather than writing a completely homebrew solution. Then when 7 comes out you can just drop the dependency on the backport and change a few package names.

    (Full disclosure: We've been using the LinkedTransferQueue in prod for a while now. No issues)

    0 讨论(0)
  • 2020-11-29 04:02

    I must say, that solutions described above of problem with recursive calling task and wait for end suborder tasks doesn't satisfy me. There is my solution inspired by original documentation from Oracle there: CountDownLatch and example there: Human resources CountDownLatch.

    The first common thread in process in instance of class HRManagerCompact has waiting latch for two daughter's threads, wich has waiting latches for their subsequent 2 daughter's threads... etc.

    Of course, latch can be set on the different value than 2 (in constructor of CountDownLatch), as well as the number of runnable objects can be established in iteration i.e. ArrayList, but it must correspond (number of count downs must be equal the parameter in CountDownLatch constructor).

    Be careful, the number of latches increases exponentially according restriction condition: 'level.get() < 2', as well as the number of objects. 1, 2, 4, 8, 16... and latches 0, 1, 2, 4... As you can see, for four levels (level.get() < 4) there will be 15 waiting threads and 7 latches in the time, when peak 16 threads are running.

    package processes.countdownlatch.hr;
    
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.atomic.AtomicInteger;
    import java.util.concurrent.atomic.AtomicLong;
    
    /** Recursively latching running classes to wait for the peak threads
     *
     * @author hariprasad
     */
    public class HRManagerCompact extends Thread {
      final int N = 2; // number of daughter's tasks for latch
      CountDownLatch countDownLatch;
      CountDownLatch originCountDownLatch;
      AtomicInteger level = new AtomicInteger(0);
      AtomicLong order = new AtomicLong(0); // id latched thread waiting for
    
      HRManagerCompact techLead1 = null;
      HRManagerCompact techLead2 = null;
      HRManagerCompact techLead3 = null;
    
    // constructor
    public HRManagerCompact(CountDownLatch countDownLatch, String name,
        AtomicInteger level, AtomicLong order){
      super(name);
      this.originCountDownLatch=countDownLatch;
      this.level = level;
      this.order = order;
     }
    
     private void doIt() {
        countDownLatch = new CountDownLatch(N);
        AtomicInteger leveli = new AtomicInteger(level.get() + 1);
        AtomicLong orderi = new AtomicLong(Thread.currentThread().getId());
        techLead1 = new HRManagerCompact(countDownLatch, "first", leveli, orderi);
        techLead2 = new HRManagerCompact(countDownLatch, "second", leveli, orderi);
        //techLead3 = new HRManagerCompact(countDownLatch, "third", leveli);
    
        techLead1.start();
        techLead2.start();
        //techLead3.start();
    
        try {
         synchronized (Thread.currentThread()) { // to prevent print and latch in the same thread
           System.out.println("*** HR Manager waiting for recruitment to complete... " + level + ", " + order + ", " + orderi);
           countDownLatch.await(); // wait actual thread
         }
         System.out.println("*** Distribute Offer Letter, it means finished. " + level + ", " + order + ", " + orderi);
        } catch (InterruptedException e) {
         e.printStackTrace();
        }
      }
    
     @Override
     public void run() {
      try {
       System.out.println(Thread.currentThread().getName() + ": working... " + level + ", " + order + ", " + Thread.currentThread().getId());
       Thread.sleep(10*level.intValue());
       if (level.get() < 2) doIt();
       Thread.yield();
      }
      catch (Exception e) {
       // TODO Auto-generated catch block
       e.printStackTrace();
      }
      /*catch (InterruptedException e) {
       // TODO Auto-generated catch block
       e.printStackTrace();
      }*/
      // TODO Auto-generated method stub
      System.out.println("--- " +Thread.currentThread().getName() + ": recruted " + level + ", " + order + ", " + Thread.currentThread().getId());
      originCountDownLatch.countDown(); // count down
     }
    
     public static void main(String args[]){
      AtomicInteger levelzero = new AtomicInteger(0);
      HRManagerCompact hr = new HRManagerCompact(null, "zero", levelzero, new AtomicLong(levelzero.longValue()));
      hr.doIt();
     }
    }
    

    Possible commented output (with some probability):

    first: working... 1, 1, 10 // thread 1, first daughter's task (10)
    second: working... 1, 1, 11 // thread 1, second daughter's task (11)
    first: working... 2, 10, 12 // thread 10, first daughter's task (12)
    first: working... 2, 11, 14 // thread 11, first daughter's task (14)
    second: working... 2, 11, 15 // thread 11, second daughter's task (15)
    second: working... 2, 10, 13 // thread 10, second daughter's task (13)
    --- first: recruted 2, 10, 12 // finished 12
    --- first: recruted 2, 11, 14 // finished 14
    --- second: recruted 2, 10, 13  // finished 13 (now can be opened latch 10)
    --- second: recruted 2, 11, 15  // finished 15 (now can be opened latch 11)
    *** HR Manager waiting for recruitment to complete... 0, 0, 1
    *** HR Manager waiting for recruitment to complete... 1, 1, 10
    *** Distribute Offer Letter, it means finished. 1, 1, 10 // latch on 10 opened
    --- first: recruted 1, 1, 10 // finished 10
    *** HR Manager waiting for recruitment to complete... 1, 1, 11
    *** Distribute Offer Letter, it means finished. 1, 1, 11 // latch on 11 opened
    --- second: recruted 1, 1, 11  // finished 11 (now can be opened latch 1)
    *** Distribute Offer Letter, it means finished. 0, 0, 1  // latch on 1 opened
    
    0 讨论(0)
提交回复
热议问题