Executioner
The question should be about the ExecutorService
or just Executor
. Such Apis are an abstraction to manual thread control for executing a set of asynchronous operations.
Therefore, the first goal here is to make asynchronous development more user-friendly and less error-prone without the need for manual synchronization.
Thread pool
A specific type of Executor
is the one who possesses a thread pool (ThreadPoolExecutor
).
A thread pool which is nothing more than a type of reservoir of threads, waiting for a job to be done. Remembering that a thread in Java is represented by an instance of the class Thread
.
Creating an executor with pool fixed-size
In your example, you used the static method Executors.newFixedThreadPool(10)
- one Factory method - to create a class instance ThreadPoolExecutor
, with a specific behaviour.
Basically:
- The pool begins empty.
- Each task submitted, using the
submit()
, creates a new thread in pool to perform the task, up to the limit of 10 threads.
- After reaching the limit of 10 threads in the pool, each new task submitted will be executed by an existing thread, if there are any free ones, otherwise the task will get in a queue, which is consumed as soon as some other task is over.
As I mentioned, there are different types of Executor
s for different scenarios. Also, using different parameters to instantiate a ThreadPoolExecutor
, you can add different upper limits in the number of threads, for example a pool that is has a minimum of 10 threads, but can reach 20 if there are many tasks in the queue.
Performance considerations
Use a Executor
with a thread pool potentially improves performance if there is a reasonable amount of asynchronous tasks to be performed.
The main reason is related to the best use of the available Cpus. For example, creating 1000 threads and running them at the same time on a CPU with 8 processors will potentially generate too much overhead for the scheduling algorithm, consuming too much memory at once, and, if there is synchronized access to a shared object, will generate huge containment and multiple threads will be locked at the same time. In this scenario, it is usually more efficient to allow a limited number of simultaneous tasks. The optimal thread limit is totally dependent on the environment and can only be determined through experiments.
Another reason is related to the creation of threads. If the number of tasks is large and the duration of each is short, then reusing threads can help a little. However, this is not a great argument, because it still exists overhead for the management of threads in the pool and some synchronization types that are required.
On the other hand, before dividing a large task into small ones, it is always necessary to consider whether there is a real benefit to it, whether each task is really individual.
In his example, if the creation of clients is, for example, an access to a remote service whose bottleneck is the response time and not the amount of data, it really makes sense to parallelize 10 requests. Now suppose the bottleneck is on the network, in this case parallelizing 10 requests will increase the total transfer time, increase the possibility of a timeout and the chances of any network failure.
Another common error is parallelizing access to a database. Suppose in the example 10 parallel connections are opened, each inserting a record into the database. In addition to expending unnecessary resources by decreasing the server’s total capacity in number of users, this is probably no faster than mass input of the data. The reasons involve an overhead of opening connections and the fact that in writing operations the bank still needs to synchronize the recordings.
Other uses
The use of Executor
s is generally recommended in Java for the reasons cited at the beginning, namely:
- Facilitates and speeds development, as it is a simpler API compared to manual synchronization of threads.
- Avoid mistakes, as it abstract many of the difficult concepts of competition.
Therefore, even when performance is somewhat impaired, the sanity of programmers is reason enough to use the API.
Correct way to finish tasks
The example of the question uses a very bad technique to wait for the completion of the submitted tasks.
The noose while (!threadPool.isTerminated()) { }
will run continuously, occupying a CPU to check the completion of tasks.
Using a thread lock mechanism to wait for the operation to end will release the current thread to perform other more important things while the code does nothing useful. On a 4-color free CPU, this can represent a 25% difference in program efficiency.
Let’s go to an improved example:
//nome apropriado para a variável
final ExecutorService criarClientesExecutor = Executors.newFixedThreadPool(10);
//sempre use chaves, é uma boa prática para não errar na leitura do código
for (int i = 0; i < 10; i++) {
criarClientesExecutor.submit(new CriarClientes(NOMES[i]));
}
criarClientesExecutor.shutdown();
//aguarda a conclusão sem ocupar o processador
criarClientesExecutor.awaitTermination(10, TimeUnit.SECONDS);
ClienteRepository.instance().list();
Beware of some methods like shutdownNow()
and tryTerminate()
, as they will activate the threads interrupt flag. It means that if a threads is blocked by a lock or doing some operation on disk, database, network, an exception will be launched and the operation will not be completed.
Collecting results individually
In the example above, the executioner and its threads are recreated with each execution of the passage. However, another common scenario is to maintain a executioner shared, and places submitting a task usually want the individual result of it.
In this case, just use the return of one of the methods submit()
which is a Future
. Example:
//submete a tarefa
final Future<Integer> future = executor.submit(task);
//executa algo em paralelo ...
//coleta o resultado aqui - ou aguarda se ainda não houver acabado
Integer resultado = future.get();
Thank you, then the way it is being used is merely for didactic purposes, I want to go deeper into the subject, I will try to implement.
– Felipe Paetzold