    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017package org.apache.commons.lang3;
    018
    019import java.time.Duration;
    020import java.util.ArrayList;
    021import java.util.Collection;
    022import java.util.Collections;
    023import java.util.List;
    024
    025import org.apache.commons.lang3.time.DurationUtils;
    026
    027/**
    028 * <p>
    029 * Helpers for {@code java.lang.Thread} and {@code java.lang.ThreadGroup}.
    030 * </p>
    031 * <p>
    032 * #ThreadSafe#
    033 * </p>
    034 *
    035 * @see java.lang.Thread
    036 * @see java.lang.ThreadGroup
    037 * @since 3.5
    038 */
    039public class ThreadUtils {
    040
    041    /**
    042     * A predicate implementation which always returns true.
    043     */
    044    private static final class AlwaysTruePredicate implements ThreadPredicate, ThreadGroupPredicate {
    045
    046        private AlwaysTruePredicate() {
    047        }
    048
    049        @Override
    050        public boolean test(final Thread thread) {
    051            return true;
    052        }
    053
    054        @Override
    055        public boolean test(final ThreadGroup threadGroup) {
    056            return true;
    057        }
    058    }
    059
    060    /**
    061     * A predicate implementation which matches a thread or threadgroup name.
    062     */
    063    public static class NamePredicate implements ThreadPredicate, ThreadGroupPredicate {
    064
    065        private final String name;
    066
    067        /**
    068         * Predicate constructor
    069         *
    070         * @param name thread or threadgroup name
    071         * @throws IllegalArgumentException if the name is {@code null}
    072         */
    073        public NamePredicate(final String name) {
    074            Validate.notNull(name, "name");
    075            this.name = name;
    076        }
    077
    078        @Override
    079        public boolean test(final Thread thread) {
    080            return thread != null && thread.getName().equals(name);
    081        }
    082
    083        @Override
    084        public boolean test(final ThreadGroup threadGroup) {
    085            return threadGroup != null && threadGroup.getName().equals(name);
    086        }
    087    }
    088
    089    /**
    090     * A predicate for selecting threadgroups.
    091     */
    092    // When breaking BC, replace this with Predicate<ThreadGroup>
    093    @FunctionalInterface
    094    public interface ThreadGroupPredicate {
    095
    096        /**
    097         * Evaluates this predicate on the given threadgroup.
    098         * @param threadGroup the threadgroup
    099         * @return {@code true} if the threadGroup matches the predicate, otherwise {@code false}
    100         */
    101        boolean test(ThreadGroup threadGroup);
    102    }
    103
    104    /**
    105     * A predicate implementation which matches a thread id.
    106     */
    107    public static class ThreadIdPredicate implements ThreadPredicate {
    108
    109        private final long threadId;
    110
    111        /**
    112         * Predicate constructor
    113         *
    114         * @param threadId the threadId to match
    115         * @throws IllegalArgumentException if the threadId is zero or negative
    116         */
    117        public ThreadIdPredicate(final long threadId) {
    118            if (threadId <= 0) {
    119                throw new IllegalArgumentException("The thread id must be greater than zero");
    120            }
    121            this.threadId = threadId;
    122        }
    123
    124        @Override
    125        public boolean test(final Thread thread) {
    126            return thread != null && thread.getId() == threadId;
    127        }
    128    }
    129
    130    /**
    131     * A predicate for selecting threads.
    132     */
    133    // When breaking BC, replace this with Predicate<Thread>
    134    @FunctionalInterface
    135    public interface ThreadPredicate {
    136
    137        /**
    138         * Evaluates this predicate on the given thread.
    139         * @param thread the thread
    140         * @return {@code true} if the thread matches the predicate, otherwise {@code false}
    141         */
    142        boolean test(Thread thread);
    143    }
    144
    145    /**
    146     * Predicate which always returns true.
    147     */
    148    public static final AlwaysTruePredicate ALWAYS_TRUE_PREDICATE = new AlwaysTruePredicate();
    149
    150    /**
    151     * Finds the active thread with the specified id.
    152     *
    153     * @param threadId The thread id
    154     * @return The thread with the specified id or {@code null} if no such thread exists
    155     * @throws IllegalArgumentException if the specified id is zero or negative
    156     * @throws  SecurityException
    157     *          if the current thread cannot access the system thread group
    158     *
    159     * @throws  SecurityException  if the current thread cannot modify
    160     *          thread groups from this thread's thread group up to the system thread group
    161     */
    162    public static Thread findThreadById(final long threadId) {
    163        final Collection<Thread> result = findThreads(new ThreadIdPredicate(threadId));
    164        return result.isEmpty() ? null : result.iterator().next();
    165    }
    166
    167    /**
    168     * Finds the active thread with the specified id if it belongs to a thread group with the specified group name.
    169     *
    170     * @param threadId The thread id
    171     * @param threadGroupName The thread group name
    172     * @return The threads which belongs to a thread group with the specified group name and the thread's id match the specified id.
    173     * {@code null} is returned if no such thread exists
    174     * @throws IllegalArgumentException if the specified id is zero or negative or the group name is null
    175     * @throws  SecurityException
    176     *          if the current thread cannot access the system thread group
    177     *
    178     * @throws  SecurityException  if the current thread cannot modify
    179     *          thread groups from this thread's thread group up to the system thread group
    180     */
    181    public static Thread findThreadById(final long threadId, final String threadGroupName) {
    182        Validate.notNull(threadGroupName, "threadGroupName");
    183        final Thread thread = findThreadById(threadId);
    184        if (thread != null && thread.getThreadGroup() != null && thread.getThreadGroup().getName().equals(threadGroupName)) {
    185            return thread;
    186        }
    187        return null;
    188    }
    189
    190    /**
    191     * Finds the active thread with the specified id if it belongs to the specified thread group.
    192     *
    193     * @param threadId The thread id
    194     * @param threadGroup The thread group
    195     * @return The thread which belongs to a specified thread group and the thread's id match the specified id.
    196     * {@code null} is returned if no such thread exists
    197     * @throws IllegalArgumentException if the specified id is zero or negative or the group is null
    198     * @throws  SecurityException
    199     *          if the current thread cannot access the system thread group
    200     *
    201     * @throws  SecurityException  if the current thread cannot modify
    202     *          thread groups from this thread's thread group up to the system thread group
    203     */
    204    public static Thread findThreadById(final long threadId, final ThreadGroup threadGroup) {
    205        Validate.notNull(threadGroup, "threadGroup");
    206        final Thread thread = findThreadById(threadId);
    207        if (thread != null && threadGroup.equals(thread.getThreadGroup())) {
    208            return thread;
    209        }
    210        return null;
    211    }
    212
    213    /**
    214     * Select all active threadgroups which match the given predicate and which is a subgroup of the given thread group (or one of its subgroups).
    215     *
    216     * @param group the thread group
    217     * @param recurse if {@code true} then evaluate the predicate recursively on all threadgroups in all subgroups of the given group
    218     * @param predicate the predicate
    219     * @return An unmodifiable {@code Collection} of active threadgroups which match the given predicate and which is a subgroup of the given thread group
    220     * @throws IllegalArgumentException if the given group or predicate is null
    221     * @throws  SecurityException  if the current thread cannot modify
    222     *          thread groups from this thread's thread group up to the system thread group
    223     */
    224    public static Collection<ThreadGroup> findThreadGroups(final ThreadGroup group, final boolean recurse, final ThreadGroupPredicate predicate) {
    225        Validate.notNull(group, "group");
    226        Validate.notNull(predicate, "predicate");
    227
    228        int count = group.activeGroupCount();
    229        ThreadGroup[] threadGroups;
    230        do {
    231            threadGroups = new ThreadGroup[count + (count / 2) + 1]; //slightly grow the array size
    232            count = group.enumerate(threadGroups, recurse);
    233            //return value of enumerate() must be strictly less than the array size according to javadoc
    234        } while (count >= threadGroups.length);
    235
    236        final List<ThreadGroup> result = new ArrayList<>(count);
    237        for (int i = 0; i < count; ++i) {
    238            if (predicate.test(threadGroups[i])) {
    239                result.add(threadGroups[i]);
    240            }
    241        }
    242        return Collections.unmodifiableCollection(result);
    243    }
    244
    245    /**
    246     * Select all active threadgroups which match the given predicate.
    247     *
    248     * @param predicate the predicate
    249     * @return An unmodifiable {@code Collection} of active threadgroups matching the given predicate
    250     * @throws IllegalArgumentException if the predicate is null
    251     * @throws  SecurityException
    252     *          if the current thread cannot access the system thread group
    253     * @throws  SecurityException  if the current thread cannot modify
    254     *          thread groups from this thread's thread group up to the system thread group
    255     */
    256    public static Collection<ThreadGroup> findThreadGroups(final ThreadGroupPredicate predicate) {
    257        return findThreadGroups(getSystemThreadGroup(), true, predicate);
    258    }
    259
    260    /**
    261     * Finds active thread groups with the specified group name.
    262     *
    263     * @param threadGroupName The thread group name
    264     * @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.
    265     * @throws IllegalArgumentException if group name is null
    266     * @throws  SecurityException
    267     *          if the current thread cannot access the system thread group
    268     *
    269     * @throws  SecurityException  if the current thread cannot modify
    270     *          thread groups from this thread's thread group up to the system thread group
    271     */
    272    public static Collection<ThreadGroup> findThreadGroupsByName(final String threadGroupName) {
    273        return findThreadGroups(new NamePredicate(threadGroupName));
    274    }
    275
    276    /**
    277     * Select all active threads which match the given predicate and which belongs to the given thread group (or one of its subgroups).
    278     *
    279     * @param group the thread group
    280     * @param recurse if {@code true} then evaluate the predicate recursively on all threads in all subgroups of the given group
    281     * @param predicate the predicate
    282     * @return An unmodifiable {@code Collection} of active threads which match the given predicate and which belongs to the given thread group
    283     * @throws IllegalArgumentException if the given group or predicate is null
    284     * @throws  SecurityException  if the current thread cannot modify
    285     *          thread groups from this thread's thread group up to the system thread group
    286     */
    287    public static Collection<Thread> findThreads(final ThreadGroup group, final boolean recurse, final ThreadPredicate predicate) {
    288        Validate.notNull(group, "The group must not be null");
    289        Validate.notNull(predicate, "The predicate must not be null");
    290
    291        int count = group.activeCount();
    292        Thread[] threads;
    293        do {
    294            threads = new Thread[count + (count / 2) + 1]; //slightly grow the array size
    295            count = group.enumerate(threads, recurse);
    296            //return value of enumerate() must be strictly less than the array size according to javadoc
    297        } while (count >= threads.length);
    298
    299        final List<Thread> result = new ArrayList<>(count);
    300        for (int i = 0; i < count; ++i) {
    301            if (predicate.test(threads[i])) {
    302                result.add(threads[i]);
    303            }
    304        }
    305        return Collections.unmodifiableCollection(result);
    306    }
    307
    308    /**
    309     * Select all active threads which match the given predicate.
    310     *
    311     * @param predicate the predicate
    312     * @return An unmodifiable {@code Collection} of active threads matching the given predicate
    313     *
    314     * @throws IllegalArgumentException if the predicate is null
    315     * @throws  SecurityException
    316     *          if the current thread cannot access the system thread group
    317     * @throws  SecurityException  if the current thread cannot modify
    318     *          thread groups from this thread's thread group up to the system thread group
    319     */
    320    public static Collection<Thread> findThreads(final ThreadPredicate predicate) {
    321        return findThreads(getSystemThreadGroup(), true, predicate);
    322    }
    323
    324    /**
    325     * Finds active threads with the specified name.
    326     *
    327     * @param threadName The thread name
    328     * @return The threads with the specified name or an empty collection if no such thread exists. The collection returned is always unmodifiable.
    329     * @throws IllegalArgumentException if the specified name is null
    330     * @throws  SecurityException
    331     *          if the current thread cannot access the system thread group
    332     *
    333     * @throws  SecurityException  if the current thread cannot modify
    334     *          thread groups from this thread's thread group up to the system thread group
    335     */
    336    public static Collection<Thread> findThreadsByName(final String threadName) {
    337        return findThreads(new NamePredicate(threadName));
    338    }
    339
    340    /**
    341     * Finds active threads with the specified name if they belong to a thread group with the specified group name.
    342     *
    343     * @param threadName The thread name
    344     * @param threadGroupName The thread group name
    345     * @return The threads which belongs to a thread group with the specified group name and the thread's name match the specified name,
    346     * An empty collection is returned if no such thread exists. The collection returned is always unmodifiable.
    347     * @throws IllegalArgumentException if the specified thread name or group name is null
    348     * @throws  SecurityException
    349     *          if the current thread cannot access the system thread group
    350     *
    351     * @throws  SecurityException  if the current thread cannot modify
    352     *          thread groups from this thread's thread group up to the system thread group
    353     */
    354    public static Collection<Thread> findThreadsByName(final String threadName, final String threadGroupName) {
    355        Validate.notNull(threadName, "threadName");
    356        Validate.notNull(threadGroupName, "threadGroupName");
    357
    358        final Collection<ThreadGroup> threadGroups = findThreadGroups(new NamePredicate(threadGroupName));
    359
    360        if (threadGroups.isEmpty()) {
    361            return Collections.emptyList();
    362        }
    363
    364        final Collection<Thread> result = new ArrayList<>();
    365        final NamePredicate threadNamePredicate = new NamePredicate(threadName);
    366        for (final ThreadGroup group : threadGroups) {
    367            result.addAll(findThreads(group, false, threadNamePredicate));
    368        }
    369        return Collections.unmodifiableCollection(result);
    370    }
    371
    372    /**
    373     * Finds active threads with the specified name if they belong to a specified thread group.
    374     *
    375     * @param threadName The thread name
    376     * @param threadGroup The thread group
    377     * @return The threads which belongs to a thread group and the thread's name match the specified name,
    378     * An empty collection is returned if no such thread exists. The collection returned is always unmodifiable.
    379     * @throws IllegalArgumentException if the specified thread name or group is null
    380     * @throws  SecurityException
    381     *          if the current thread cannot access the system thread group
    382     *
    383     * @throws  SecurityException  if the current thread cannot modify
    384     *          thread groups from this thread's thread group up to the system thread group
    385     */
    386    public static Collection<Thread> findThreadsByName(final String threadName, final ThreadGroup threadGroup) {
    387        return findThreads(threadGroup, false, new NamePredicate(threadName));
    388    }
    389
    390    /**
    391     * Gets all active thread groups excluding the system thread group (A thread group is active if it has been not destroyed).
    392     *
    393     * @return all thread groups excluding the system thread group. The collection returned is always unmodifiable.
    394     * @throws  SecurityException
    395     *          if the current thread cannot access the system thread group
    396     *
    397     * @throws  SecurityException  if the current thread cannot modify
    398     *          thread groups from this thread's thread group up to the system thread group
    399     */
    400    public static Collection<ThreadGroup> getAllThreadGroups() {
    401        return findThreadGroups(ALWAYS_TRUE_PREDICATE);
    402    }
    403
    404    /**
    405     * Gets all active threads (A thread is active if it has been started and has not yet died).
    406     *
    407     * @return all active threads. The collection returned is always unmodifiable.
    408     * @throws  SecurityException
    409     *          if the current thread cannot access the system thread group
    410     *
    411     * @throws  SecurityException  if the current thread cannot modify
    412     *          thread groups from this thread's thread group up to the system thread group
    413     */
    414    public static Collection<Thread> getAllThreads() {
    415        return findThreads(ALWAYS_TRUE_PREDICATE);
    416    }
    417
    418    /**
    419     * Gets the system thread group (sometimes also referred as "root thread group").
    420     *
    421     * @return the system thread group
    422     * @throws  SecurityException  if the current thread cannot modify
    423     *          thread groups from this thread's thread group up to the system thread group
    424     */
    425    public static ThreadGroup getSystemThreadGroup() {
    426        ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
    427        while (threadGroup.getParent() != null) {
    428            threadGroup = threadGroup.getParent();
    429        }
    430        return threadGroup;
    431    }
    432
    433    /**
    434     * Waits for the given thread to die for the given duration. Implemented using {@link Thread#join(long, int)}.
    435     *
    436     * @param thread The thread to join.
    437     * @param duration How long to wait.
    438     * @throws InterruptedException if any thread has interrupted the current thread.
    439     * @see Thread#join(long, int)
    440     * @since 3.12.0
    441     */
    442    public static void join(final Thread thread, final Duration duration) throws InterruptedException {
    443        DurationUtils.accept(thread::join, duration);
    444    }
    445
    446    /**
    447     * Sleeps the current thread for the given duration. Implemented using {@link Thread#sleep(long, int)}.
    448     *
    449     * @param duration How long to sleep.
    450     * @throws InterruptedException if any thread has interrupted the current thread.
    451     * @see Thread#sleep(long, int)
    452     * @since 3.12.0
    453     */
    454    public static void sleep(final Duration duration) throws InterruptedException {
    455        DurationUtils.accept(Thread::sleep, duration);
    456    }
    457
    458    /**
    459     * <p>
    460     * ThreadUtils instances should NOT be constructed in standard programming. Instead, the class should be used as
    461     * {@code ThreadUtils.getAllThreads()}
    462     * </p>
    463     * <p>
    464     * This constructor is public to permit tools that require a JavaBean instance to operate.
    465     * </p>
    466     */
    467    public ThreadUtils() {
    468    }
    469}