View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   * https://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.commons.lang3;
21  
22  import static org.apache.commons.lang3.LangAssertions.assertIllegalArgumentException;
23  import static org.apache.commons.lang3.LangAssertions.assertNullPointerException;
24  import static org.junit.jupiter.api.Assertions.assertEquals;
25  import static org.junit.jupiter.api.Assertions.assertFalse;
26  import static org.junit.jupiter.api.Assertions.assertNotNull;
27  import static org.junit.jupiter.api.Assertions.assertNull;
28  import static org.junit.jupiter.api.Assertions.assertSame;
29  import static org.junit.jupiter.api.Assertions.assertTrue;
30  
31  import java.lang.reflect.Constructor;
32  import java.lang.reflect.Modifier;
33  import java.time.Duration;
34  import java.util.Arrays;
35  import java.util.Collection;
36  import java.util.List;
37  import java.util.Objects;
38  import java.util.concurrent.CountDownLatch;
39  import java.util.concurrent.atomic.AtomicBoolean;
40  import java.util.function.Predicate;
41  
42  import org.apache.commons.lang3.ThreadUtils.ThreadGroupPredicate;
43  import org.apache.commons.lang3.ThreadUtils.ThreadPredicate;
44  import org.apache.commons.lang3.function.Predicates;
45  import org.junit.jupiter.api.Test;
46  
47  /**
48   * Tests {@link ThreadUtils}.
49   */
50  class ThreadUtilsTest extends AbstractLangTest {
51  
52      private static final class TestThread extends Thread {
53          private final CountDownLatch latch = new CountDownLatch(1);
54  
55          TestThread(final String name) {
56              super(name);
57          }
58  
59          TestThread(final ThreadGroup group, final String name) {
60              super(group, name);
61          }
62  
63          @Override
64          public void run() {
65              latch.countDown();
66              try {
67                  synchronized (this) {
68                      this.wait();
69                  }
70              } catch (final InterruptedException e) {
71                  Thread.currentThread().interrupt();
72              }
73          }
74  
75          @Override
76          public synchronized void start() {
77              super.start();
78              try {
79                  latch.await();
80              } catch (final InterruptedException e) {
81                  Thread.currentThread().interrupt();
82              }
83          }
84      }
85  
86      @Test
87      void testAtLeastOneThreadExists() {
88          assertFalse(ThreadUtils.getAllThreads().isEmpty());
89      }
90  
91      @Test
92      void testAtLeastOneThreadGroupsExists() {
93          assertFalse(ThreadUtils.getAllThreadGroups().isEmpty());
94      }
95  
96      @Test
97      void testComplexThreadGroups() throws Exception {
98          final ThreadGroup threadGroup1 = new ThreadGroup("thread_group_1__");
99          final ThreadGroup threadGroup2 = new ThreadGroup("thread_group_2__");
100         final ThreadGroup threadGroup3 = new ThreadGroup(threadGroup2, "thread_group_3__");
101         final ThreadGroup threadGroup4 = new ThreadGroup(threadGroup2, "thread_group_4__");
102         final ThreadGroup threadGroup5 = new ThreadGroup(threadGroup1, "thread_group_5__");
103         final ThreadGroup threadGroup6 = new ThreadGroup(threadGroup4, "thread_group_6__");
104         final ThreadGroup threadGroup7 = new ThreadGroup(threadGroup4, "thread_group_7__");
105         final ThreadGroup threadGroup7Doubled = new ThreadGroup(threadGroup4, "thread_group_7__");
106         final List<ThreadGroup> threadGroups = Arrays.asList(threadGroup1, threadGroup2, threadGroup3, threadGroup4, threadGroup5, threadGroup6, threadGroup7,
107             threadGroup7Doubled);
108 
109         final Thread t1 = new TestThread("thread1_X__");
110         final Thread t2 = new TestThread(threadGroup1, "thread2_X__");
111         final Thread t3 = new TestThread(threadGroup2, "thread3_X__");
112         final Thread t4 = new TestThread(threadGroup3, "thread4_X__");
113         final Thread t5 = new TestThread(threadGroup4, "thread5_X__");
114         final Thread t6 = new TestThread(threadGroup5, "thread6_X__");
115         final Thread t7 = new TestThread(threadGroup6, "thread7_X__");
116         final Thread t8 = new TestThread(threadGroup4, "thread8_X__");
117         final Thread t9 = new TestThread(threadGroup6, "thread9_X__");
118         final Thread t10 = new TestThread(threadGroup3, "thread10_X__");
119         final Thread t11 = new TestThread(threadGroup7, "thread11_X__");
120         final Thread t11Doubled = new TestThread(threadGroup7Doubled, "thread11_X__");
121         final List<Thread> threads = Arrays.asList(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t11Doubled);
122 
123         try {
124             for (final Thread thread : threads) {
125                 thread.start();
126             }
127             assertTrue(ThreadUtils.getAllThreadGroups().size() >= 7, "getAllThreadGroups");
128             assertTrue(ThreadUtils.getAllThreads().size() >= 11, "getAllThreads");
129             assertTrue(ThreadUtils.findThreads(Predicates.truePredicate()).size() >= 11, "findThreads(ThreadUtils.truePredicate())");
130             assertEquals(1, ThreadUtils.findThreadsByName(t4.getName(), threadGroup3.getName()).size());
131             assertEquals(0, ThreadUtils.findThreadsByName(t4.getName(), threadGroup2.getName()).size());
132             assertEquals(2, ThreadUtils.findThreadsByName(t11.getName(), threadGroup7.getName()).size());
133         } finally {
134             for (final Thread thread : threads) {
135                 thread.interrupt();
136                 thread.join();
137             }
138             for (final ThreadGroup threadGroup : threadGroups) {
139                 if (!threadGroup.isDestroyed()) {
140                     threadGroup.destroy();
141                 }
142             }
143         }
144     }
145 
146     @Test
147     void testConstructor() {
148         assertNotNull(new ThreadUtils());
149         final Constructor<?>[] cons = ThreadUtils.class.getDeclaredConstructors();
150         assertEquals(1, cons.length);
151         assertTrue(Modifier.isPublic(cons[0].getModifiers()));
152         assertTrue(Modifier.isPublic(ThreadUtils.class.getModifiers()));
153         assertFalse(Modifier.isFinal(ThreadUtils.class.getModifiers()));
154     }
155 
156     @SuppressWarnings("deprecation")
157     @Test
158     void testDepreacted() {
159         assertNotNull(ThreadUtils.ALWAYS_TRUE_PREDICATE);
160         final ThreadPredicate tp = ThreadUtils.ALWAYS_TRUE_PREDICATE;
161         assertTrue(tp.test(null));
162         assertTrue(tp.test(new Thread()));
163         final ThreadGroupPredicate tgp = ThreadUtils.ALWAYS_TRUE_PREDICATE;
164         assertTrue(tgp.test(null));
165         assertTrue(tgp.test(new ThreadGroup("")));
166     }
167 
168     @Test
169     void testGetAllThreadGroupsDoesNotReturnNull() {
170         // LANG-1706 getAllThreadGroups and findThreadGroups should not return null items
171         final Collection<ThreadGroup> threads = ThreadUtils.getAllThreadGroups();
172         assertEquals(0, threads.stream().filter(Objects::isNull).count());
173     }
174 
175     @Test
176     void testGetAllThreadsDoesNotReturnNull() {
177         // LANG-1706 getAllThreads and findThreads should not return null items
178         final Collection<Thread> threads = ThreadUtils.getAllThreads();
179         assertEquals(0, threads.stream().filter(Objects::isNull).count());
180     }
181 
182     @Test
183     void testInvalidThreadId() {
184         assertIllegalArgumentException(() -> ThreadUtils.findThreadById(-5L));
185     }
186 
187     @Test
188     void testJoinDuration() throws InterruptedException {
189         ThreadUtils.join(new Thread(), Duration.ofMillis(1));
190         ThreadUtils.join(new Thread(), Duration.ofMillis(2));
191     }
192 
193     @Test
194     void testJoinDurationZero() throws InterruptedException {
195         // This test doesn't wait forever because the thread is not alive, so the call returns immediately.
196         ThreadUtils.join(new Thread(), Duration.ZERO);
197     }
198 
199     @Test
200     void testNoThread() {
201         assertEquals(0, ThreadUtils.findThreadsByName("some_thread_which_does_not_exist_18762ZucTT").size());
202     }
203 
204     @Test
205     void testNoThreadGroup() {
206         assertEquals(0, ThreadUtils.findThreadGroupsByName("some_thread_group_which_does_not_exist_18762ZucTTII").size());
207     }
208 
209     @Test
210     void testNullThreadGroupName() {
211         assertNullPointerException(() -> ThreadUtils.findThreadGroupsByName(null));
212     }
213 
214     @Test
215     void testNullThreadName() {
216         assertNullPointerException(() -> ThreadUtils.findThreadsByName(null));
217     }
218 
219     @Test
220     void testNullThreadThreadGroup1() {
221         assertNullPointerException(() -> ThreadUtils.findThreadsByName("tname", (ThreadGroup) null));
222     }
223 
224     @Test
225     void testNullThreadThreadGroup2() {
226         assertNullPointerException(() -> ThreadUtils.findThreadById(1L, (ThreadGroup) null));
227     }
228 
229     @Test
230     void testNullThreadThreadGroup3() {
231         assertNullPointerException(() -> ThreadUtils.findThreadsByName(null, (ThreadGroup) null));
232     }
233 
234     @Test
235     void testNullThreadThreadGroupName1() {
236         assertNullPointerException(() -> ThreadUtils.findThreadsByName(null, "tgname"));
237     }
238 
239     @Test
240     void testNullThreadThreadGroupName2() {
241         assertNullPointerException(() -> ThreadUtils.findThreadsByName("tname", (String) null));
242     }
243 
244     @Test
245     void testNullThreadThreadGroupName3() {
246         assertNullPointerException(() -> ThreadUtils.findThreadsByName(null, (String) null));
247     }
248 
249     @Test
250     void testSleepDuration() throws InterruptedException {
251         ThreadUtils.sleep(Duration.ZERO);
252         ThreadUtils.sleep(Duration.ofMillis(1));
253     }
254 
255     @Test
256     void testSleepQuietlyInterruptDuringSleep() throws Exception {
257         final AtomicBoolean isInterrupted = new AtomicBoolean();
258         final Thread testThread = new Thread(() -> {
259             // This will be interrupted while sleeping
260             ThreadUtils.sleepQuietly(Duration.ofSeconds(10));
261             isInterrupted.set(Thread.currentThread().isInterrupted());
262         });
263         testThread.start();
264         Thread.sleep(100);
265         testThread.interrupt();
266         testThread.join(1000);
267         assertTrue(isInterrupted.get(), "Interrupted flag should be preserved after InterruptedException");
268     }
269 
270     @Test
271     void testSleepQuietlyInterruptedFlagPreserved() {
272         Thread.currentThread().interrupt();
273         // Immediately throw InterruptedException and restores the interrupted status
274         ThreadUtils.sleepQuietly(Duration.ofMillis(1000));
275         assertTrue(Thread.interrupted(), "Interrupted flag should be preserved");
276     }
277 
278     @Test
279     void testSleepQuietlyNormalNoInterrupt() {
280         Thread.interrupted();
281         ThreadUtils.sleepQuietly(Duration.ofMillis(10));
282         assertFalse(Thread.currentThread().isInterrupted(), "Interrupted flag should not be set for normal sleep");
283     }
284 
285     @Test
286     void testSystemThreadGroupExists() {
287         final ThreadGroup systemThreadGroup = ThreadUtils.getSystemThreadGroup();
288         assertNotNull(systemThreadGroup);
289         assertNull(systemThreadGroup.getParent());
290         assertEquals("system", systemThreadGroup.getName());
291     }
292 
293     @Test
294     void testThreadGroups() throws InterruptedException {
295         final String threadGroupName = "thread_group_DDZZ99__for_testThreadGroups";
296         final ThreadGroup threadGroup = new ThreadGroup(threadGroupName);
297         final Thread t1 = new TestThread(threadGroup, "thread1_XXOOPP__");
298         final Thread t2 = new TestThread(threadGroup, "thread2_XXOOPP__");
299 
300         try {
301             t1.start();
302             t2.start();
303             assertEquals(1, ThreadUtils.findThreadsByName("thread1_XXOOPP__").size());
304             assertEquals(1, ThreadUtils.findThreadsByName("thread1_XXOOPP__", threadGroupName).size());
305             assertEquals(1, ThreadUtils.findThreadsByName("thread2_XXOOPP__", threadGroupName).size());
306             assertEquals(0, ThreadUtils.findThreadsByName("thread1_XXOOPP__", "non_existent_thread_group_JJHHZZ__").size());
307             assertEquals(0, ThreadUtils.findThreadsByName("non_existent_thread_BBDDWW__", threadGroupName).size());
308             assertEquals(1, ThreadUtils.findThreadGroupsByName(threadGroupName).size());
309             assertEquals(0, ThreadUtils.findThreadGroupsByName("non_existent_thread_group_JJHHZZ__").size());
310             assertNotNull(ThreadUtils.findThreadById(t1.getId(), threadGroup));
311         } finally {
312             t1.interrupt();
313             t2.interrupt();
314             t1.join();
315             t2.join();
316             threadGroup.destroy();
317         }
318     }
319 
320     @Test
321     void testThreadGroupsById() throws InterruptedException {
322         final String threadGroupName = "thread_group_DDZZ99__for_testThreadGroupsById";
323         final ThreadGroup threadGroup = new ThreadGroup(threadGroupName);
324         final Thread t1 = new TestThread(threadGroup, "thread1_XXOOPP__");
325         final Thread t2 = new TestThread(threadGroup, "thread2_XXOOPP__");
326         final long nonExistingId = t1.getId() + t2.getId();
327 
328         try {
329             t1.start();
330             t2.start();
331             assertSame(t1, ThreadUtils.findThreadById(t1.getId(), threadGroupName));
332             assertSame(t2, ThreadUtils.findThreadById(t2.getId(), threadGroupName));
333             assertNull(ThreadUtils.findThreadById(nonExistingId, "non_existent_thread_group_JJHHZZ__"));
334             assertNull(ThreadUtils.findThreadById(nonExistingId, threadGroupName));
335         } finally {
336             t1.interrupt();
337             t2.interrupt();
338             t1.join();
339             t2.join();
340             threadGroup.destroy();
341         }
342     }
343 
344     @Test
345     void testThreadGroupsByIdFail() {
346         assertNullPointerException(() -> ThreadUtils.findThreadById(Thread.currentThread().getId(), (String) null));
347     }
348 
349     @Test
350     void testThreadGroupsNullParent() {
351         assertNullPointerException(() -> ThreadUtils.findThreadGroups(null, true, Predicates.truePredicate()));
352         assertNullPointerException(() -> ThreadUtils.findThreadGroups(null, false, Predicates.truePredicate()));
353     }
354 
355     @Test
356     void testThreadGroupsNullPredicate() {
357         assertNullPointerException(() -> ThreadUtils.findThreadGroups((ThreadGroupPredicate) null));
358         assertNullPointerException(() -> ThreadUtils.findThreadGroups((Predicate<ThreadGroup>) null));
359         assertNullPointerException(() -> ThreadUtils.findThreadGroups((Predicate) null));
360     }
361 
362     @Test
363     void testThreadGroupsRef() throws InterruptedException {
364         final ThreadGroup threadGroup = new ThreadGroup("thread_group_DDZZ99__");
365         final ThreadGroup deadThreadGroup = new ThreadGroup("dead_thread_group_MMQQSS__");
366         deadThreadGroup.destroy();
367         final Thread t1 = new TestThread(threadGroup, "thread1_XXOOPP__");
368         final Thread t2 = new TestThread(threadGroup, "thread2_XXOOPP__");
369 
370         try {
371             t1.start();
372             t2.start();
373             assertEquals(1, ThreadUtils.findThreadsByName("thread1_XXOOPP__").size());
374             assertEquals(1, ThreadUtils.findThreadsByName("thread1_XXOOPP__", threadGroup).size());
375             assertEquals(1, ThreadUtils.findThreadsByName("thread2_XXOOPP__", threadGroup).size());
376             assertEquals(0, ThreadUtils.findThreadsByName("thread1_XXOOPP__", deadThreadGroup).size());
377         } finally {
378             t1.interrupt();
379             t2.interrupt();
380             t1.join();
381             t2.join();
382             threadGroup.destroy();
383             assertEquals(0, ThreadUtils.findThreadsByName("thread2_XXOOPP__", threadGroup).size());
384         }
385     }
386 
387     @Test
388     void testThreads() throws InterruptedException {
389         final Thread t1 = new TestThread("thread1_XXOOLL__");
390         final Thread t2 = new TestThread("thread2_XXOOLL__");
391 
392         try {
393             t1.start();
394             t2.start();
395             assertEquals(1, ThreadUtils.findThreadsByName("thread2_XXOOLL__").size());
396         } finally {
397             t1.interrupt();
398             t2.interrupt();
399             t1.join();
400             t2.join();
401         }
402     }
403 
404     @Test
405     void testThreadsById() throws InterruptedException {
406         final Thread t1 = new TestThread("thread1_XXOOLL__");
407         final Thread t2 = new TestThread("thread2_XXOOLL__");
408 
409         try {
410             t1.start();
411             t2.start();
412             assertSame(t1, ThreadUtils.findThreadById(t1.getId()));
413             assertSame(t2, ThreadUtils.findThreadById(t2.getId()));
414         } finally {
415             t1.interrupt();
416             t2.interrupt();
417             t1.join();
418             t2.join();
419         }
420     }
421 
422     @Test
423     void testThreadsByIdWrongGroup() throws InterruptedException {
424         final Thread t1 = new TestThread("thread1_XXOOLL__");
425         final ThreadGroup tg = new ThreadGroup("tg__HHEE22");
426 
427         try {
428             t1.start();
429             assertNull(ThreadUtils.findThreadById(t1.getId(), tg));
430         } finally {
431             t1.interrupt();
432             t1.join();
433             tg.destroy();
434         }
435     }
436 
437     @Test
438     void testThreadsNullPredicate() {
439         assertNullPointerException(() -> ThreadUtils.findThreads((ThreadPredicate) null));
440         assertNullPointerException(() -> ThreadUtils.findThreads((Predicate<Thread>) null));
441         assertNullPointerException(() -> ThreadUtils.findThreads((Predicate) null));
442     }
443 
444     @Test
445     void testThreadsSameName() throws InterruptedException {
446         final Thread t1 = new TestThread("thread1_XXOOLL__");
447         final Thread alsot1 = new TestThread("thread1_XXOOLL__");
448 
449         try {
450             t1.start();
451             alsot1.start();
452             assertEquals(2, ThreadUtils.findThreadsByName("thread1_XXOOLL__").size());
453         } finally {
454             t1.interrupt();
455             alsot1.interrupt();
456             t1.join();
457             alsot1.join();
458         }
459     }
460 
461 }