using Threads and Executors

We continue to analyze questions from Java certification from the IBS Training Center together with Igor Sudakevich, a certified Java developer, an authorized instructor of Oracle Corporation and the Udemy platform, a Java methodologist with 15 years of experience. In this article, we’ll take a look at using Threads and Executors and help you prepare for testing.

Let’s say the question is about creating threads (also known as “subprocesses” threads) based on Runnable and Callable interfaces. In addition, it is planned that the ExecutorService object will control the execution of tasks in multithreaded mode. So:

Given:

pubic class Logger implements Runnable {
	String msg;
	public Logger(String msg) {
    	this.msg = msg;
	}
	@Override
	public void run() {
    	System.out.print(msg);
	}
}

Let also the following code fragment be given:

Stream<Logger> s = Stream.of(
	new Logger("Severe "),
	new Logger("Warning "),
	new Logger("Info "));
ExecutorService es = Executors.newCachedThreadPool();
s.sequential().forEach(lgr -> es.execute(lgr));     	
es.shutdown();
es.awaitTermination(15, TimeUnit.SECONDS);

What is the result? Choose two answers:

A. Severe Info Warning

B. Severe Warning Info

C. Severe Severe Info

D. Severe Info

Even though there are no import directives in the question, this does not mean that the proposed code will not compile. And if there are no indications of data type imports in the text of the question, this means that all the necessary imports are in place and the code is successfully compiled and run. Another thing is that exceptions can be thrown at the execution stage, but our example should work properly.

For the Advanced level exam, subtopics from the section Concurrency, concerning the Executors utility class and the ExecutorService interface. In addition, the exam is actively interested in thread pools that implement the ExecutorService interface.

In the early versions of Java, the programmer was responsible for creating and managing the life cycle of threads. Because creation thread‘ov is limited by the resources of the operating system, the concept of a pool of threads (“thread pool“”) to set thread’ov could serve a lot more executable background tasks. In other words, instead of creating a separate thread for each task and then destroying it, you can create several thread’ov and configure them so that they can accept and execute “orders” (for example, Runnable objects). A thread that suspends such an “order” will execute it and, upon completion, return to the pool, where it can be assigned the next “order”, and so on.

Ready solutions for thread-pools appeared in the Java API with the release of the “five”. The Executor and ExecutorService interfaces are a generalization threadpools and supported types of interactions. In addition, a number of ExecutorService implementations can be instantiated using the Executors class, which contains a set of appropriate factory methods. These three data types are located in the java.util.concurrent package, along with other high-level classes and interfaces to help the developer with multi-threaded programming problems.

While the base interface Executor is designed to execute tasks described in objects that are implementers of the Runnable interface, in practice we often use ExecutorService, which is a child interface of Executor, since ExecutorService gives us the additional ability to execute Callable tasks and stop accepting tasks for the entire pool. Recall that the Callable interface assumes the return of a certain result, which will be asynchronously received by the subprocess that initiates the execution of the task.

It is interesting to note that neither Executor nor ExecutorService prescribes a specific strategy for executing tasks. Some implementations are multi-threaded in a fixed-size pool, sometimes leading to queues of tasks waiting for the next one to finally free up. thread. Other implementations are able to independently create worker threads as the load increases and even “clean up after themselves” when the need for additional thread’ah disappears. There is also an implementation where tasks are executed strictly sequentially, since only one is allocated for everything about everything thread. Which strategy to take depends on the specific application area, so the developer must analyze the architecture and needs of the application.

The above strategies are implemented in the following factory methods of the Executors utility class:

● newFixedThreadPool(int nThreads)

● newCachedThreadPool()

● newSingleThreadExecutor()

The first two create pools with several worker threads, the newSingleThreadExecutor() method returns a service that processes tasks strictly in turn in a single thread’e.

We see that in our example, a caching, dynamic pool is involved. This kind of executor spawns new workers thread’s and destroys them when the thread remains idle for 60 seconds. Such a pool also has a drawback: there is no ceiling on the number of thread’ov. This can cause excessive resource consumption and, as a result, degradation of application performance under high load.

We have a number of tasks to calculate, and there is reason to expect that the pool from our question will contain several thread’s in multithreaded mode. That is why there is no guarantee that the task will be counted first in a row, second, and so on, regardless of the order in which they are launched for execution. In other words, there is no way to tell in advance in what order messages will be printed to the console. It follows that the correct answers are options A and B.

Note that the ExecutorService executes the assigned task once. We cannot rule out that the execution will end abnormally or the task will not be processed at all, because the pool will be closed by this time. However, the task is calculated only once. We won’t see duplicate messages, so option C is wrong.

Now more about the shutdown() method. After its call, the ExecutorService will not accept new orders, but the running tasks will be counted to the end. From this we get that if we assigned three tasks to the pool and then called shutdown (), in the event of a regular shutdown, all three messages will appear in the console. Since option D lists only two messages and does not mention any exception being thrown, we conclude that option D is also wrong.

But what if some task is calculated so long that we will fly out of the timeout boundary in 15 seconds after the request to close the pool? After all, in this case, we will not see all three messages, right?

In this case, firstly, it is generally extremely unlikely that a simple task to print a message to the console will take such a long time. Secondly, the question asked for two options, and options A and B look absolutely natural, do not require any exaggerations, reservations and exotic corner cases with microscopically small chances for implementation.

We can refer to such a scenario when the kernel of the operating system on the server terminates the operation of the virtual machine due to the urgent need to install some update or security patch in the face of a desperate shortage of resources, but in our exam all these circumstances would be described explicitly. Since any flow thread-pool is non-daemonic, the virtual machine cannot voluntarily abort its execution forcibly. That’s why the program must run to the end and print all three messages to the console, even if in an unpredictable order.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *