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.
CodePudding user response:
For adding tasks to threads and replacing them you can use ExecutorService. You can create it by using:
ExecutorService executor = Executors.newFixedThreadPool(10);
CodePudding user response:
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()
.