Running on Java 24-ea+24-2960 (Preview)
Home of The JavaSpecialists' Newsletter

226Discovering Where Threads Are Being Constructed

Author: Dr. Heinz M. KabutzDate: 2015-02-24Java Version: 8Category: Tips and Tricks
 

Abstract: How can you discover all the places in your program where threads are being constructed? In this newsletter we create our own little SecurityManager to keep an eye on thread creation.

 

Welcome to the 226th issue of The Java(tm) Specialists' Newsletter, sent to you from the beautiful island of Crete. The mountains of Crete are covered with snow at this time of year. From where I'm sitting right now in our conference room, I have a beautiful view of the Lefka Ori mountain range in the distance. My neighbour's sleepy vineyard is to my left and I can see Mr Kostas walking around and inspecting his property. He was just a wee lad when Zorba the Greek was filmed in our village, and he got the role of running around and throwing stones. Perfect for a young Cretan boy. I'm sure my son would've enjoyed that too. In front of me is the sea. Most days the sun is too bright on the azure water and I need to close the shutters.

javaspecialists.teachable.com: Please visit our new self-study course catalog to see how you can upskill your Java knowledge.

Discovering Where Threads Are Being Constructed

This newsletter was prompted by a question by one of my readers, Scott Morgan from Adligo. He wanted to know whether there was a way in which we could somehow prevent threads from being created. Java does not have the concept of thread ownership. Once you have started a thread, you have no further claim over at. It immediately has full rights, same as its creator. We do have thread groups. These were used initially with Java, but since Java 5, there has not been much need for them. We use to handle uncaught exceptions with thread groups in the early days of Java. But nowadays, we have UncaughtExceptionHandler. Furthermore, in modern code, threads are partitioned using thread pools rather than thread groups.

So how can we control the creation and starting of threads? If you look inside the source code for the constructor of thread, you will notice a call to the security manager. This checks whether we are allowed to modify our current thread group. A thread automatically is created in the same thread group as its parent. Thus if we can check the permission, we could also determine whether the thread may be created or not.

The code I will present in this newsletter, would typically be used in a testing environment. It would not work if you already had a security manager installed. We present a ThreadWatcher security manager that will test a given predicate to determine whether an action should be executed. Let's start with the ThreadWatcher:

import java.security.*;
import java.util.function.*;

public class ThreadWatcher extends SecurityManager {
  private final Predicate<Thread> predicate;
  private final Consumer<Thread> action;

  public ThreadWatcher(Predicate<Thread> predicate,
                       Consumer<Thread> action) {
    this.predicate = predicate;
    this.action = action;
  }

  public void checkPermission(Permission perm) {
    // allow everything
  }

  public void checkPermission(Permission perm, Object context) {
    // allow everything
  }

  public void checkAccess(ThreadGroup g) {
    Thread creatingThread = Thread.currentThread();
    if (predicate.test(creatingThread)) {
      action.accept(creatingThread);
    }
  }
}

Let's say that we wanted to prevent threads within thread pools from creating new threads. This is an arbitrary rule, and just meant as an illustration. Typically, thread pool threads follow a particular naming convention, which we can find in the thread factory. It is of the form pool-#-thread-#, where the #'s would hopefully render the names unique. We could thus define a predicate that used a regular expression match on the thread name, such as a Lambda Predicate (Thread t) -> t.getName().matches("pool-\\d+-thread-\\d+")

We also define a simple job that prints "hello" plus the thread name and a consumer that throws a SecurityException if we try to create a thread for a context where the predicate matches. These are all provided by the DemoSupport interface:

import java.util.function.*;

public interface DemoSupport {
  static Predicate<Thread> createPredicate() {
    return (Thread t) ->
        t.getName().matches("pool-\\d+-thread-\\d+");
  }

  static Consumer<Thread> createConsumer() {
    return (Thread creator) -> {
      throw new SecurityException(creator +
          " tried to create a thread");
    };
  }

  static Runnable createHelloJob() {
    return () -> System.out.printf(
        "Hello from \"%s\"",
        Thread.currentThread()
    );
  }
}

In our first example, we try to create a new thread from within our main thread. This should work:

import java.util.concurrent.*;

public class ThreadFromMainThread {
  public static void main(String... args)
      throws InterruptedException {
    System.setSecurityManager(
        new ThreadWatcher(
            DemoSupport.createPredicate(),
            DemoSupport.createConsumer()
        )
    );

    new Thread(DemoSupport.createHelloJob(),
        "This should work 1").start();

    System.setSecurityManager(null);
  }
}

