Be sure of using Fork/Join common pool; they are daemon threads

Posted on by

Categories:         

There are two kinds of threads in java-

The user threads are priority threads. The user thread dies when it completes its work. So if not terminated by any external means, the thread will live until it finishes its work. On the other hand, daemon threads are created from a user thread and serve the user thread. So a daemon thread may die without completing its work if the user thread dies from which it was created.

Java program starts from the main method, and the main method starts with the main Thread. Let’s see an example-

package com.bazlur;

import java.util.concurrent.TimeUnit;

public class Day024_Playground {
	public static void main(String[] args) {
		var thread = new Thread(() -> {
			while (true) {
				//this thread is supposed to be running forever
				sleep(1);
				System.out.println(Thread.currentThread().getName()
						+ " is daemon? "
						+ Thread.currentThread().isDaemon());
			}
		});

		thread.setDaemon(true);
		thread.start();

		System.out.println("Running from main method");
		sleep(5);
		System.out.println("Done! will die now");
	}

	private static void sleep(int second) {
		try {
			TimeUnit.SECONDS.sleep(second);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

In the above code, we have created a new thread from the main method. The Thread is created from a user thread, which is basically the main Thread. Inside the Thread, we have an infinite loop. This means this Thread will keep running forever until it is stopped by an external means.
However, we marked it as a daemon thread. That means this it will keep running as long as the main Thread is alive.

The main Thread creates this new Thread, sleeps, then prints a statement and then dies. So immediately, the daemon thread will die.

If you run the above code, you will get the following output-

main method is running from Thread [main,5,main]
Running from main method
Thread-0 is daemon? true
Thread-0 is daemon? true
Thread-0 is daemon? true
Thread-0 is daemon? true
Done! will die now

Nothing will print will anymore.

We have learned about daemon thread so far. Let’s learn something else now-

Java has a new construct called CompletableFuture. Using it, we can execute a piece of code asynchronously.

There are a few factory methods in it which we can use easily. For example- runAsync(), supplyAsync() . Example-

	CompletableFuture<?> future  = CompletableFuture.runAsync(() -> {
			System.out.println("Running asynchronously!");
		});

Or in case we need a value from it -

CompletableFuture<Integer> meaningOfLife = CompletableFuture.supplyAsync(() -> {
    return 42;
});

Now the question is, under which Thread these methods get executed?

The answer is, they get executed by Fork/Join Common pool.

If you run the following code in your main method-

package com.bazlur;

import java.util.concurrent.CompletableFuture;

public class Day024_Playground2 {
	public static void main(String[] args) {

		CompletableFuture.runAsync(() -> {
			System.out.println("Running asynchronously!");
			System.out.println("from " + Thread.currentThread());
		});

	}
}

You should get the following-

Running asynchronously!
from Thread[ForkJoinPool.commonPool-worker-19,5,main]

However, if your program has only the above code and nothing else in the main method, you may not get anything at all. Nothing will print in the main method.

Seems puzzling, right?

To able to get a result, either you have to call a get() on the CompletableFuture as bellow-

package com.bazlur;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class Day024_Playground2 {
	public static void main(String[] args) throws ExecutionException, InterruptedException {

		CompletableFuture.runAsync(() -> {
			System.out.println("Running asynchronously!");
			System.out.println("from " + Thread.currentThread());
		}).get();
	}
}

The get() call is a blocking call, which means the main Thread will block here, and nothing from the main Thread will progress until the asynchronous block finishes.

If we have some other code in the main Thread, they will wait until this asynchronous block finishes its job, which in turn diminishes the asynchronicity.

Or, the main Thread lives longer than the asynchronous block. In that case we will defiantly get the result as expected.

package com.bazlur;

import java.util.concurrent.CompletableFuture;

public class Day024_Playground2 {
	public static void main(String[] args) throws InterruptedException {

		CompletableFuture.runAsync(() -> {
			System.out.println("Running asynchronously!");
			System.out.println("from " + Thread.currentThread());
		});

		System.out.println("Doing some important work!");
		Thread.sleep(1000);
	}
}

Now if we run the above code, we will get the result. The reason is, the main Thread lives longer than the CompletableFuture. Assuming the CompletableFuture won’t take longer than 1000 milliseconds.

From the above discussion, we can conclude that the ForkJoinPool.commonPool holds the daemon threads. If they were user threads, they would finish their job; it wouldn’t matter if the main Thread finishes it works or not.

Let’s see if this is the case or not?


package com.bazlur;

import java.util.concurrent.CompletableFuture;

public class Day024_Playground2 {
	public static void main(String[] args) throws InterruptedException {

		CompletableFuture.runAsync(() -> {
			System.out.println("Running asynchronously!");
			System.out.println("from " + Thread.currentThread());
			System.out.println("Is it a daemon thread? "+ Thread.currentThread().isDaemon());
		});

		System.out.println("Doing some important work!");
		Thread.sleep(1000);
	}
}

The answer is an astounding yes. They are indeed daemon thread.

So what have we learned from this entire discussion?

If our main Thread doesn’t live longer than the daemon thread, we won’t get any result from the CompletableFuture. This is because they will not live to finish their work.

What can we do if we have a case where multiple CompletableFuture do some asynchronous jobs, but the user thread from which they are created dies early?

Instead of using the ForkJoin common pool, my solution would be to provide our own thread pool. We have executors.

package com.bazlur;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;

public class Day024_Playground2 {
	public static void main(String[] args) throws InterruptedException {

		var threadPool = Executors.newCachedThreadPool();

		CompletableFuture.runAsync(() -> {
			System.out.println("Running asynchronously!");
			sleep();
			System.out.println("from " + Thread.currentThread());
		}, threadPool);

		System.out.println("I'm Done!! going to die.");
		threadPool.shutdown();
	}

	private static void sleep() {
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

In the above code, even the user thread dies, it won’t affect the CompletableFuture; it will finish its job and then it would die.

If we need such assurance, we should always provide executors to the CompletableFuture.

That’s for today! Cheers!

             

Share on:

Author: A N M Bazlur Rahman

Java Champion | Software Engineer | JUG Leader | Book Author | InfoQ & Foojay.IO Editor | Jakarta EE Ambassadors| Helping Java Developers to improve their coding & collaboration skills so that they can meet great people & collaborate

100daysofcode 100daysofjava access advance-java agile algorithm arraylist article bangla-book becoming-expert biginteger book calculator checked checked-exceptions cloning code-readability code-review coding coding-convention collection-framework compact-strings completablefuture concatenation concurrency concurrentmodificationexception concurrentskiplistmap counting countingcollections critical-section daemon-thread data-race data-structure datetime day002 deliberate-practice deserialization design-pattern developers duration execute-around executors export fibonacci file file-copy fork/join-common-pool functional future-java-developers groupby hash-function hashmap history history-of-java how-java-performs-better how-java-works http-client image import inspiration io itext-pdf java java-10 java-11 java-17 java-8 java-9 java-developers java-performance java-programming java-thread java-thread-programming java11 java16 java8 lambda-expression learning learning-and-development linkedlist list local-type-inference localdatetime map methodology microservices nio non-blockingio null-pointer-exception object-cloning optional packaging parallel pass-by-reference pass-by-value pdf performance prime-number programming project-loom race-condition readable-code record refactoring review scheduler scrum serialization serversocket simple-calculator socket software-development softwarearchitecture softwareengineering sorting source-code stack string string-pool stringbuilder swing thread threads tutorial unchecked vector virtual-thread volatile why-java zoneid