1 /*
  2  * Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  */
 23 
 24 /**
 25  * @test
 26  * @summary Unit test for Thread.Builder
 27  * @run junit BuilderTest
 28  */
 29 
 30 import java.util.concurrent.*;
 31 import java.util.concurrent.atomic.AtomicBoolean;
 32 import java.util.concurrent.atomic.AtomicReference;
 33 import java.util.concurrent.locks.LockSupport;
 34 
 35 import org.junit.jupiter.api.Test;
 36 import static org.junit.jupiter.api.Assertions.*;
 37 import static org.junit.jupiter.api.Assumptions.*;
 38 
 39 class BuilderTest {
 40 
 41     /**
 42      * Test Thread.ofPlatform to create platform threads.
 43      */
 44     @Test
 45     void testPlatformThread() throws Exception {
 46         Thread parent = Thread.currentThread();
 47         Thread.Builder.OfPlatform builder = Thread.ofPlatform();
 48 
 49         // unstarted
 50         AtomicBoolean done1 = new AtomicBoolean();
 51         Thread thread1 = builder.unstarted(() -> done1.set(true));
 52         assertFalse(thread1.isVirtual());
 53         assertTrue(thread1.getState() == Thread.State.NEW);
 54         assertFalse(thread1.getName().isEmpty());
 55         assertTrue(thread1.getThreadGroup() == parent.getThreadGroup());
 56         assertTrue(thread1.isDaemon() == parent.isDaemon());
 57         assertTrue(thread1.getPriority() == parent.getPriority());
 58         assertTrue(thread1.getContextClassLoader() == parent.getContextClassLoader());
 59         thread1.start();
 60         thread1.join();
 61         assertTrue(done1.get());
 62 
 63         // start
 64         AtomicBoolean done2 = new AtomicBoolean();
 65         Thread thread2 = builder.start(() -> done2.set(true));
 66         assertFalse(thread2.isVirtual());
 67         assertTrue(thread2.getState() != Thread.State.NEW);
 68         assertFalse(thread2.getName().isEmpty());
 69         ThreadGroup group2 = thread2.getThreadGroup();
 70         assertTrue(group2 == parent.getThreadGroup() || group2 == null);
 71         assertTrue(thread2.isDaemon() == parent.isDaemon());
 72         assertTrue(thread2.getPriority() == parent.getPriority());
 73         assertTrue(thread2.getContextClassLoader() == parent.getContextClassLoader());
 74         thread2.join();
 75         assertTrue(done2.get());
 76 
 77         // factory
 78         AtomicBoolean done3 = new AtomicBoolean();
 79         Thread thread3 = builder.factory().newThread(() -> done3.set(true));
 80         assertFalse(thread3.isVirtual());
 81         assertTrue(thread3.getState() == Thread.State.NEW);
 82         assertFalse(thread3.getName().isEmpty());
 83         assertTrue(thread3.getThreadGroup() == parent.getThreadGroup());
 84         assertTrue(thread3.isDaemon() == parent.isDaemon());
 85         assertTrue(thread3.getPriority() == parent.getPriority());
 86         assertTrue(thread3.getContextClassLoader() == parent.getContextClassLoader());
 87         thread3.start();
 88         thread3.join();
 89         assertTrue(done3.get());
 90     }
 91 
 92     /**
 93      * Test Thread.ofVirtual to create virtual threads.
 94      */
 95     @Test
 96     void testVirtualThread() throws Exception {
 97         Thread parent = Thread.currentThread();
 98         Thread.Builder.OfVirtual builder = Thread.ofVirtual();
 99 
100         // unstarted
101         AtomicBoolean done1 = new AtomicBoolean();
102         Thread thread1 = builder.unstarted(() -> done1.set(true));
103         assertTrue(thread1.isVirtual());
104         assertTrue(thread1.getState() == Thread.State.NEW);
105         assertTrue(thread1.getName().isEmpty());
106         assertTrue(thread1.getContextClassLoader() == parent.getContextClassLoader());
107         assertTrue(thread1.isDaemon());
108         assertTrue(thread1.getPriority() == Thread.NORM_PRIORITY);
109         thread1.start();
110         thread1.join();
111         assertTrue(done1.get());
112 
113         // start
114         AtomicBoolean done2 = new AtomicBoolean();
115         Thread thread2 = builder.start(() -> done2.set(true));
116         assertTrue(thread2.isVirtual());
117         assertTrue(thread2.getState() != Thread.State.NEW);
118         assertTrue(thread2.getName().isEmpty());
119         assertTrue(thread2.getContextClassLoader() == parent.getContextClassLoader());
120         assertTrue(thread2.isDaemon());
121         assertTrue(thread2.getPriority() == Thread.NORM_PRIORITY);
122         thread2.join();
123         assertTrue(done2.get());
124 
125         // factory
126         AtomicBoolean done3 = new AtomicBoolean();
127         Thread thread3 = builder.factory().newThread(() -> done3.set(true));
128         assertTrue(thread3.isVirtual());
129         assertTrue(thread3.getState() == Thread.State.NEW);
130         assertTrue(thread3.getName().isEmpty());
131         assertTrue(thread3.getContextClassLoader() == parent.getContextClassLoader());
132         assertTrue(thread3.isDaemon());
133         assertTrue(thread3.getPriority() == Thread.NORM_PRIORITY);
134         thread3.start();
135         thread3.join();
136         assertTrue(done3.get());
137     }
138 
139     /**
140      * Test Thread.Builder.name.
141      */
142     @Test
143     void testName1() {
144         Thread.Builder builder = Thread.ofPlatform().name("duke");
145 
146         Thread thread1 = builder.unstarted(() -> { });
147         Thread thread2 = builder.start(() -> { });
148         Thread thread3 = builder.factory().newThread(() -> { });
149 
150         assertTrue(thread1.getName().equals("duke"));
151         assertTrue(thread2.getName().equals("duke"));
152         assertTrue(thread3.getName().equals("duke"));
153     }
154 
155     @Test
156     void testName2() {
157         Thread.Builder builder = Thread.ofVirtual().name("duke");
158 
159         Thread thread1 = builder.unstarted(() -> { });
160         Thread thread2 = builder.start(() -> { });
161         Thread thread3 = builder.factory().newThread(() -> { });
162 
163         assertTrue(thread1.getName().equals("duke"));
164         assertTrue(thread2.getName().equals("duke"));
165         assertTrue(thread3.getName().equals("duke"));
166     }
167 
168     @Test
169     void testName3() {
170         Thread.Builder builder = Thread.ofPlatform().name("duke-", 100);
171 
172         Thread thread1 = builder.unstarted(() -> { });
173         Thread thread2 = builder.unstarted(() -> { });
174         Thread thread3 = builder.unstarted(() -> { });
175 
176         assertTrue(thread1.getName().equals("duke-100"));
177         assertTrue(thread2.getName().equals("duke-101"));
178         assertTrue(thread3.getName().equals("duke-102"));
179 
180         ThreadFactory factory = builder.factory();
181         Thread thread4 = factory.newThread(() -> { });
182         Thread thread5 = factory.newThread(() -> { });
183         Thread thread6 = factory.newThread(() -> { });
184 
185         assertTrue(thread4.getName().equals("duke-103"));
186         assertTrue(thread5.getName().equals("duke-104"));
187         assertTrue(thread6.getName().equals("duke-105"));
188     }
189 
190     @Test
191     void testName4() {
192         Thread.Builder builder = Thread.ofVirtual().name("duke-", 100);
193 
194         Thread thread1 = builder.unstarted(() -> { });
195         Thread thread2 = builder.unstarted(() -> { });
196         Thread thread3 = builder.unstarted(() -> { });
197 
198         assertTrue(thread1.getName().equals("duke-100"));
199         assertTrue(thread2.getName().equals("duke-101"));
200         assertTrue(thread3.getName().equals("duke-102"));
201 
202         ThreadFactory factory = builder.factory();
203         Thread thread4 = factory.newThread(() -> { });
204         Thread thread5 = factory.newThread(() -> { });
205         Thread thread6 = factory.newThread(() -> { });
206 
207         assertTrue(thread4.getName().equals("duke-103"));
208         assertTrue(thread5.getName().equals("duke-104"));
209         assertTrue(thread6.getName().equals("duke-105"));
210     }
211 
212     /**
213      * Test Thread.Builder.OfPlatform.group.
214      */
215     @Test
216     void testThreadGroup1() {
217         ThreadGroup group = new ThreadGroup("groupies");
218         Thread.Builder builder = Thread.ofPlatform().group(group);
219 
220         Thread thread1 = builder.unstarted(() -> { });
221 
222         AtomicBoolean done = new AtomicBoolean();
223         Thread thread2 = builder.start(() -> {
224             while (!done.get()) {
225                 LockSupport.park();
226             }
227         });
228 
229         Thread thread3 = builder.factory().newThread(() -> { });
230 
231         try {
232             assertTrue(thread1.getThreadGroup() == group);
233             assertTrue(thread2.getThreadGroup() == group);
234             assertTrue(thread3.getThreadGroup() == group);
235         } finally {
236             done.set(true);
237             LockSupport.unpark(thread2);
238         }
239     }
240 
241     @Test
242     void testThreadGroup2() {
243         ThreadGroup vgroup = Thread.ofVirtual().unstarted(() -> { }).getThreadGroup();
244         assertEquals("VirtualThreads", vgroup.getName());
245 
246         Thread thread1 = Thread.ofVirtual().unstarted(() -> { });
247         Thread thread2 = Thread.ofVirtual().start(LockSupport::park);
248         Thread thread3 = Thread.ofVirtual().factory().newThread(() -> { });
249 
250         try {
251             assertTrue(thread1.getThreadGroup() == vgroup);
252             assertTrue(thread2.getThreadGroup() == vgroup);
253             assertTrue(thread3.getThreadGroup() == vgroup);
254         } finally {
255             LockSupport.unpark(thread2);
256         }
257     }
258 
259     /**
260      * Test Thread.Builder.OfPlatform.priority.
261      */
262     @Test
263     void testPriority1() {
264         int priority = Thread.currentThread().getPriority();
265 
266         Thread.Builder builder = Thread.ofPlatform();
267         Thread thread1 = builder.unstarted(() -> { });
268         Thread thread2 = builder.start(() -> { });
269         Thread thread3 = builder.factory().newThread(() -> { });
270 
271         assertTrue(thread1.getPriority() == priority);
272         assertTrue(thread2.getPriority() == priority);
273         assertTrue(thread3.getPriority() == priority);
274     }
275 
276     @Test
277     void testPriority2() {
278         int priority = Thread.MIN_PRIORITY;
279 
280         Thread.Builder builder = Thread.ofPlatform().priority(priority);
281         Thread thread1 = builder.unstarted(() -> { });
282         Thread thread2 = builder.start(() -> { });
283         Thread thread3 = builder.factory().newThread(() -> { });
284 
285         assertTrue(thread1.getPriority() == priority);
286         assertTrue(thread2.getPriority() == priority);
287         assertTrue(thread3.getPriority() == priority);
288     }
289 
290     @Test
291     void testPriority3() {
292         Thread currentThread = Thread.currentThread();
293         assumeFalse(currentThread.isVirtual(), "Main thread is a virtual thread");
294 
295         int maxPriority = currentThread.getThreadGroup().getMaxPriority();
296         int priority = Math.min(maxPriority + 1, Thread.MAX_PRIORITY);
297 
298         Thread.Builder builder = Thread.ofPlatform().priority(priority);
299         Thread thread1 = builder.unstarted(() -> { });
300         Thread thread2 = builder.start(() -> { });
301         Thread thread3 = builder.factory().newThread(() -> { });
302 
303         assertTrue(thread1.getPriority() == priority);
304         assertTrue(thread2.getPriority() == priority);
305         assertTrue(thread3.getPriority() == priority);
306     }
307 
308     @Test
309     void testPriority4() {
310         var builder = Thread.ofPlatform();
311         assertThrows(IllegalArgumentException.class,
312                      () -> builder.priority(Thread.MIN_PRIORITY - 1));
313     }
314 
315     @Test
316     void testPriority5() {
317         var builder = Thread.ofPlatform();
318         assertThrows(IllegalArgumentException.class,
319                      () -> builder.priority(Thread.MAX_PRIORITY + 1));
320     }
321 
322     /**
323      * Test Thread.Builder.OfPlatform.daemon.
324      */
325     @Test
326     void testDaemon1() {
327         Thread.Builder builder = Thread.ofPlatform().daemon(false);
328 
329         Thread thread1 = builder.unstarted(() -> { });
330         Thread thread2 = builder.start(() -> { });
331         Thread thread3 = builder.factory().newThread(() -> { });
332 
333         assertFalse(thread1.isDaemon());
334         assertFalse(thread2.isDaemon());
335         assertFalse(thread3.isDaemon());
336     }
337 
338     @Test
339     void testDaemon2() {
340         Thread.Builder builder = Thread.ofPlatform().daemon(true);
341 
342         Thread thread1 = builder.unstarted(() -> { });
343         Thread thread2 = builder.start(() -> { });
344         Thread thread3 = builder.factory().newThread(() -> { });
345 
346         assertTrue(thread1.isDaemon());
347         assertTrue(thread2.isDaemon());
348         assertTrue(thread3.isDaemon());
349     }
350 
351     @Test
352     void testDaemon3() {
353         Thread.Builder builder = Thread.ofPlatform().daemon();
354 
355         Thread thread1 = builder.unstarted(() -> { });
356         Thread thread2 = builder.start(() -> { });
357         Thread thread3 = builder.factory().newThread(() -> { });
358 
359         assertTrue(thread1.isDaemon());
360         assertTrue(thread2.isDaemon());
361         assertTrue(thread3.isDaemon());
362     }
363 
364     @Test
365     void testDaemon4() {
366         Thread.Builder builder = Thread.ofPlatform();
367 
368         Thread thread1 = builder.unstarted(() -> { });
369         Thread thread2 = builder.start(() -> { });
370         Thread thread3 = builder.factory().newThread(() -> { });
371 
372         // daemon status should be inherited
373         boolean d = Thread.currentThread().isDaemon();
374         assertTrue(thread1.isDaemon() == d);
375         assertTrue(thread2.isDaemon() == d);
376         assertTrue(thread3.isDaemon() == d);
377     }
378 
379     /**
380      * Test Thread.ofVirtual creates daemon threads.
381      */
382     @Test
383     void testDaemon5() {
384         Thread.Builder builder = Thread.ofVirtual();
385 
386         Thread thread1 = builder.unstarted(() -> { });
387         Thread thread2 = builder.start(() -> { });
388         Thread thread3 = builder.factory().newThread(() -> { });
389 
390         // daemon status should always be true
391         assertTrue(thread1.isDaemon());
392         assertTrue(thread2.isDaemon());
393         assertTrue(thread3.isDaemon());
394     }
395 
396     /**
397      * Test Thread.Builder.OfPlatform.stackSize.
398      */
399     @Test
400     void testStackSize1() {
401         Thread.Builder builder = Thread.ofPlatform().stackSize(1024*1024);
402         Thread thread1 = builder.unstarted(() -> { });
403         Thread thread2 = builder.start(() -> { });
404         Thread thread3 = builder.factory().newThread(() -> { });
405     }
406 
407     @Test
408     void testStackSize2() {
409         Thread.Builder builder = Thread.ofPlatform().stackSize(0);
410         Thread thread1 = builder.unstarted(() -> { });
411         Thread thread2 = builder.start(() -> { });
412         Thread thread3 = builder.factory().newThread(() -> { });
413     }
414 
415     @Test
416     void testStackSize3() {
417         var builder = Thread.ofPlatform();
418         assertThrows(IllegalArgumentException.class, () -> builder.stackSize(-1));
419     }
420 
421     /**
422      * Test Thread.Builder.uncaughtExceptionHandler.
423      */
424     @Test
425     void testUncaughtExceptionHandler1() throws Exception {
426         class FooException extends RuntimeException { }
427         AtomicReference<Thread> threadRef = new AtomicReference<>();
428         AtomicReference<Throwable> exceptionRef = new AtomicReference<>();
429         Thread thread = Thread.ofPlatform()
430                 .uncaughtExceptionHandler((t, e) -> {
431                     assertTrue(t == Thread.currentThread());
432                     threadRef.set(t);
433                     exceptionRef.set(e);
434                 })
435                 .start(() -> { throw new FooException(); });
436         thread.join();
437         assertTrue(threadRef.get() == thread);
438         assertTrue(exceptionRef.get() instanceof FooException);
439     }
440 
441     @Test
442     void testUncaughtExceptionHandler2() throws Exception {
443         class FooException extends RuntimeException { }
444         AtomicReference<Thread> threadRef = new AtomicReference<>();
445         AtomicReference<Throwable> exceptionRef = new AtomicReference<>();
446         Thread thread = Thread.ofVirtual()
447                 .uncaughtExceptionHandler((t, e) -> {
448                     assertTrue(t == Thread.currentThread());
449                     threadRef.set(t);
450                     exceptionRef.set(e);
451                 })
452                 .start(() -> { throw new FooException(); });
453         thread.join();
454         assertTrue(threadRef.get() == thread);
455         assertTrue(exceptionRef.get() instanceof FooException);
456     }
457 
458     @Test
459     void testUncaughtExceptionHandler3() throws Exception {
460         class FooException extends RuntimeException { }
461         AtomicReference<Thread> threadRef = new AtomicReference<>();
462         AtomicReference<Throwable> exceptionRef = new AtomicReference<>();
463         Thread thread = Thread.ofPlatform()
464                 .uncaughtExceptionHandler((t, e) -> {
465                     assertTrue(t == Thread.currentThread());
466                     threadRef.set(t);
467                     exceptionRef.set(e);
468                 })
469                 .factory()
470                 .newThread(() -> { throw new FooException(); });
471         thread.start();
472         thread.join();
473         assertTrue(threadRef.get() == thread);
474         assertTrue(exceptionRef.get() instanceof FooException);
475     }
476 
477     @Test
478     void testUncaughtExceptionHandler4() throws Exception {
479         class FooException extends RuntimeException { }
480         AtomicReference<Thread> threadRef = new AtomicReference<>();
481         AtomicReference<Throwable> exceptionRef = new AtomicReference<>();
482         Thread thread = Thread.ofPlatform()
483                 .uncaughtExceptionHandler((t, e) -> {
484                     assertTrue(t == Thread.currentThread());
485                     threadRef.set(t);
486                     exceptionRef.set(e);
487                 })
488                 .factory()
489                 .newThread(() -> { throw new FooException(); });
490         thread.start();
491         thread.join();
492         assertTrue(threadRef.get() == thread);
493         assertTrue(exceptionRef.get() instanceof FooException);
494     }
495 
496     static final ThreadLocal<Object> LOCAL = new ThreadLocal<>();
497     static final ThreadLocal<Object> INHERITED_LOCAL = new InheritableThreadLocal<>();
498 
499     /**
500      * Tests that a builder creates threads that support thread locals
501      */
502     private void testThreadLocals(Thread.Builder builder) throws Exception {
503         AtomicBoolean done = new AtomicBoolean();
504         Runnable task = () -> {
505             Object value = new Object();
506             LOCAL.set(value);
507             assertTrue(LOCAL.get() == value);
508             done.set(true);
509         };
510 
511         done.set(false);
512         Thread thread1 = builder.unstarted(task);
513         thread1.start();
514         thread1.join();
515         assertTrue(done.get());
516 
517         done.set(false);
518         Thread thread2 = builder.start(task);
519         thread2.join();
520         assertTrue(done.get());
521 
522         done.set(false);
523         Thread thread3 = builder.factory().newThread(task);
524         thread3.start();
525         thread3.join();
526         assertTrue(done.get());
527     }
528 
529     /**
530      * Tests that a builder creates threads that do not support thread locals
531      */
532     private void testNoThreadLocals(Thread.Builder builder) throws Exception {
533         AtomicBoolean done = new AtomicBoolean();
534         Runnable task = () -> {
535             try {
536                 LOCAL.set(new Object());
537             } catch (UnsupportedOperationException expected) {
538                 done.set(true);
539             }
540         };
541 
542         done.set(false);
543         Thread thread1 = builder.unstarted(task);
544         thread1.start();
545         thread1.join();
546         assertTrue(done.get());
547 
548         done.set(false);
549         Thread thread2 = builder.start(task);
550         thread2.join();
551         assertTrue(done.get());
552 
553         done.set(false);
554         Thread thread3 = builder.factory().newThread(task);
555         thread3.start();
556         thread3.join();
557         assertTrue(done.get());
558     }
559 
560     /**
561      * Test Thread.Builder creates threads that allow thread locals.
562      */
563     @Test
564     void testThreadLocals1() throws Exception {
565         Thread.Builder builder = Thread.ofPlatform();
566         testThreadLocals(builder);
567     }
568 
569     @Test
570     void testThreadLocals2() throws Exception {
571         Thread.Builder builder = Thread.ofVirtual();
572         testThreadLocals(builder);
573     }
574 
575     /**
576      * Tests that a builder creates threads that inherits the initial values of
577      * inheritable thread locals.
578      */
579     private void testInheritedThreadLocals(Thread.Builder builder) throws Exception {
580         Object value = new Object();
581         INHERITED_LOCAL.set(value);
582 
583         AtomicBoolean done = new AtomicBoolean();
584         Runnable task = () -> {
585             assertTrue(INHERITED_LOCAL.get() == value);
586             done.set(true);
587         };
588 
589         done.set(false);
590         Thread thread1 = builder.unstarted(task);
591         thread1.start();
592         thread1.join();
593         assertTrue(done.get());
594 
595         done.set(false);
596         Thread thread2 = builder.start(task);
597         thread2.join();
598         assertTrue(done.get());
599 
600         done.set(false);
601         Thread thread3 = builder.factory().newThread(task);
602         thread3.start();
603         thread3.join();
604         assertTrue(done.get());
605     }
606 
607     /**
608      * Tests that a builder creates threads that do not inherit the initial values
609      * of inheritable thread locals.
610      */
611     private void testNoInheritedThreadLocals(Thread.Builder builder) throws Exception {
612         Object value = new Object();
613         INHERITED_LOCAL.set(value);
614 
615         AtomicBoolean done = new AtomicBoolean();
616         Runnable task = () -> {
617             assertNull(INHERITED_LOCAL.get());
618             done.set(true);
619         };
620 
621         done.set(false);
622         Thread thread1 = builder.unstarted(task);
623         thread1.start();
624         thread1.join();
625         assertTrue(done.get());
626 
627         done.set(false);
628         Thread thread2 = builder.start(task);
629         thread2.join();
630         assertTrue(done.get());
631 
632         done.set(false);
633         Thread thread3 = builder.factory().newThread(task);
634         thread3.start();
635         thread3.join();
636         assertTrue(done.get());
637     }
638 
639     /**
640      * Test Thread.Builder creating threads that inherit or do not inherit
641      * the initial values of inheritable thread locals.
642      */
643     @Test
644     void testInheritedThreadLocals1() throws Exception {
645         Thread.Builder builder = Thread.ofPlatform();
646         testInheritedThreadLocals(builder); // default
647 
648         // do no inherit
649         builder.inheritInheritableThreadLocals(false);
650         testNoInheritedThreadLocals(builder);
651 
652         // inherit
653         builder.inheritInheritableThreadLocals(true);
654         testInheritedThreadLocals(builder);
655     }
656 
657     @Test
658     void testInheritedThreadLocals2() throws Exception {
659         Thread.Builder builder = Thread.ofVirtual();
660         testInheritedThreadLocals(builder); // default
661 
662         // do no inherit
663         builder.inheritInheritableThreadLocals(false);
664         testNoInheritedThreadLocals(builder);
665 
666         // inherit
667         builder.inheritInheritableThreadLocals(true);
668         testInheritedThreadLocals(builder);
669     }
670 
671     /**
672      * Tests a builder creates threads that inherit the context class loader.
673      */
674     private void testInheritContextClassLoader(Thread.Builder builder) throws Exception {
675         ClassLoader savedCCL = Thread.currentThread().getContextClassLoader();
676         try {
677             ClassLoader loader = new ClassLoader() { };
678             Thread.currentThread().setContextClassLoader(loader);
679 
680             var ref = new AtomicReference<ClassLoader>();
681             Runnable task = () -> {
682                 ref.set(Thread.currentThread().getContextClassLoader());
683             };
684 
685             // unstarted
686             Thread thread1 = builder.unstarted(task);
687             assertTrue(thread1.getContextClassLoader() == loader);
688 
689             // started
690             ref.set(null);
691             thread1.start();
692             thread1.join();
693             assertTrue(ref.get() == loader);
694 
695             // factory
696             Thread thread2 = builder.factory().newThread(task);
697             assertTrue(thread2.getContextClassLoader() == loader);
698 
699             // started
700             ref.set(null);
701             thread2.start();
702             thread2.join();
703             assertTrue(ref.get() == loader);
704 
705         } finally {
706             Thread.currentThread().setContextClassLoader(savedCCL);
707         }
708     }
709 
710     /**
711      * Tests a builder creates threads does not inherit the context class loader.
712      */
713     private void testNoInheritContextClassLoader(Thread.Builder builder) throws Exception {
714         ClassLoader savedCCL = Thread.currentThread().getContextClassLoader();
715         try {
716             Thread.currentThread().setContextClassLoader( new ClassLoader() { });
717 
718             ClassLoader scl = ClassLoader.getSystemClassLoader();
719 
720             var ref = new AtomicReference<ClassLoader>();
721             Runnable task = () -> {
722                 ref.set(Thread.currentThread().getContextClassLoader());
723             };
724 
725             // unstarted
726             Thread thread1 = builder.unstarted(task);
727             assertTrue(thread1.getContextClassLoader() == scl);
728 
729             // started
730             ref.set(null);
731             thread1.start();
732             thread1.join();
733             assertTrue(ref.get() == scl);
734 
735             // factory
736             Thread thread2 = builder.factory().newThread(task);
737             assertTrue(thread2.getContextClassLoader() == scl);
738 
739             // started
740             ref.set(null);
741             thread2.start();
742             thread2.join();
743             assertTrue(ref.get() == scl);
744 
745         } finally {
746             Thread.currentThread().setContextClassLoader(savedCCL);
747         }
748     }
749 
750     /**
751      * Test Thread.Builder creating threads that inherit or do not inherit
752      * the thread context class loader.
753      */
754     @Test
755     void testContextClassLoader1() throws Exception {
756         Thread.Builder builder = Thread.ofPlatform();
757         testInheritContextClassLoader(builder); // default
758 
759         // do no inherit
760         builder.inheritInheritableThreadLocals(false);
761         testNoInheritContextClassLoader(builder);
762 
763         // inherit
764         builder.inheritInheritableThreadLocals(true);
765         testInheritContextClassLoader(builder);
766     }
767 
768     @Test
769     void testContextClassLoader2() throws Exception {
770         Thread.Builder builder = Thread.ofVirtual();
771         testInheritContextClassLoader(builder); // default
772 
773         // do no inherit
774         builder.inheritInheritableThreadLocals(false);
775         testNoInheritContextClassLoader(builder);
776 
777         // inherit
778         builder.inheritInheritableThreadLocals(true);
779         testInheritContextClassLoader(builder);
780     }
781 
782     /**
783      * Test NullPointerException.
784      */
785     @Test
786     void testNulls1() {
787         Thread.Builder.OfPlatform builder = Thread.ofPlatform();
788         assertThrows(NullPointerException.class, () -> builder.group(null));
789         assertThrows(NullPointerException.class, () -> builder.name(null));
790         assertThrows(NullPointerException.class, () -> builder.name(null, 0));
791         assertThrows(NullPointerException.class, () -> builder.uncaughtExceptionHandler(null));
792         assertThrows(NullPointerException.class, () -> builder.unstarted(null));
793         assertThrows(NullPointerException.class, () -> builder.start(null));
794     }
795 
796     @Test
797     void testNulls2() {
798         Thread.Builder builder = Thread.ofVirtual();
799         assertThrows(NullPointerException.class, () -> builder.name(null));
800         assertThrows(NullPointerException.class, () -> builder.name(null, 0));
801         assertThrows(NullPointerException.class, () -> builder.uncaughtExceptionHandler(null));
802         assertThrows(NullPointerException.class, () -> builder.unstarted(null));
803         assertThrows(NullPointerException.class, () -> builder.start(null));
804     }
805 }