Output is this:

Hello from "Thread[This should work 1,5,main]"

On the other hand, if we try to create a new thread from within a thread pool thread, then the name will match our regular expression and it will fail:

import java.util.concurrent.*;

public class ThreadFromThreadPool {
  public static void main(String... args)
      throws InterruptedException {
    System.setSecurityManager(
        new ThreadWatcher(
            DemoSupport.createPredicate(),
            DemoSupport.createConsumer()
        )
    );

    ExecutorService pool = Executors.newFixedThreadPool(10);
    Future<?> future = pool.submit(() ->
            new Thread(DemoSupport.createHelloJob(),
                "This should print a warning 1")
    );
    try {
      future.get();
    } catch (ExecutionException e) {
      e.getCause().printStackTrace();
    }
    pool.shutdown();

    System.setSecurityManager(null);
  }
}

The output is now:

java.lang.SecurityException: Thread[pool-1-thread-1,5,main] \
    tried to create a thread
  at DemoSupport.lambda$createConsumer$1(DemoSupport.java:11)
  at DemoSupport$$Lambda$2/558638686.accept(Unknown Source)
  at ThreadWatcher.checkAccess(ThreadWatcher.java:25)
  at java.lang.ThreadGroup.checkAccess(ThreadGroup.java:315)
  at java.lang.Thread.init(Thread.java:391)
  at java.lang.Thread.init(Thread.java:349)
  at java.lang.Thread.<init>(Thread.java:548)
  at ThreadFromThreadPool.lambda$main$0(ThreadFromThreadPool:15)
  at ThreadFromThreadPool$$Lambda$3/1452126962.call
  at java.util.concurrent.FutureTask.run(FutureTask.java:266)
  at java.util.concurrent.ThreadPoolExecutor.runWorker
  at java.util.concurrent.ThreadPoolExecutor$Worker.run
  at java.lang.Thread.run(Thread.java:745)

We could also get a bit more fancy with how we manage the threads who are misbehaving. For example, we could put all the miscreants into a map, with functions forEach() and toString() implemented to view the elements, such as:

import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
import java.util.function.*;
import java.util.stream.*;

public class ThreadAccumulator implements Consumer<Thread> {
  private final ConcurrentMap<Thread, LongAdder> miscreants =
      new ConcurrentHashMap<>();

  public void accept(Thread thread) {
    miscreants.computeIfAbsent(
        thread, t -> new LongAdder()).increment();
  }

  public int getNumberOfMisbehavingThreads() {
    return miscreants.size();
  }

  public void forEach(BiConsumer<Thread, Integer> action) {
    miscreants.entrySet()
        .forEach(e -> action.accept(
            e.getKey(),
            e.getValue().intValue()));
  }

  public String toString() {
    return miscreants.entrySet()
        .parallelStream()
        .map(ThreadAccumulator::format)
        .collect(Collectors.joining(", "));
  }

  private static String format(Map.Entry<Thread, LongAdder> e) {
    return String.format("%s created %d thread(s)",
        e.getKey().getName(),
        e.getValue().intValue());
  }
}

I must say, the new features in Java 8 are very nice. Have a look at how easy it is to convert the map of miscreants into a nice comma separated String in the toString() method! I also like the computeIfAbsent() method inside Map, which means that there is no more need for the putIfAbsent check.

Thanks for your feedback as always. If you'd like to say "hello", simply reply to this email or send me a note to heinz@javaspecialists.eu. I enjoy hearing from you :-)

Kind regards

Heinz

 

Comments

We are always happy to receive comments from our readers. Feel free to send me a comment via email or discuss the newsletter in our JavaSpecialists Slack Channel (Get an invite here)

When you load these comments, you'll be connected to Disqus. Privacy Statement.

Related Articles

Browse the Newsletter Archive

About the Author

Heinz Kabutz Java Conference Speaker

Java Champion, author of the Javaspecialists Newsletter, conference speaking regular... About Heinz

Superpack '23

Superpack '23 Our entire Java Specialists Training in one huge bundle more...

Free Java Book

Dynamic Proxies in Java Book
Java Training

We deliver relevant courses, by top Java developers to produce more resourceful and efficient programmers within their organisations.

Java Consulting

We can help make your Java application run faster and trouble-shoot concurrency and performance bugs...