How do I create custom thread factories?

Creating a custom thread factory in Java is a powerful way to manage how threads are initialized. Instead of using the default factory, you can customize thread names (vital for debugging!), set priority levels, or even create daemon threads.

To do this, you need to implement the java.util.concurrent.ThreadFactory interface.

1. Implement the ThreadFactory Interface

The interface has a single method: newThread(Runnable r). Here is a clean, reusable example:

package org.kodejava.util.concurrent;

import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

public class CustomThreadFactory implements ThreadFactory {
    private final String namePrefix;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final boolean daemon;

    public CustomThreadFactory(String poolName, boolean daemon) {
        this.namePrefix = poolName + "-worker-";
        this.daemon = daemon;
    }

    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(r, namePrefix + threadNumber.getAndIncrement());
        t.setDaemon(daemon);
        t.setPriority(Thread.NORM_PRIORITY);
        return t;
    }
}

2. Use the Factory with an ExecutorService

Once you’ve defined your factory, you can pass it to any ThreadPoolExecutor or static Executors factory method. This ensures every thread created by that pool follows your rules.

package org.kodejava.util.concurrent;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadFactoryDemo {
    public static void main(String[] args) {
        // Create the factory
        CustomThreadFactory factory = new CustomThreadFactory("OrderProcessor", false);

        // Pass it to a Fixed Thread Pool
        ExecutorService executor = Executors.newFixedThreadPool(3, factory);

        for (int i = 0; i < 5; i++) {
            executor.submit(() -> {
                System.out.println("Running task in: " + Thread.currentThread().getName());
            });
        }

        executor.shutdown();
    }
}

Why use a custom factory?

  • Identifiability: In thread dumps or logs, seeing OrderProcessor-worker-1 is much more helpful than pool-1-thread-1.
  • Security & Cleanup: You can set setDaemon(true) for background cleanup tasks so they don’t prevent the JVM from shutting down.
  • Context: You can use the factory to inject ThreadLocal variables or set a custom UncaughtExceptionHandler for all threads in a pool.

How do I use ExecutorService.invokeAll?

Hello! ExecutorService.invokeAll is a powerful method when you have a collection of tasks and need to wait until every single one of them finishes before moving forward.

Here’s a breakdown of how it works and how to use it effectively.

What does invokeAll do?

  1. Executes a collection of tasks: It takes a Collection of Callable<T> objects.
  2. Blocks until completion: Unlike submit(), which returns immediately, invokeAll is blocking. It will not return until all tasks in the collection have completed (either normally or by throwing an exception).
  3. Returns a list of Futures: It returns a List<Future<T>> that holds the results (or exceptions) of the tasks, in the same order they were provided in the input collection.

Basic Usage Pattern

Here is a clean example of how to implement it:

package org.kodejava.util.concurrent;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.*;

public class InvokeAllDemo {
    public static void main(String[] args) {
        // 1. Create your ExecutorService
        try (ExecutorService executor = Executors.newFixedThreadPool(3)) {

            // 2. Define your tasks (Callable returns a value)
            List<Callable<String>> tasks = Arrays.asList(
                    () -> { Thread.sleep(500); return "Result A"; },
                    () -> { Thread.sleep(1000); return "Result B"; },
                    () -> { Thread.sleep(200); return "Result C"; }
            );

            try {
                // 3. Invoke all tasks. Execution stops here until all are done.
                System.out.println("Executing tasks...");
                List<Future<String>> futures = executor.invokeAll(tasks);

                // 4. Process the results
                for (Future<String> future : futures) {
                    // Future.get() will not block here because invokeAll already waited
                    System.out.println("Task output: " + future.get());
                }
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        } // Executor closes automatically with try-with-resources (Java 19+)
    }
}

Important Considerations

  • Order Preservation: The returned List<Future> maintains the same order as the input task list. futures.get(0) will always correspond to tasks.get(0).
  • Timeouts: There is an overloaded version: invokeAll(tasks, timeout, unit). If the timeout expires, tasks that haven’t finished are canceled, and the method returns the list of futures (some will be marked as canceled).
  • Exceptions: If a task throws an exception, invokeAll doesn’t fail. Instead, that specific Future.get() will throw an ExecutionException.
  • Blocking Behavior: Since invokeAll blocks the calling thread, avoid calling it on a thread that needs to stay responsive (like a UI thread or a primary event loop) without careful planning.

When to use it vs invokeAny?

