简体   繁体   中英

I want to stop a group of completed threads, wait for uncompleted threads

I need a group of threads to run at the same time and then another group of threads after that. for example, 10 threads start working, and then 10 or 15 other threads. of course the first approach I tried was to make loop.

while (true) {
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(
                    new Runnable() {
                        @Override
                        public void run() {
                            System.out.println("hi");
                        }
                    });
            thread.start();
        }
    }

but the problem is when scenario like this happens: imagine if in first iteration, 8 threads finished their tasks, and 2 threads take longer time. the next 10 threads wont start until all 8 + 2 (completed and not completed) threads finish. while I want an approach where 8 threads get replaced by 8 of waiting to start threads.

For adding tasks to threads and replacing them you can use ExecutorService . You can create it by using:

ExecutorService executor = Executors.newFixedThreadPool(10);

Bare Threads

It can be done using bare Thread and Runnable without diving into more advance technologies.

For that, you need to perform the following steps:

  • define your task (provide an implementation of the Runnable interface);
  • generate a collection of Threads creating based on this task);
  • start every thread;
  • invoke join() on every of these thread ( note that firstly we need to start all threads).

That's how it might look like:

public static void main(String[] args) throws InterruptedException {
    Runnable task = () -> System.out.println("hi");
    
    int counter = 0;
    
    while (true) {
        System.out.println("iteration: " + counter++);

        List<Thread> threads = new ArrayList<>();
        
        for (int i = 0; i < 10; i++) {
            threads.add(new Thread(task));
        }

        for (Thread thread : threads) {
            thread.start();
        }

        for (Thread thread : threads) {
            thread.join();
        }

        Thread.currentThread().sleep(1000);
    }
}

Instead of managing your Threads manually, it definitely would be wise to look at the facilities provided by the implementations of the ExecutorService interfaces.

Things would be a bit earthier if you use Callable interface for your task instead of Runnable . Callable is more handy in many cases because it allows obtaining the result from the worker-thread and also propagating an exception if thing went wrong (as opposed run() would force you to catch every checked exception). If you have in mind something more interesting than printing a dummy message, you might find Callable to be useful for your purpose.

ExecutorService.invokeAll() + Callable

ExecutorService has a blocking method invokeAll() which expects a collection of the callable-tasks and return a list of completed Future objects when all the tasks are done.

To generate a light-weight collection of repeated elements (since we need to fire a bunch of identical tasks) we can use utility method Collections.nCopies() .

Here's a sample code which repeatedly runs a dummy task:

ExecutorService executor = Executors.newWorkStealingPool();

while (true) {
    executor.invokeAll(Collections.nCopies(10, () -> {
        System.out.println("hi");
        return true;
    }));
}

To make sure that it does what expected, we can add a counter of iterations and display it on the console and Thread.currentThread().sleep() to avoid cluttering the output very fast (for the same reason, the number of tasks reduced to 3 ):

public static void main(String[] args) throws InterruptedException {
    ExecutorService executor = Executors.newWorkStealingPool();

    int counter = 0;
    
    while (true) {
        System.out.println("iteration: " + counter++);
        
        executor.invokeAll(Collections.nCopies(3, () -> {
            System.out.println("hi");
            return true;
        }));
        
        Thread.currentThread().sleep(1000);
    }
}

Output:

iteration: 0
hi
hi
hi
iteration: 1
hi
hi
hi
... etc.

CompletableFuture.allOf().join() + Runnable

Another possibility is to use CompletableFuture API, and it's method allOf() which expects a varargs of submitted tasks in the form CompletableFuture and return a single CompletableFuture which would be completed when all provided arguments are done.

In order to synchronize the execution of the tasks with the main thread, we need to invoke join() on the resulting CompletableFuture instance.

That's how it might be implemented:

public static void main(String[] args) throws InterruptedException {
    ExecutorService executor = Executors.newWorkStealingPool();
    Runnable task = () -> System.out.println("hi");

    int counter = 0;
    
    while (true) {
        System.out.println("iteration: " + counter++);

        CompletableFuture.allOf(
            Stream.generate(() -> task)
                .limit(3)
                .map(t -> CompletableFuture.runAsync(t, executor))
                .toArray(CompletableFuture<?>[]::new)
        ).join();
        
        Thread.currentThread().sleep(1000);
    }
}

Output:

iteration: 0
hi
hi
hi
iteration: 1
hi
hi
hi
... etc.

ScheduledExecutorService

I suspect you might interested in scheduling these tasks instead of running them reputedly. If that's the case, have a look at ScheduledExecutorService and it's methods scheduleAtFixedRate() and scheduleWithFixedDelay() .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM