What is Threadpool all about?

Asked

Viewed 3,964 times

6

I’m studying Threads in Java, and came across the following code:

ExecutorService threadPool = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++)
    threadPool.submit(new CriarClientes(NOMES[i]));

    threadPool.shutdown();

    while (!threadPool.isTerminated()) {
    }

    ClienteRepository.instance().list();

I’d like to know what the ThreadPool, and why is there a inteiro for the builder. I heard that it creates the number of Threads which passes into the construtor but I did not understand very well where to apply in practice.

3 answers

10

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 Executors 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 Executors 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();

9


Is a set of threads ready for use. They are not usually released, and are available there (in Idle) for recycling. In fact the process of creation and destruction is a little more complicated than this and dependent on implementation, but the basic is this. Some will be destroyed if you have excess.

Create a thread costs a little expensive. Keeping track of all active is not such a simple task. Having an object that manages this helps a lot since it was done by those who understand the subject well, and with it makes it possible to use threads shorter execution, since the cost is minimized by not having to keep creating and destroying several threads.

In some cases it is possible to create an execution queue.

Specific example

In fact the newFixedThreadPool creates a pool with threads with 10 threads If you pass, get in line.

The way you are using is not a problem, but there is little or no gain compared to the normal use of threads. Maybe it will get more organized, but the performance will not be improved.

Completion

The design pattern of pool is widely used in various advanced programming cases to reuse objects that can be recycled.

More information on Wikipedia.

This can help: It’s always guaranteed that a multi-threaded application runs faster than using a single thread?

If you have specific questions, send it to us.

  • 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.

1

ThreadPool would be a collection of threads organized in a queue and running a list of tasks. The idea is to help you avoid creating or destroying more threads than you would really need. Once a task is completed, the active thread requests another task.

When you define, in the method newFixedThreadPool, integer 10, you are setting a list of at most 10 active threads. If more than 10 threads are submitted, they will be queued until other threads are terminated.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.