  • Use invokeAll when you need the results of everything you started.
  • Use invokeAny when you have multiple ways to get a result, and you only care about the first one that finishes successfully (it cancels the rest).

Happy coding! If you’re working within a Spring environment, you might also want to look into @Async for higher-level abstraction, but for raw concurrency control, invokeAll is a classic choice.

How do I use ExecutorService with virtual threads?

To use ExecutorService with virtual threads in Java, you can leverage the Executors.newVirtualThreadPerTaskExecutor() method. This method creates an ExecutorService where each task is executed on a new virtual thread, managed by the Java runtime. Here’s a step-by-step guide:


1. Dependencies & Setup

Ensure you are using Java 19 or newer. Virtual threads were introduced as a preview feature, but from Java 21 onward, they are part of the platform. You may need --enable-preview as a JVM option for Java 19 and 20.


2. Creating an ExecutorService with Virtual Threads

The Executors.newVirtualThreadPerTaskExecutor() method provides an easy way to create an executor service for virtual threads.

Example:

package org.kodejava.util.concurrent;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class VirtualThreadExample {
    public static void main(String[] args) {
        // Creates an ExecutorService with virtual threads
        try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
            // Submitting tasks to executor
            executor.submit(() -> System.out.println("Task 1 on virtual thread"));
            executor.submit(() -> System.out.println("Task 2 on another virtual thread"));
        } // The executor is automatically closed after the try block
    }
}

In this example:

  • Each task runs in its own virtual thread, allowing them to scale efficiently.
  • The try block ensures the resources are cleaned up when the executor is closed.

3. Advantages

  • Concurrency: High-concurrency tasks, such as I/O-bound operations, benefit from virtual threads.
  • Scalability: You don’t have to limit the number of threads since virtual threads don’t demand system OS threads.
  • Simplicity: Virtual threads make it easier to adopt a thread-per-task model without resource overhead.

4. Important Use Cases

  • Concurrent workloads like handling multiple incoming web requests.
  • Tasks that rely on blocking operations, such as database access or network I/O.

5. Combining with Structured Concurrency (Optional)

Using structured concurrency (Java 21+) simplifies managing tasks by controlling their lifecycle. Here’s a snippet combining virtual threads with structured concurrency:

Example:

package org.kodejava.util.concurrent;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class StructuredConcurrencyExample {
    public static void main(String[] args) throws Exception {
        // Using an ExecutorService with virtual threads
        try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
            var task1 = executor.submit(() -> {
                Thread.sleep(500); // Simulating a long-running task
                return "Result from task 1";
            });

            var task2 = executor.submit(() -> {
                Thread.sleep(300); // Another long-running task
                return "Result from task 2";
            });

            // Getting results from tasks
            System.out.println(task1.get());
            System.out.println(task2.get());
        }
    }
}

6. Considerations

  • Resource Efficiency: Virtual threads work well for blocking I/O tasks. However, for CPU-bound tasks, you’re limited by the number of available processors.
  • Preview Feature (If Applicable): Ensure you run the program with --enable-preview if using a preview version of Java.

Virtual threads offer a significant leap in simplifying multithreaded programming while improving scalability. Transitioning to virtual threads in most legacy multithreaded systems is straightforward because they integrate seamlessly with the existing threading APIs.

How do I use CompletableFuture for reactive-style concurrency?

Using CompletableFuture in Java can be a powerful way to implement reactive-style concurrency. It provides a clean and functional way to perform asynchronous tasks, compose their results, and handle exceptions without blocking threads. Here’s an explanation with examples to guide you through its reactive-style usage:


Key Features of CompletableFuture

  1. Asynchronous execution – Run tasks on background threads.
  2. Chaining tasks – Perform dependent actions when a task completes using thenApply, thenAccept, thenCompose, etc.
  3. Combining tasks – Execute multiple tasks in parallel and combine their results using thenCombine, allOf, or anyOf.
  4. Exception handling – Handle errors gracefully with handle, exceptionally, or whenComplete.

Example Use Cases

1. Basic Asynchronous Execution

You can run a task asynchronously without blocking the main thread:

