diff --git a/src/main/java/org/apache/commons/lang3/ThreadUtils.java b/src/main/java/org/apache/commons/lang3/ThreadUtils.java new file mode 100644 index 000000000..602facee9 --- /dev/null +++ b/src/main/java/org/apache/commons/lang3/ThreadUtils.java @@ -0,0 +1,459 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.lang3; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + *

+ * Helpers for {@code java.lang.Thread} and {@code java.lang.ThreadGroup}. + *

+ *

+ * #ThreadSafe# + *

+ * + * @see java.lang.Thread + * @see java.lang.ThreadGroup + * @since 3.5 + * @version $Id$ + */ +public class ThreadUtils { + + /** + * Return the active thread with the specified id if it belong's to the specified thread group. + * + * @param threadId The thread id + * @param threadGroup The thread group + * @return The thread which belongs to a specified thread group and the thread's id match the specified id. + * {@code null} is returned if no such thread exists + * @throws IllegalArgumentException if the specified id is zero or negative or the group is null + * @throws SecurityException + * if the current thread cannot access the system thread group + * + * @throws SecurityException if the current thread cannot modify + * thread groups from this thread's thread group up to the system thread group + */ + public static Thread findThreadById(final long threadId, final ThreadGroup threadGroup) { + if (threadGroup == null) { + throw new IllegalArgumentException("The thread group must not be null"); + } + final Thread thread = findThreadById(threadId); + if(thread != null && threadGroup.equals(thread.getThreadGroup())) { + return thread; + } + return null; + } + + /** + * Return the active thread with the specified id if it belong's to a thread group with the specified group name. + * + * @param threadId The thread id + * @param threadGroupName The thread group name + * @return The threads which belongs to a thread group with the specified group name and the thread's id match the specified id. + * {@code null} is returned if no such thread exists + * @throws IllegalArgumentException if the specified id is zero or negative or the group name is null + * @throws SecurityException + * if the current thread cannot access the system thread group + * + * @throws SecurityException if the current thread cannot modify + * thread groups from this thread's thread group up to the system thread group + */ + public static Thread findThreadById(final long threadId, final String threadGroupName) { + if (threadGroupName == null) { + throw new IllegalArgumentException("The thread group name must not be null"); + } + final Thread thread = findThreadById(threadId); + if(thread != null && thread.getThreadGroup() != null && thread.getThreadGroup().getName().equals(threadGroupName)) { + return thread; + } + return null; + } + + /** + * Return active threads with the specified name if they belong to a specified thread group. + * + * @param threadName The thread name + * @param threadGroupName The thread group + * @return The threads which belongs to a thread group and the thread's name match the specified name, + * An empty collection is returned if no such thread exists. The collection returned is always unmodifiable. + * @throws IllegalArgumentException if the specified thread name or group is null + * @throws SecurityException + * if the current thread cannot access the system thread group + * + * @throws SecurityException if the current thread cannot modify + * thread groups from this thread's thread group up to the system thread group + */ + public static Collection findThreadsByName(final String threadName, final ThreadGroup threadGroup) { + return findThreads(threadGroup, false, new NamePredicate(threadName)); + } + + /** + * Return active threads with the specified name if they belong to a thread group with the specified group name. + * + * @param threadName The thread name + * @param threadGroupName The thread group name + * @return The threads which belongs to a thread group with the specified group name and the thread's name match the specified name, + * An empty collection is returned if no such thread exists. The collection returned is always unmodifiable. + * @throws IllegalArgumentException if the specified thread name or group name is null + * @throws SecurityException + * if the current thread cannot access the system thread group + * + * @throws SecurityException if the current thread cannot modify + * thread groups from this thread's thread group up to the system thread group + */ + public static Collection findThreadsByName(final String threadName, final String threadGroupName) { + if (threadName == null) { + throw new IllegalArgumentException("The thread name must not be null"); + } + if (threadGroupName == null) { + throw new IllegalArgumentException("The thread group name must not be null"); + } + + final Collection result = new ArrayList(); + for(final ThreadGroup group : findThreadGroups(new NamePredicate(threadGroupName))) { + result.addAll(findThreads(group, false, new NamePredicate(threadName))); + } + return Collections.unmodifiableCollection(result); + } + + /** + * Return active thread groups with the specified group name. + * + * @param threadGroupName The thread group name + * @return the thread groups with the specified group name or an empty collection if no such thread group exists. The collection returned is always unmodifiable. + * @throws IllegalArgumentException if group name is null + * @throws SecurityException + * if the current thread cannot access the system thread group + * + * @throws SecurityException if the current thread cannot modify + * thread groups from this thread's thread group up to the system thread group + */ + public static Collection findThreadGroupsByName(final String threadGroupName) { + return findThreadGroups(new NamePredicate(threadGroupName)); + } + + /** + * Return all active thread groups excluding the system thread group (A thread group is active if it has been not destroyed). + * + * @return all thread groups excluding the system thread group. The collection returned is always unmodifiable. + * @throws SecurityException + * if the current thread cannot access the system thread group + * + * @throws SecurityException if the current thread cannot modify + * thread groups from this thread's thread group up to the system thread group + */ + public static Collection getAllThreadGroups() { + return findThreadGroups(ALWAYS_TRUE_PREDICATE); + } + + /** + * Return the system thread group (sometimes also referred as "root thread group"). + * + * @return the system thread group + * @throws SecurityException if the current thread cannot modify + * thread groups from this thread's thread group up to the system thread group + */ + public static ThreadGroup getSystemThreadGroup() { + ThreadGroup threadGroup = Thread.currentThread().getThreadGroup(); + while(threadGroup.getParent() != null) { + threadGroup = threadGroup.getParent(); + } + return threadGroup; + } + + /** + * Return all active threads (A thread is active if it has been started and has not yet died). + * + * @return all active threads. The collection returned is always unmodifiable. + * @throws SecurityException + * if the current thread cannot access the system thread group + * + * @throws SecurityException if the current thread cannot modify + * thread groups from this thread's thread group up to the system thread group + */ + public static Collection getAllThreads() { + return findThreads(ALWAYS_TRUE_PREDICATE); + } + + /** + * Return active threads with the specified name. + * + * @param threadName The thread name + * @return The threads with the specified name or an empty collection if no such thread exists. The collection returned is always unmodifiable. + * @throws IllegalArgumentException if the specified name is null + * @throws SecurityException + * if the current thread cannot access the system thread group + * + * @throws SecurityException if the current thread cannot modify + * thread groups from this thread's thread group up to the system thread group + */ + public static Collection findThreadsByName(final String threadName) { + return findThreads(new NamePredicate(threadName)); + } + + /** + * Return the active thread with the specified id. + * + * @param threadId The thread id + * @return The thread with the specified id or {@code null} if no such thread exists + * @throws IllegalArgumentException if the specified id is zero or negative + * @throws SecurityException + * if the current thread cannot access the system thread group + * + * @throws SecurityException if the current thread cannot modify + * thread groups from this thread's thread group up to the system thread group + */ + public static Thread findThreadById(final long threadId) { + final Collection result = findThreads(new ThreadIdPredicate(threadId)); + + if(!result.iterator().hasNext()) { + return null; + } else { + return result.iterator().next(); + } + + } + + /** + *

+ * ThreadUtils instances should NOT be constructed in standard programming. Instead, the class should be used as + * {@code ThreadUtils.getAllThreads()} + *

+ *

+ * This constructor is public to permit tools that require a JavaBean instance to operate. + *

+ */ + public ThreadUtils() { + super(); + } + + /** + * A predicate for selecting threads. + */ + //if java minimal version for lang becomes 1.8 extend this interface from java.util.function.Predicate + public static interface ThreadPredicate /*extends java.util.function.Predicate*/{ + + /** + * Evaluates this predicate on the given thread. + * @param thread the thread + * @return {@code true} if the thread matches the predicate, otherwise {@code false} + */ + boolean test(Thread thread); + } + + /** + * A predicate for selecting threadgroups. + */ + //if java minimal version for lang becomes 1.8 extend this interface from java.util.function.Predicate + public static interface ThreadGroupPredicate /*extends java.util.function.Predicate*/{ + + /** + * Evaluates this predicate on the given threadgroup. + * @param threadGroup the threadgroup + * @return {@code true} if the threadGroup matches the predicate, otherwise {@code false} + */ + boolean test(ThreadGroup threadGroup); + } + + /** + * Predicate which always returns true. + */ + public static final AlwaysTruePredicate ALWAYS_TRUE_PREDICATE = new AlwaysTruePredicate(); + + /** + * A predicate implementation which always returns true. + */ + private final static class AlwaysTruePredicate implements ThreadPredicate, ThreadGroupPredicate{ + + private AlwaysTruePredicate() { + } + + @Override + public boolean test(@SuppressWarnings("unused") final ThreadGroup threadGroup) { + return true; + } + + @Override + public boolean test(@SuppressWarnings("unused") final Thread thread) { + return true; + } + + } + + /** + * A predicate implementation which matches a thread or threadgroup name. + */ + public static class NamePredicate implements ThreadPredicate, ThreadGroupPredicate { + + private final String name; + + /** + * Predicate constructor + * + * @param name thread or threadgroup name + * @throws IllegalArgumentException if the name is {@code null} + */ + public NamePredicate(final String name) { + super(); + if (name == null) { + throw new IllegalArgumentException("The name must not be null"); + } + this.name = name; + } + + @Override + public boolean test(final ThreadGroup threadGroup) { + return threadGroup != null && threadGroup.getName().equals(name); + } + + @Override + public boolean test(final Thread thread) { + return thread != null && thread.getName().equals(name); + } + } + + /** + * A predicate implementation which matches a thread id. + */ + public static class ThreadIdPredicate implements ThreadPredicate { + + private final long threadId; + + /** + * Predicate constructor + * + * @param threadId the threadId to match + * @throws IllegalArgumentException if the threadId is zero or negative + */ + public ThreadIdPredicate(final long threadId) { + super(); + if (threadId <= 0) { + throw new IllegalArgumentException("The thread id must be greater than zero"); + } + this.threadId = threadId; + } + + @Override + public boolean test(final Thread thread) { + return thread != null && thread.getId() == threadId; + } + } + + /** + * Select all active threads which match the given predicate. + * + * @param predicate the predicate + * @return An unmodifiable {@code Collection} of active threads matching the given predicate + * + * @throws IllegalArgumentException if the predicate is null + * @throws SecurityException + * if the current thread cannot access the system thread group + * @throws SecurityException if the current thread cannot modify + * thread groups from this thread's thread group up to the system thread group + */ + public static Collection findThreads(final ThreadPredicate predicate){ + return findThreads(getSystemThreadGroup(), true, predicate); + } + + /** + * Select all active threadgroups which match the given predicate. + * + * @param predicate + * @return An unmodifiable {@code Collection} of active threadgroups matching the given predicate + * @throws IllegalArgumentException if the predicate is null + * @throws SecurityException + * if the current thread cannot access the system thread group + * @throws SecurityException if the current thread cannot modify + * thread groups from this thread's thread group up to the system thread group + */ + public static Collection findThreadGroups(final ThreadGroupPredicate predicate){ + return findThreadGroups(getSystemThreadGroup(), true, predicate); + } + + /** + * Select all active threads which match the given predicate and which belongs to the given thread group (or one of its subgroups). + * + * @param group the thread group + * @param recurse if {@code true} then evaluate the predicate recursively on all threads in all subgroups of the given group + * @param predicate the predicate + * @return An unmodifiable {@code Collection} of active threads which match the given predicate and which belongs to the given thread group + * @throws IllegalArgumentException if the given group or predicate is null + * @throws SecurityException if the current thread cannot modify + * thread groups from this thread's thread group up to the system thread group + */ + public static Collection findThreads(final ThreadGroup group, final boolean recurse, final ThreadPredicate predicate) { + if (group == null) { + throw new IllegalArgumentException("The group must not be null"); + } + if (predicate == null) { + throw new IllegalArgumentException("The predicate must not be null"); + } + + final List result = new ArrayList(); + int count = group.activeCount(); + Thread[] threads; + do { + threads = new Thread[count + (count >> 1) + 1]; + count = group.enumerate(threads, recurse); + } while (count >= threads.length); + + for (int i = 0; i < count; ++i) { + if (predicate.test(threads[i])) { + result.add(threads[i]); + } + } + return Collections.unmodifiableCollection(result); + } + + /** + * Select all active threadgroups which match the given predicate and which is a subgroup of the given thread group (or one of its subgroups). + * + * @param group the thread group + * @param recurse if {@code true} then evaluate the predicate recursively on all threadgroups in all subgroups of the given group + * @param predicate the predicate + * @return An unmodifiable {@code Collection} of active threadgroups which match the given predicate and which is a subgroup of the given thread group + * @throws IllegalArgumentException if the given group or predicate is null + * @throws SecurityException if the current thread cannot modify + * thread groups from this thread's thread group up to the system thread group + */ + public static Collection findThreadGroups(final ThreadGroup group, final boolean recurse, final ThreadGroupPredicate predicate){ + if (group == null) { + throw new IllegalArgumentException("The group must not be null"); + } + if (predicate == null) { + throw new IllegalArgumentException("The predicate must not be null"); + } + final List result = new ArrayList(); + int count = group.activeGroupCount(); + ThreadGroup[] threadGroups; + do { + threadGroups = new ThreadGroup[count + (count>>1) + 1]; + count = group.enumerate(threadGroups, recurse); + } + while(count >= threadGroups.length); + + for(int i = 0; i 0); + } + + @Test + public void testAtLeastOneThreadGroupsExists() throws InterruptedException { + assertTrue(ThreadUtils.getAllThreadGroups().size() > 0); + } + + @Test + public void testThreadsSameName() throws InterruptedException { + final Thread t1 = new TestThread("thread1_XXOOLL__"); + final Thread alsot1 = new TestThread("thread1_XXOOLL__"); + + try { + t1.start(); + alsot1.start(); + assertEquals(2, ThreadUtils.findThreadsByName("thread1_XXOOLL__").size()); + } finally { + t1.interrupt(); + alsot1.interrupt(); + t1.join(); + alsot1.join(); + } + } + + @Test + public void testThreads() throws InterruptedException { + final Thread t1 = new TestThread("thread1_XXOOLL__"); + final Thread t2 = new TestThread("thread2_XXOOLL__"); + + try { + t1.start(); + t2.start(); + assertEquals(1, ThreadUtils.findThreadsByName("thread2_XXOOLL__").size()); + } finally { + t1.interrupt(); + t2.interrupt(); + t1.join(); + t2.join(); + } + } + + @Test + public void testThreadsById() throws InterruptedException { + final Thread t1 = new TestThread("thread1_XXOOLL__"); + final Thread t2 = new TestThread("thread2_XXOOLL__"); + + try { + t1.start(); + t2.start(); + assertEquals(t1.getName(), ThreadUtils.findThreadById(t1.getId()).getName()); + assertSame(t2, ThreadUtils.findThreadById(t2.getId())); + } finally { + t1.interrupt(); + t2.interrupt(); + t1.join(); + t2.join(); + } + } + + @Test + public void testThreadsByIdWrongGroup() throws InterruptedException { + final Thread t1 = new TestThread("thread1_XXOOLL__"); + final ThreadGroup tg = new ThreadGroup("tg__HHEE22"); + + try { + t1.start(); + assertNull(ThreadUtils.findThreadById(t1.getId(), tg)); + } finally { + t1.interrupt(); + t1.join(); + tg.destroy(); + } + } + + + @Test + public void testThreadGroups() throws InterruptedException { + final ThreadGroup threadGroup = new ThreadGroup("thread_group_DDZZ99__"); + final Thread t1 = new TestThread(threadGroup, "thread1_XXOOPP__"); + final Thread t2 = new TestThread(threadGroup, "thread2_XXOOPP__"); + + try { + t1.start(); + t2.start(); + assertEquals(1, ThreadUtils.findThreadsByName("thread1_XXOOPP__").size()); + assertEquals(1, ThreadUtils.findThreadsByName("thread1_XXOOPP__","thread_group_DDZZ99__").size()); + assertEquals(1, ThreadUtils.findThreadsByName("thread2_XXOOPP__","thread_group_DDZZ99__").size()); + assertEquals(0, ThreadUtils.findThreadsByName("thread1_XXOOPP__","non_existent_thread_group_JJHHZZ__").size()); + assertEquals(0, ThreadUtils.findThreadsByName("non_existent_thread_BBDDWW__","thread_group_DDZZ99__").size()); + assertEquals(1, ThreadUtils.findThreadGroupsByName("thread_group_DDZZ99__").size()); + assertEquals(0, ThreadUtils.findThreadGroupsByName("non_existent_thread_group_JJHHZZ__").size()); + assertNotNull(ThreadUtils.findThreadById(t1.getId(),threadGroup)); + } finally { + t1.interrupt(); + t2.interrupt(); + t1.join(); + t2.join(); + threadGroup.destroy(); + } + } + + @Test + public void testThreadGroupsRef() throws InterruptedException { + final ThreadGroup threadGroup = new ThreadGroup("thread_group_DDZZ99__"); + final ThreadGroup deadThreadGroup = new ThreadGroup("dead_thread_group_MMQQSS__"); + deadThreadGroup.destroy(); + final Thread t1 = new TestThread(threadGroup, "thread1_XXOOPP__"); + final Thread t2 = new TestThread(threadGroup, "thread2_XXOOPP__"); + + try { + t1.start(); + t2.start(); + assertEquals(1, ThreadUtils.findThreadsByName("thread1_XXOOPP__").size()); + assertEquals(1, ThreadUtils.findThreadsByName("thread1_XXOOPP__",threadGroup).size()); + assertEquals(1, ThreadUtils.findThreadsByName("thread2_XXOOPP__",threadGroup).size()); + assertEquals(0, ThreadUtils.findThreadsByName("thread1_XXOOPP__",deadThreadGroup).size()); + } finally { + t1.interrupt(); + t2.interrupt(); + t1.join(); + t2.join(); + threadGroup.destroy(); + assertEquals(0, ThreadUtils.findThreadsByName("thread2_XXOOPP__",threadGroup).size()); + } + } + + @Test + public void testThreadGroupsById() throws InterruptedException { + final ThreadGroup threadGroup = new ThreadGroup("thread_group_DDZZ99__"); + final Thread t1 = new TestThread(threadGroup, "thread1_XXOOPP__"); + final Thread t2 = new TestThread(threadGroup, "thread2_XXOOPP__"); + final long nonExistingId = t1.getId()+t2.getId(); + + try { + t1.start(); + t2.start(); + assertEquals(t1.getName(), ThreadUtils.findThreadById(t1.getId(),"thread_group_DDZZ99__").getName()); + assertEquals(t2.getName(), ThreadUtils.findThreadById(t2.getId(),"thread_group_DDZZ99__").getName()); + assertNull(ThreadUtils.findThreadById(nonExistingId,"non_existent_thread_group_JJHHZZ__")); + assertNull(ThreadUtils.findThreadById(nonExistingId,"thread_group_DDZZ99__")); + } finally { + t1.interrupt(); + t2.interrupt(); + t1.join(); + t2.join(); + threadGroup.destroy(); + } + } + + @Test + public void testConstructor() throws InterruptedException { + assertNotNull(new ThreadUtils()); + final Constructor[] cons = ThreadUtils.class.getDeclaredConstructors(); + assertEquals(1, cons.length); + assertTrue(Modifier.isPublic(cons[0].getModifiers())); + assertTrue(Modifier.isPublic(ThreadUtils.class.getModifiers())); + assertFalse(Modifier.isFinal(ThreadUtils.class.getModifiers())); + } + + @Test + public void testComplexThreadGroups() throws Exception { + final ThreadGroup threadGroup1 = new ThreadGroup("thread_group_1__"); + final ThreadGroup threadGroup2 = new ThreadGroup("thread_group_2__"); + final ThreadGroup threadGroup3 = new ThreadGroup(threadGroup2, "thread_group_3__"); + final ThreadGroup threadGroup4 = new ThreadGroup(threadGroup2, "thread_group_4__"); + final ThreadGroup threadGroup5 = new ThreadGroup(threadGroup1, "thread_group_5__"); + final ThreadGroup threadGroup6 = new ThreadGroup(threadGroup4, "thread_group_6__"); + final List threadGroups = Arrays.asList(threadGroup1,threadGroup2,threadGroup3,threadGroup4,threadGroup5,threadGroup6); + + final Thread t1 = new TestThread("thread1_X__"); + final Thread t2 = new TestThread(threadGroup1, "thread2_X__"); + final Thread t3 = new TestThread(threadGroup2, "thread3_X__"); + final Thread t4 = new TestThread(threadGroup3, "thread4_X__"); + final Thread t5 = new TestThread(threadGroup4, "thread5_X__"); + final Thread t6 = new TestThread(threadGroup5, "thread6_X__"); + final Thread t7 = new TestThread(threadGroup6, "thread7_X__"); + final Thread t8 = new TestThread(threadGroup4, "thread8_X__"); + final Thread t9 = new TestThread(threadGroup6, "thread9_X__"); + final Thread t10 = new TestThread(threadGroup3, "thread10_X__"); + final List threads = Arrays.asList(t1,t2,t3,t4,t5,t6,t7,t8,t9,t10); + + try { + for (final Iterator iterator = threads.iterator(); iterator.hasNext();) { + final Thread thread = (Thread) iterator.next(); + thread.start(); + } + assertTrue(ThreadUtils.getAllThreadGroups().size() >= 7); + assertTrue(ThreadUtils.getAllThreads().size() >= 11); + assertTrue(ThreadUtils.findThreads(ThreadUtils.ALWAYS_TRUE_PREDICATE).size() >= 11); + assertEquals(1, ThreadUtils.findThreadsByName(t4.getName(), threadGroup3.getName()).size()); + assertEquals(0, ThreadUtils.findThreadsByName(t4.getName(), threadGroup2.getName()).size()); + + }finally { + for (final Iterator iterator = threads.iterator(); iterator.hasNext();) { + final Thread thread = (Thread) iterator.next(); + thread.interrupt(); + thread.join(); + } + for (final Iterator iterator = threadGroups.iterator(); iterator.hasNext();) { + final ThreadGroup threadGroup = (ThreadGroup) iterator.next(); + if(!threadGroup.isDestroyed()) + threadGroup.destroy(); + } + } + } + + + private static class TestThread extends Thread { + private final CountDownLatch latch = new CountDownLatch(1); + + public TestThread(final String name) { + super(name); + } + + public TestThread(final ThreadGroup group, final String name) { + super(group, name); + } + + @Override + public synchronized void start() { + super.start(); + try { + latch.await(); + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + @Override + public void run() { + latch.countDown(); + try { + synchronized(this){ + this.wait(); + } + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } +}