And then somehow the unnecessary statics started to bother me:
import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicReference; public class Stop { public static void main(String[] args) throws Exception { final CountDownLatch ready = new CountDownLatch(1); final ThreadGroup group = new ThreadGroup(""); final AtomicReference<Throwable> firstThrowable = new AtomicReference<>(); final AtomicReference<Throwable> secondThrowable = new AtomicReference<>(); final Thread second = new Thread(group, () -> { ready.countDown(); try { do {} while (true); } catch (Throwable ex) { secondThrowable.set(ex); }}); final Thread first = new Thread(group, () -> { // Wait until "second" is started try { ready.await(); group.stop(); } catch (Throwable ex) { firstThrowable.set(ex); }}); // Launch two threads as part of the same thread group first.start(); second.start(); // Both threads should terminate when the first thread // terminates the thread group. second.join(); first.join(); if (! (firstThrowable.get() instanceof ThreadDeath) || ! (secondThrowable.get() instanceof ThreadDeath)) throw new AssertionError("should have thrown ThreadDeath"); // Test passed - if never get here the test times out and fails. } }