package org.kodejava.util.concurrent;

import java.util.concurrent.CompletableFuture;

public class CompletableFutureExample {
    public static void main(String[] args) {
        CompletableFuture.supplyAsync(() -> {
            // Simulate a long computation
            System.out.println("Running in background...");
            return "Result";
        }).thenAccept(result -> {
            // Use the result once completed
            System.out.println("Completed with: " + result);
        });

        System.out.println("Main thread is free to do other work...");
    }
}

Output:

Main thread is free to do other work...
Running in background...
Completed with: Result

2. Chaining Dependent Tasks

Reactive-style programming involves chaining tasks, which can be done with thenApply or thenCompose:

package org.kodejava.util.concurrent;

import java.util.concurrent.CompletableFuture;

public class CompletableFutureChaining {
    public static void main(String[] args) {
        CompletableFuture.supplyAsync(() -> {
            // Fetch some data (simulate API/database call)
            return "Data from API";
        }).thenApply(data -> {
            // Transform the data
            return data.toUpperCase();
        }).thenAccept(processedData -> {
            // Use transformed data
            System.out.println("Processed Data: " + processedData);
        });
    }
}

3. Combining Multiple Async Tasks

To run multiple tasks in parallel and combine their results:

package org.kodejava.util.concurrent;

import java.util.concurrent.CompletableFuture;

public class CompletableFutureCombine {
    public static void main(String[] args) {
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Task 1 Result");
        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "Task 2 Result");

        CompletableFuture<String> combinedFuture = future1.thenCombine(future2, (result1, result2) -> {
            return result1 + " & " + result2;
        });

        combinedFuture.thenAccept(System.out::println);
    }
}

Output:

Task 1 Result & Task 2 Result

4. Waiting for All Tasks to Complete

If you need to wait for multiple independent tasks to complete:

package org.kodejava.util.concurrent;

import java.util.concurrent.CompletableFuture;
import java.util.List;

public class CompletableFutureAllOf {
    public static void main(String[] args) {
        CompletableFuture<Void> allTasks = CompletableFuture.allOf(
                CompletableFuture.runAsync(() -> System.out.println("Task 1")),
                CompletableFuture.runAsync(() -> System.out.println("Task 2")),
                CompletableFuture.runAsync(() -> System.out.println("Task 3"))
        );

        // Wait for all tasks to complete
        allTasks.join();
        System.out.println("All tasks completed.");
    }
}

5. Handling Exceptions

You can handle exceptions gracefully with methods like exceptionally, handle, or whenComplete:

package org.kodejava.util.concurrent;

import java.util.concurrent.CompletableFuture;

public class CompletableFutureExceptionHandling {
    public static void main(String[] args) {
        CompletableFuture.supplyAsync(() -> {
                    // Simulate an error
                    if (true) throw new RuntimeException("Something went wrong!");
                    return "Task Result";
                })
                .exceptionally(ex -> {
                    System.out.println("Error: " + ex.getMessage());
                    return "Fallback Result";
                })
                .thenAccept(result -> System.out.println("Result: " + result));
    }
}

Output:

Error: Something went wrong!
Result: Fallback Result

6. Running Tasks in a Custom Executor

By default, CompletableFuture uses the common ForkJoinPool, but you can specify a custom executor:

package org.kodejava.util.concurrent;

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

public class CustomExecutorExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(2);

        CompletableFuture.runAsync(() -> {
            System.out.println("Task executing in custom executor");
        }, executor).thenRun(() -> executor.shutdown());
    }
}

Summary of Key Methods

Method Purpose
supplyAsync(Supplier) Run a computation in another thread and return a result.
runAsync(Runnable) Run a computation without returning a result.
thenApply(Function) Transform result of the stage.
thenCompose(Function) Chain another async computation dependent on the previous one.
thenAccept(Consumer) Consume the result.
thenCombine(CompletableFuture, BiFunction) Combine results of two independent computations.
allOf(CompletableFuture...) Wait for all tasks to complete.
anyOf(CompletableFuture...) Return as soon as any task is complete.
exceptionally(Function) Handle exceptions and provide a fallback value.
handle(BiFunction) Process the result or handle exceptions.

