/* * Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /** * @test * @summary Unit test for Thread.Builder * @run junit BuilderTest */ import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.LockSupport; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assumptions.*; class BuilderTest { /** * Test Thread.ofPlatform to create platform threads. */ @Test void testPlatformThread() throws Exception { Thread parent = Thread.currentThread(); Thread.Builder.OfPlatform builder = Thread.ofPlatform(); // unstarted AtomicBoolean done1 = new AtomicBoolean(); Thread thread1 = builder.unstarted(() -> done1.set(true)); assertFalse(thread1.isVirtual()); assertTrue(thread1.getState() == Thread.State.NEW); assertFalse(thread1.getName().isEmpty()); assertTrue(thread1.getThreadGroup() == parent.getThreadGroup()); assertTrue(thread1.isDaemon() == parent.isDaemon()); assertTrue(thread1.getPriority() == parent.getPriority()); assertTrue(thread1.getContextClassLoader() == parent.getContextClassLoader()); thread1.start(); thread1.join(); assertTrue(done1.get()); // start AtomicBoolean done2 = new AtomicBoolean(); Thread thread2 = builder.start(() -> done2.set(true)); assertFalse(thread2.isVirtual()); assertTrue(thread2.getState() != Thread.State.NEW); assertFalse(thread2.getName().isEmpty()); ThreadGroup group2 = thread2.getThreadGroup(); assertTrue(group2 == parent.getThreadGroup() || group2 == null); assertTrue(thread2.isDaemon() == parent.isDaemon()); assertTrue(thread2.getPriority() == parent.getPriority()); assertTrue(thread2.getContextClassLoader() == parent.getContextClassLoader()); thread2.join(); assertTrue(done2.get()); // factory AtomicBoolean done3 = new AtomicBoolean(); Thread thread3 = builder.factory().newThread(() -> done3.set(true)); assertFalse(thread3.isVirtual()); assertTrue(thread3.getState() == Thread.State.NEW); assertFalse(thread3.getName().isEmpty()); assertTrue(thread3.getThreadGroup() == parent.getThreadGroup()); assertTrue(thread3.isDaemon() == parent.isDaemon()); assertTrue(thread3.getPriority() == parent.getPriority()); assertTrue(thread3.getContextClassLoader() == parent.getContextClassLoader()); thread3.start(); thread3.join(); assertTrue(done3.get()); } /** * Test Thread.ofVirtual to create virtual threads. */ @Test void testVirtualThread() throws Exception { Thread parent = Thread.currentThread(); Thread.Builder.OfVirtual builder = Thread.ofVirtual(); // unstarted AtomicBoolean done1 = new AtomicBoolean(); Thread thread1 = builder.unstarted(() -> done1.set(true)); assertTrue(thread1.isVirtual()); assertTrue(thread1.getState() == Thread.State.NEW); assertTrue(thread1.getName().isEmpty()); assertTrue(thread1.getContextClassLoader() == parent.getContextClassLoader()); assertTrue(thread1.isDaemon()); assertTrue(thread1.getPriority() == Thread.NORM_PRIORITY); thread1.start(); thread1.join(); assertTrue(done1.get()); // start AtomicBoolean done2 = new AtomicBoolean(); Thread thread2 = builder.start(() -> done2.set(true)); assertTrue(thread2.isVirtual()); assertTrue(thread2.getState() != Thread.State.NEW); assertTrue(thread2.getName().isEmpty()); assertTrue(thread2.getContextClassLoader() == parent.getContextClassLoader()); assertTrue(thread2.isDaemon()); assertTrue(thread2.getPriority() == Thread.NORM_PRIORITY); thread2.join(); assertTrue(done2.get()); // factory AtomicBoolean done3 = new AtomicBoolean(); Thread thread3 = builder.factory().newThread(() -> done3.set(true)); assertTrue(thread3.isVirtual()); assertTrue(thread3.getState() == Thread.State.NEW); assertTrue(thread3.getName().isEmpty()); assertTrue(thread3.getContextClassLoader() == parent.getContextClassLoader()); assertTrue(thread3.isDaemon()); assertTrue(thread3.getPriority() == Thread.NORM_PRIORITY); thread3.start(); thread3.join(); assertTrue(done3.get()); } /** * Test Thread.Builder.name. */ @Test void testName1() { Thread.Builder builder = Thread.ofPlatform().name("duke"); Thread thread1 = builder.unstarted(() -> { }); Thread thread2 = builder.start(() -> { }); Thread thread3 = builder.factory().newThread(() -> { }); assertTrue(thread1.getName().equals("duke")); assertTrue(thread2.getName().equals("duke")); assertTrue(thread3.getName().equals("duke")); } @Test void testName2() { Thread.Builder builder = Thread.ofVirtual().name("duke"); Thread thread1 = builder.unstarted(() -> { }); Thread thread2 = builder.start(() -> { }); Thread thread3 = builder.factory().newThread(() -> { }); assertTrue(thread1.getName().equals("duke")); assertTrue(thread2.getName().equals("duke")); assertTrue(thread3.getName().equals("duke")); } @Test void testName3() { Thread.Builder builder = Thread.ofPlatform().name("duke-", 100); Thread thread1 = builder.unstarted(() -> { }); Thread thread2 = builder.unstarted(() -> { }); Thread thread3 = builder.unstarted(() -> { }); assertTrue(thread1.getName().equals("duke-100")); assertTrue(thread2.getName().equals("duke-101")); assertTrue(thread3.getName().equals("duke-102")); ThreadFactory factory = builder.factory(); Thread thread4 = factory.newThread(() -> { }); Thread thread5 = factory.newThread(() -> { }); Thread thread6 = factory.newThread(() -> { }); assertTrue(thread4.getName().equals("duke-103")); assertTrue(thread5.getName().equals("duke-104")); assertTrue(thread6.getName().equals("duke-105")); } @Test void testName4() { Thread.Builder builder = Thread.ofVirtual().name("duke-", 100); Thread thread1 = builder.unstarted(() -> { }); Thread thread2 = builder.unstarted(() -> { }); Thread thread3 = builder.unstarted(() -> { }); assertTrue(thread1.getName().equals("duke-100")); assertTrue(thread2.getName().equals("duke-101")); assertTrue(thread3.getName().equals("duke-102")); ThreadFactory factory = builder.factory(); Thread thread4 = factory.newThread(() -> { }); Thread thread5 = factory.newThread(() -> { }); Thread thread6 = factory.newThread(() -> { }); assertTrue(thread4.getName().equals("duke-103")); assertTrue(thread5.getName().equals("duke-104")); assertTrue(thread6.getName().equals("duke-105")); } /** * Test Thread.Builder.OfPlatform.group. */ @Test void testThreadGroup1() { ThreadGroup group = new ThreadGroup("groupies"); Thread.Builder builder = Thread.ofPlatform().group(group); Thread thread1 = builder.unstarted(() -> { }); AtomicBoolean done = new AtomicBoolean(); Thread thread2 = builder.start(() -> { while (!done.get()) { LockSupport.park(); } }); Thread thread3 = builder.factory().newThread(() -> { }); try { assertTrue(thread1.getThreadGroup() == group); assertTrue(thread2.getThreadGroup() == group); assertTrue(thread3.getThreadGroup() == group); } finally { done.set(true); LockSupport.unpark(thread2); } } @Test void testThreadGroup2() { ThreadGroup vgroup = Thread.ofVirtual().unstarted(() -> { }).getThreadGroup(); assertEquals("VirtualThreads", vgroup.getName()); Thread thread1 = Thread.ofVirtual().unstarted(() -> { }); Thread thread2 = Thread.ofVirtual().start(LockSupport::park); Thread thread3 = Thread.ofVirtual().factory().newThread(() -> { }); try { assertTrue(thread1.getThreadGroup() == vgroup); assertTrue(thread2.getThreadGroup() == vgroup); assertTrue(thread3.getThreadGroup() == vgroup); } finally { LockSupport.unpark(thread2); } } /** * Test Thread.Builder.OfPlatform.priority. */ @Test void testPriority1() { int priority = Thread.currentThread().getPriority(); Thread.Builder builder = Thread.ofPlatform(); Thread thread1 = builder.unstarted(() -> { }); Thread thread2 = builder.start(() -> { }); Thread thread3 = builder.factory().newThread(() -> { }); assertTrue(thread1.getPriority() == priority); assertTrue(thread2.getPriority() == priority); assertTrue(thread3.getPriority() == priority); } @Test void testPriority2() { int priority = Thread.MIN_PRIORITY; Thread.Builder builder = Thread.ofPlatform().priority(priority); Thread thread1 = builder.unstarted(() -> { }); Thread thread2 = builder.start(() -> { }); Thread thread3 = builder.factory().newThread(() -> { }); assertTrue(thread1.getPriority() == priority); assertTrue(thread2.getPriority() == priority); assertTrue(thread3.getPriority() == priority); } @Test void testPriority3() { Thread currentThread = Thread.currentThread(); assumeFalse(currentThread.isVirtual(), "Main thread is a virtual thread"); int maxPriority = currentThread.getThreadGroup().getMaxPriority(); int priority = Math.min(maxPriority + 1, Thread.MAX_PRIORITY); Thread.Builder builder = Thread.ofPlatform().priority(priority); Thread thread1 = builder.unstarted(() -> { }); Thread thread2 = builder.start(() -> { }); Thread thread3 = builder.factory().newThread(() -> { }); assertTrue(thread1.getPriority() == priority); assertTrue(thread2.getPriority() == priority); assertTrue(thread3.getPriority() == priority); } @Test void testPriority4() { var builder = Thread.ofPlatform(); assertThrows(IllegalArgumentException.class, () -> builder.priority(Thread.MIN_PRIORITY - 1)); } @Test void testPriority5() { var builder = Thread.ofPlatform(); assertThrows(IllegalArgumentException.class, () -> builder.priority(Thread.MAX_PRIORITY + 1)); } /** * Test Thread.Builder.OfPlatform.daemon. */ @Test void testDaemon1() { Thread.Builder builder = Thread.ofPlatform().daemon(false); Thread thread1 = builder.unstarted(() -> { }); Thread thread2 = builder.start(() -> { }); Thread thread3 = builder.factory().newThread(() -> { }); assertFalse(thread1.isDaemon()); assertFalse(thread2.isDaemon()); assertFalse(thread3.isDaemon()); } @Test void testDaemon2() { Thread.Builder builder = Thread.ofPlatform().daemon(true); Thread thread1 = builder.unstarted(() -> { }); Thread thread2 = builder.start(() -> { }); Thread thread3 = builder.factory().newThread(() -> { }); assertTrue(thread1.isDaemon()); assertTrue(thread2.isDaemon()); assertTrue(thread3.isDaemon()); } @Test void testDaemon3() { Thread.Builder builder = Thread.ofPlatform().daemon(); Thread thread1 = builder.unstarted(() -> { }); Thread thread2 = builder.start(() -> { }); Thread thread3 = builder.factory().newThread(() -> { }); assertTrue(thread1.isDaemon()); assertTrue(thread2.isDaemon()); assertTrue(thread3.isDaemon()); } @Test void testDaemon4() { Thread.Builder builder = Thread.ofPlatform(); Thread thread1 = builder.unstarted(() -> { }); Thread thread2 = builder.start(() -> { }); Thread thread3 = builder.factory().newThread(() -> { }); // daemon status should be inherited boolean d = Thread.currentThread().isDaemon(); assertTrue(thread1.isDaemon() == d); assertTrue(thread2.isDaemon() == d); assertTrue(thread3.isDaemon() == d); } /** * Test Thread.ofVirtual creates daemon threads. */ @Test void testDaemon5() { Thread.Builder builder = Thread.ofVirtual(); Thread thread1 = builder.unstarted(() -> { }); Thread thread2 = builder.start(() -> { }); Thread thread3 = builder.factory().newThread(() -> { }); // daemon status should always be true assertTrue(thread1.isDaemon()); assertTrue(thread2.isDaemon()); assertTrue(thread3.isDaemon()); } /** * Test Thread.Builder.OfPlatform.stackSize. */ @Test void testStackSize1() { Thread.Builder builder = Thread.ofPlatform().stackSize(1024*1024); Thread thread1 = builder.unstarted(() -> { }); Thread thread2 = builder.start(() -> { }); Thread thread3 = builder.factory().newThread(() -> { }); } @Test void testStackSize2() { Thread.Builder builder = Thread.ofPlatform().stackSize(0); Thread thread1 = builder.unstarted(() -> { }); Thread thread2 = builder.start(() -> { }); Thread thread3 = builder.factory().newThread(() -> { }); } @Test void testStackSize3() { var builder = Thread.ofPlatform(); assertThrows(IllegalArgumentException.class, () -> builder.stackSize(-1)); } /** * Test Thread.Builder.uncaughtExceptionHandler. */ @Test void testUncaughtExceptionHandler1() throws Exception { class FooException extends RuntimeException { } AtomicReference threadRef = new AtomicReference<>(); AtomicReference exceptionRef = new AtomicReference<>(); Thread thread = Thread.ofPlatform() .uncaughtExceptionHandler((t, e) -> { assertTrue(t == Thread.currentThread()); threadRef.set(t); exceptionRef.set(e); }) .start(() -> { throw new FooException(); }); thread.join(); assertTrue(threadRef.get() == thread); assertTrue(exceptionRef.get() instanceof FooException); } @Test void testUncaughtExceptionHandler2() throws Exception { class FooException extends RuntimeException { } AtomicReference threadRef = new AtomicReference<>(); AtomicReference exceptionRef = new AtomicReference<>(); Thread thread = Thread.ofVirtual() .uncaughtExceptionHandler((t, e) -> { assertTrue(t == Thread.currentThread()); threadRef.set(t); exceptionRef.set(e); }) .start(() -> { throw new FooException(); }); thread.join(); assertTrue(threadRef.get() == thread); assertTrue(exceptionRef.get() instanceof FooException); } @Test void testUncaughtExceptionHandler3() throws Exception { class FooException extends RuntimeException { } AtomicReference threadRef = new AtomicReference<>(); AtomicReference exceptionRef = new AtomicReference<>(); Thread thread = Thread.ofPlatform() .uncaughtExceptionHandler((t, e) -> { assertTrue(t == Thread.currentThread()); threadRef.set(t); exceptionRef.set(e); }) .factory() .newThread(() -> { throw new FooException(); }); thread.start(); thread.join(); assertTrue(threadRef.get() == thread); assertTrue(exceptionRef.get() instanceof FooException); } @Test void testUncaughtExceptionHandler4() throws Exception { class FooException extends RuntimeException { } AtomicReference threadRef = new AtomicReference<>(); AtomicReference exceptionRef = new AtomicReference<>(); Thread thread = Thread.ofPlatform() .uncaughtExceptionHandler((t, e) -> { assertTrue(t == Thread.currentThread()); threadRef.set(t); exceptionRef.set(e); }) .factory() .newThread(() -> { throw new FooException(); }); thread.start(); thread.join(); assertTrue(threadRef.get() == thread); assertTrue(exceptionRef.get() instanceof FooException); } static final ThreadLocal LOCAL = new ThreadLocal<>(); static final ThreadLocal INHERITED_LOCAL = new InheritableThreadLocal<>(); /** * Tests that a builder creates threads that support thread locals */ private void testThreadLocals(Thread.Builder builder) throws Exception { AtomicBoolean done = new AtomicBoolean(); Runnable task = () -> { Object value = new Object(); LOCAL.set(value); assertTrue(LOCAL.get() == value); done.set(true); }; done.set(false); Thread thread1 = builder.unstarted(task); thread1.start(); thread1.join(); assertTrue(done.get()); done.set(false); Thread thread2 = builder.start(task); thread2.join(); assertTrue(done.get()); done.set(false); Thread thread3 = builder.factory().newThread(task); thread3.start(); thread3.join(); assertTrue(done.get()); } /** * Tests that a builder creates threads that do not support thread locals */ private void testNoThreadLocals(Thread.Builder builder) throws Exception { AtomicBoolean done = new AtomicBoolean(); Runnable task = () -> { try { LOCAL.set(new Object()); } catch (UnsupportedOperationException expected) { done.set(true); } }; done.set(false); Thread thread1 = builder.unstarted(task); thread1.start(); thread1.join(); assertTrue(done.get()); done.set(false); Thread thread2 = builder.start(task); thread2.join(); assertTrue(done.get()); done.set(false); Thread thread3 = builder.factory().newThread(task); thread3.start(); thread3.join(); assertTrue(done.get()); } /** * Test Thread.Builder creates threads that allow thread locals. */ @Test void testThreadLocals1() throws Exception { Thread.Builder builder = Thread.ofPlatform(); testThreadLocals(builder); } @Test void testThreadLocals2() throws Exception { Thread.Builder builder = Thread.ofVirtual(); testThreadLocals(builder); } /** * Tests that a builder creates threads that inherits the initial values of * inheritable thread locals. */ private void testInheritedThreadLocals(Thread.Builder builder) throws Exception { Object value = new Object(); INHERITED_LOCAL.set(value); AtomicBoolean done = new AtomicBoolean(); Runnable task = () -> { assertTrue(INHERITED_LOCAL.get() == value); done.set(true); }; done.set(false); Thread thread1 = builder.unstarted(task); thread1.start(); thread1.join(); assertTrue(done.get()); done.set(false); Thread thread2 = builder.start(task); thread2.join(); assertTrue(done.get()); done.set(false); Thread thread3 = builder.factory().newThread(task); thread3.start(); thread3.join(); assertTrue(done.get()); } /** * Tests that a builder creates threads that do not inherit the initial values * of inheritable thread locals. */ private void testNoInheritedThreadLocals(Thread.Builder builder) throws Exception { Object value = new Object(); INHERITED_LOCAL.set(value); AtomicBoolean done = new AtomicBoolean(); Runnable task = () -> { assertNull(INHERITED_LOCAL.get()); done.set(true); }; done.set(false); Thread thread1 = builder.unstarted(task); thread1.start(); thread1.join(); assertTrue(done.get()); done.set(false); Thread thread2 = builder.start(task); thread2.join(); assertTrue(done.get()); done.set(false); Thread thread3 = builder.factory().newThread(task); thread3.start(); thread3.join(); assertTrue(done.get()); } /** * Test Thread.Builder creating threads that inherit or do not inherit * the initial values of inheritable thread locals. */ @Test void testInheritedThreadLocals1() throws Exception { Thread.Builder builder = Thread.ofPlatform(); testInheritedThreadLocals(builder); // default // do no inherit builder.inheritInheritableThreadLocals(false); testNoInheritedThreadLocals(builder); // inherit builder.inheritInheritableThreadLocals(true); testInheritedThreadLocals(builder); } @Test void testInheritedThreadLocals2() throws Exception { Thread.Builder builder = Thread.ofVirtual(); testInheritedThreadLocals(builder); // default // do no inherit builder.inheritInheritableThreadLocals(false); testNoInheritedThreadLocals(builder); // inherit builder.inheritInheritableThreadLocals(true); testInheritedThreadLocals(builder); } /** * Tests a builder creates threads that inherit the context class loader. */ private void testInheritContextClassLoader(Thread.Builder builder) throws Exception { ClassLoader savedCCL = Thread.currentThread().getContextClassLoader(); try { ClassLoader loader = new ClassLoader() { }; Thread.currentThread().setContextClassLoader(loader); var ref = new AtomicReference(); Runnable task = () -> { ref.set(Thread.currentThread().getContextClassLoader()); }; // unstarted Thread thread1 = builder.unstarted(task); assertTrue(thread1.getContextClassLoader() == loader); // started ref.set(null); thread1.start(); thread1.join(); assertTrue(ref.get() == loader); // factory Thread thread2 = builder.factory().newThread(task); assertTrue(thread2.getContextClassLoader() == loader); // started ref.set(null); thread2.start(); thread2.join(); assertTrue(ref.get() == loader); } finally { Thread.currentThread().setContextClassLoader(savedCCL); } } /** * Tests a builder creates threads does not inherit the context class loader. */ private void testNoInheritContextClassLoader(Thread.Builder builder) throws Exception { ClassLoader savedCCL = Thread.currentThread().getContextClassLoader(); try { Thread.currentThread().setContextClassLoader( new ClassLoader() { }); ClassLoader scl = ClassLoader.getSystemClassLoader(); var ref = new AtomicReference(); Runnable task = () -> { ref.set(Thread.currentThread().getContextClassLoader()); }; // unstarted Thread thread1 = builder.unstarted(task); assertTrue(thread1.getContextClassLoader() == scl); // started ref.set(null); thread1.start(); thread1.join(); assertTrue(ref.get() == scl); // factory Thread thread2 = builder.factory().newThread(task); assertTrue(thread2.getContextClassLoader() == scl); // started ref.set(null); thread2.start(); thread2.join(); assertTrue(ref.get() == scl); } finally { Thread.currentThread().setContextClassLoader(savedCCL); } } /** * Test Thread.Builder creating threads that inherit or do not inherit * the thread context class loader. */ @Test void testContextClassLoader1() throws Exception { Thread.Builder builder = Thread.ofPlatform(); testInheritContextClassLoader(builder); // default // do no inherit builder.inheritInheritableThreadLocals(false); testNoInheritContextClassLoader(builder); // inherit builder.inheritInheritableThreadLocals(true); testInheritContextClassLoader(builder); } @Test void testContextClassLoader2() throws Exception { Thread.Builder builder = Thread.ofVirtual(); testInheritContextClassLoader(builder); // default // do no inherit builder.inheritInheritableThreadLocals(false); testNoInheritContextClassLoader(builder); // inherit builder.inheritInheritableThreadLocals(true); testInheritContextClassLoader(builder); } /** * Test NullPointerException. */ @Test void testNulls1() { Thread.Builder.OfPlatform builder = Thread.ofPlatform(); assertThrows(NullPointerException.class, () -> builder.group(null)); assertThrows(NullPointerException.class, () -> builder.name(null)); assertThrows(NullPointerException.class, () -> builder.name(null, 0)); assertThrows(NullPointerException.class, () -> builder.uncaughtExceptionHandler(null)); assertThrows(NullPointerException.class, () -> builder.unstarted(null)); assertThrows(NullPointerException.class, () -> builder.start(null)); } @Test void testNulls2() { Thread.Builder builder = Thread.ofVirtual(); assertThrows(NullPointerException.class, () -> builder.name(null)); assertThrows(NullPointerException.class, () -> builder.name(null, 0)); assertThrows(NullPointerException.class, () -> builder.uncaughtExceptionHandler(null)); assertThrows(NullPointerException.class, () -> builder.unstarted(null)); assertThrows(NullPointerException.class, () -> builder.start(null)); } }