Benefits of Using CompletableFuture for Reactive Programming

  • Non-blocking and efficient concurrency.
  • Easier composition of asynchronous operations compared to traditional threads.
  • Fine-grained exception handling and coordination of parallel tasks.
  • Works well with APIs like REST or streaming in a reactive pipeline.

By taking advantage of these features, you can implement clean, reactive, and efficient systems.

How do I configure a custom thread factory for better debugging?

Configuring a custom thread factory can enhance debugging by customizing the naming and behavior of threads you create for your application. By providing meaningful names to threads and optionally logging their creation, you can significantly simplify debugging and profiling, especially in multi-threaded environments.

Here’s how you can configure a custom thread factory in Java:


Steps to Configure a Custom Thread Factory

  1. Implement a Custom ThreadFactory
    Create a custom class that implements the java.util.concurrent.ThreadFactory interface.

  2. Customize Thread Creation
    Override the newThread() method to provide specific thread naming, priorities, daemon flags, or other settings.

  3. Make the Threads Traceable
    Use meaningful thread names (e.g., include a prefix to indicate the purpose), which can be extremely helpful in logs during debugging.


Example of a Custom Thread Factory

Below is a code example of a custom thread factory:

package org.kodejava.util.concurrent;

import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

public class DebuggableThreadFactory implements ThreadFactory {

   private final String threadNamePrefix;
   private final boolean daemon;
   private final int threadPriority;
   private final AtomicInteger threadCount = new AtomicInteger(1);

   public DebuggableThreadFactory(String threadNamePrefix, boolean daemon, int threadPriority) {
      this.threadNamePrefix = threadNamePrefix != null ? threadNamePrefix : "Thread";
      this.daemon = daemon;
      this.threadPriority = threadPriority;
   }

   @Override
   public Thread newThread(Runnable r) {
      String threadName = threadNamePrefix + "-" + threadCount.getAndIncrement();
      Thread thread = new Thread(r, threadName);
      thread.setDaemon(daemon);
      thread.setPriority(threadPriority);

      // For debugging, log thread creation
      System.out.println("Created thread: " + thread.getName() +
                         ", Daemon: " + daemon +
                         ", Priority: " + thread.getPriority());
      return thread;
   }
}

How to Use the Custom Thread Factory

You can use this custom thread factory to create executor services or individual threads:

Using with an ExecutorService:

package org.kodejava.util.concurrent;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main {
   public static void main(String[] args) {
      DebuggableThreadFactory threadFactory =
              new DebuggableThreadFactory("Worker", false, Thread.NORM_PRIORITY);

      try (ExecutorService executorService = Executors.newFixedThreadPool(5, threadFactory)) {
         executorService.submit(() -> System.out.println("Task executed by: " + Thread.currentThread().getName()));
         executorService.shutdown();
      }
   }
}

Creating Individual Threads:

package org.kodejava.util.concurrent;

public class Main {
   public static void main(String[] args) {
      DebuggableThreadFactory threadFactory =
              new DebuggableThreadFactory("CustomThread", true, Thread.MAX_PRIORITY);

      Thread customThread = threadFactory.newThread(() -> {
         System.out.println("Running in: " + Thread.currentThread().getName());
      });

      customThread.start();
   }
}

Key Features of the Example

  1. Thread Naming:
    • Threads are named with a prefix and a counter (Worker-1, Worker-2, etc.).
    • Helps identify which thread is handling which task during debugging.
  2. Daemon Threads:
    • You can optionally configure threads as daemon or non-daemon.
    • Daemon threads do not prevent the JVM from exiting.
  3. Thread Priority:
    • You can set thread priorities (e.g., Thread.NORM_PRIORITY, Thread.MAX_PRIORITY, etc.).
  4. Debugging Logs:
    • Logs thread creation for visibility.
  5. Atomic Synchronization:
    • Ensures thread-safe counters when generating unique thread names.

Further Improvements

  • Custom Uncaught Exception Handlers:
    Set an uncaught exception handler for catching unhandled exceptions:

    thread.setUncaughtExceptionHandler((t, e) -> {
      System.err.println("Uncaught exception in thread " + t.getName() + ": " + e.getMessage());
    });
    
  • Thread Context Information:
    Consider associating thread-local variables to store additional debugging details when necessary.

By using this approach, you’ll gain greater control over thread behavior and be better equipped for debugging multi-threaded applications.