Thread构造函数与API

线程的命名

Thread的构造函数中,有几个并没有提供为线程命名的参数,那么此时线程会有一个怎样的命名呢?

Thread()
Thread(Runnable target)
Thread(ThreadGroup group, Runnable target)
    
public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}

/* For autonumbering anonymous threads. */
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
    return threadInitNumber++;
}

如果没有为线程显式地指定一个名字,那么线程将会以Thread-作为前缀与一个自增数字进行组合,这个自增数字在整个JVM进程中将会不断自增:

import java.util.stream.IntStream;

public class Test {
    public static void main(String[] args) {
        IntStream.range(0, 5).boxed().map(i -> new Thread(
                () -> System.out.println(Thread.currentThread().getName())
        )).forEach(Thread::start);
    }
}

输出结果:

Thread-0
Thread-4
Thread-2
Thread-3
Thread-1

可以使用Thread的构造函数对线程进行命名:

Thread(String name)
Thread(Runnable target, String name)
Thread(ThreadGroup group, String name)
Thread(ThreadGroup group, Runnable target, String name)
Thread(ThreadGroup group, Runnable target, String name, long stackSize)

不论你使用的是默认的函数命名规则,还是指定了一个特殊的名字,在线程启动之前还有一个机会可以对其进行修改,一旦线程启动,名字将不再被修改。

import java.util.stream.IntStream;

public class Test {
    private final static String PREFIX = "ThreadName-";

    private static Thread createThread(final int name) {
        return new Thread(
                () -> System.out.println(Thread.currentThread().getName()), PREFIX + name
        );
    }

    public static void main(String[] args) {
        // 创建实例,覆写run方法(run方法中调用createThread方法)
        IntStream.range(0, 5).mapToObj(Test::createThread).forEach(Thread::start);
    }
}

输出结果:

ThreadName-0
ThreadName-3
ThreadName-4
ThreadName-2
ThreadName-1

线程的父子关系

Thread的所有构造函数,最终都会去调用一个静态方法init

public class Thread implements Runnable {
  public Thread() {
    init(null, null, "Thread-" + nextThreadNum(), 0);
  }

  /**
   * Initializes a Thread.
   */
  private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc) {
    if (name == null) {
      throw new NullPointerException("name cannot be null");
    }

    this.name = name.toCharArray();
    // 获取当前线程作为父线程
    Thread parent = currentThread();
    SecurityManager security = System.getSecurityManager();
    // ...
  }
}

currentThread()是获取当前线程,在线程生命周期中,线程的最初状态为NEW,没有执行start方法之前,它只能算是一个Thread的实例,并不意味着一个新的线程被创建。因此currentThread()代表的将会是创建当前线程的那个线程

  • 一个线程的创建,是由另一个线程完成的。
  • 被创建线程的父线程,是创建它的线程。

main函数所在的线程是由JVM创建的,也就是main线程,意味着前面创建的所有线程,其父线程都是main线程

守护线程

当在一个JVM进程里面开多个线程时,这些线程被分成两类:守护线程非守护线程默认开的都是非守护线程

  • 线程分为用户线程守护(daemon)线程
    • 虚拟机必须确保用户线程(如main线程)执行完毕

    • 虚拟机不用等待守护线程(如垃圾回收线程)执行完毕

  • 守护线程应用:后台记录操作日志、监控内存、垃圾回收等。

在没有调用System.exit()方法的前提下,若主线程结束后,JVM中没有一个非守护线程(全是守护线程),则JVM的进程会退出

The Java Virtual Machine exits when the only threads running are all daemon threads.

public class DaemonThread {
    public static void main(String[] args) throws InterruptedException {
        // main线程开始,由JVM启动

        // 1. 创建thread线程
        /**
         * 调用thread线程,程序不会退出
         * 不退出是因为while (true),使得线程一直没有结束运行
         */
        Thread thread = new Thread (
                () -> {
                    while (true) {
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
        );

        /**
         * 调用thread1线程,main线程会先结束,程序等待线程结束后,才会退出
         * 因为剩余都是守护线程
         */
        Thread thread1 = new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        // 2. 将thread设置为守护线程
        thread.setDaemon(true);

        // 3. 启动thread线程
        thread1.start();

        Thread.sleep(2_000L);
        // 4. main 线程结束
        System.out.println("main thread finished lifecycle.");
    }
}

如果没有将thread设置为守护线程,则JVM进程永远不会退出,即使main线程正常地结束了自己的生命周期(main线程的生命周期是从注释0到注释4之间的那段代码),原因就是在JVM进程中还存在一个非守护线程在运行

如果将thread设置为守护线程:thread.setDaemon(true);,那么main进程结束生命周期后,JVM也会随之退出运行,thread线程也会结束。

守护线程具备自动结束生命周期的特性,而非守护线程则不具备这个特点。守护线程经常用作执行一些后台任务,当你希望关闭某些线程,或者退出JVM进程的时候,一些线程能够自动关闭,此时就可以考虑用守护线程为你完成这样的工作。

线程sleep

public class Thread implements Runnable {
    public static native void sleep(long millis) throws InterruptedException;
    
    public static void sleep(long millis, int nanos) throws InterruptedException {
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException("nanosecond timeout value out of range");
        }

        if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
            millis++;
        }

        sleep(millis);
    }    
}

sleep方法会使当前线程进入指定毫秒数的休眠,暂停执行,虽然给定了一个休眠的时间,但是最终要以系统的定时器和调度器的精度为准休眠有一个非常重要的特性,那就是其不会放弃monitor锁的所有权

import java.util.concurrent.TimeUnit;

/**
 * 在主线程和自定义的线程中进行休眠
 */

public class ThreadSleep {
    public static void main(String[] args) throws InterruptedException {

        new Thread(
                () -> {
                    long startTime = System.currentTimeMillis();
                    sleep(2_000L);
                    long endTime = System.currentTimeMillis();
                    System.out.println(String.format("Total spend %d ms", (endTime - startTime)));
                }
        ).start();

        long startTime = System.currentTimeMillis();
        TimeUnit.SECONDS.sleep(3);
        long endTime = System.currentTimeMillis();
        System.out.println(String.format("main thread total spends %d ms", (endTime - startTime)));
    }

    private static void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

在JDK 1.5 以后,JDK引入了一个枚举TimeUnit,其对sleep方法提供了很好的封装,使用它可以省去时间单位的换算步骤:TimeUnit.SECONDS.sleep(3);

线程yield

yield方法属于一种启发式的方法,其会提醒调度器我愿意放弃当前的CPU资源如果CPU的资源不紧张,则会忽略这种提醒。即,yield只是一个提示(hint),CPU调度器并不会担保每次都能满足yield提示

调用yield方法会使当前线程从RUNNING状态切换到RUNNABLE状态,一般这个方法不太常用。

yield与sleep的区别

在JDK 1.5以前的版本中yield的方法事实上是调用了sleep(0),但是它们之间存在着本质的区别:

  • sleep会导致当前线程暂停指定的时间,没有CPU时间片的消耗;而yield只是对CPU调度器的一个提示,如果CPU调度器没有忽略这个提示,它会导致线程上下文的切换

  • sleep会使线程短暂block,会在给定的时间内释放CPU资源;yield会使RUNNING状态的Thread进入RUNNABLE状态(如果CPU调度器没有忽略这个提示的话)。

  • sleep几乎百分之百地完成了给定时间的休眠,而yield的提示并不能一定担保

  • 一个线程sleep,另一个线程调用interrupt会捕获到中断信号,而yield则不会。

设置线程优先级

// 设置优先级
public final void setPriority(int newPriority) {
    ThreadGroup g;
    checkAccess();
    if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
        throw new IllegalArgumentException();
    }
    if((g = getThreadGroup()) != null) {
        if (newPriority > g.getMaxPriority()) {
            newPriority = g.getMaxPriority();
        }
        setPriority0(priority = newPriority);
    }
}

// 获取线程优先级
public final int getPriority() {
    return priority;
}

理论上是优先级比较高的线程会获取优先被CPU调度的机会,但是事实上设置线程的优先级同样也是一个hint操作

如果CPU比较忙,设置优先级可能会获得更多的CPU时间片,但是闲时优先级的高低几乎不会有任何作用

一般情况下,不会对线程设定优先级别,更不会让某些业务严重地依赖线程的优先级别,比如权重,借助优先级设定某个任务的权重,这种方式是不可取的,一般定义线程的时候使用默认的优先级就好了,那么线程默认的优先级是多少呢?

线程默认的优先级和它的父类保持一致,一般情况下都是5,因为main线程的优先级就是5,所以它派生出来的线程都是5。

线程join

join方法与sleep一样也是一个可中断的方法, 如果有其他线程执行了对当前线程的interrupt操作,它也会捕获到中断信号,并且擦除线程的interrupt标识!

join某个线程A(线程A插队),会使当前线程B进入等待,直到线程A结束生命周期,或者到达给定的时间

import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class ThreadJoin {

    private static Thread create(int seq) {
        // Thread(Runnable target, String name)
        return new Thread(
                () -> {
                    System.out.println("线程" + Thread.currentThread().getName() + "状态:"
                            + Thread.currentThread().getState());
                    shortSleep();
                }, String.valueOf(seq)
        );
    }

    private static void shortSleep() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println("线程" + Thread.currentThread().getName() + "在其他线程调用join前。");

        // 1.定义两个线程
        List<Thread> threads = IntStream.range(1, 3).mapToObj(ThreadJoin::create).collect(Collectors.toList());
        // 2.启动两个线程
        threads.forEach(Thread::start);
        // 3.在main线程中,两个线程调用join方法
        for (Thread thread : threads) {
            thread.join();
        }
        // 4.main线程输出
        System.out.println("线程" + Thread.currentThread().getName() + "在其他线程调用join后。");
    }
}

输出结果:

线程main在其他线程调用join前。
线程1状态:RUNNABLE
线程2状态:RUNNABLE
线程main在其他线程调用join后。

线程interrupt

在多线程编程中,中断是一种协同机制,怎么理解这么高大上的词呢?

例如你妈妈叫你吃饭,你收到了中断游戏通知但是否马上放下手中的游戏(收到中断信息,但如何处理需要具体设置)去吃饭看你心情 。在程序中怎样演绎这个心情就看具体的业务逻辑。

在多线程的场景中,有的线程可能自旋浪费资源,这时就可以用其他线程在恰当的时机给它个中断通知,被“中断”的线程,可以选择在恰当的时机选择跳出怪圈,最大化的利用资源。

线程中断指的是唤醒轻量级阻塞,而不是中断一个线程!它相当于给线程发送了一个唤醒的信号

  • 如果线程此时恰好处于WAITING或者TIMED_WAITING状态,就会抛出一个InterruptedException,并且线程被唤醒
  • 而如果线程此时并没有被阻塞,则线程什么都不会做。但在后续,线程可以判断自己是否收到过其他线程发来的中断信号,然后做一些对应的处理!

interrupt()方法是 唯一一个可以将中断标志设置为true的方法,它是一个Thread类public的对象方法,所以任何线程对象都可以调用该方法可以一个线程interrupt其他线程,也可以interrupt自己。其中,中断标识的设置是通过native方法 interrupt0 完成的。

public class Thread implements Runnable {
    public void interrupt() {
    	if (this != Thread.currentThread())
        	checkAccess();
    
    	synchronized (blockerLock) {
        	Interruptible b = blocker;
        	if (b != null) {
            	interrupt0();           // Just to set the interrupt flag
            	b.interrupt(this);
            	return;
        	}
    	}
    	interrupt0();
	}

	public static boolean interrupted() {
    	return currentThread().isInterrupted(true);
	}

	public boolean isInterrupted() {
    	return isInterrupted(false);
	}

	private native boolean isInterrupted(boolean ClearInterrupted);
	// 参数ClearInterrupted主要用来控制是否擦除线程interrupt的标识。
}

并不是说一个线程运行到一半,把它中断了,然后抛出了InterruptedException异常,只有那些声明了会抛出InterruptedException的函数才会抛出异常:

public static native void sleep(long millis) throws InterruptedException;
public final void join() throws InterruptedException{}
public final void wait() throws InterruptedException{}

可中断方法

Object#waitThread#sleepThread#joinInterruptibleChannel的IO操作Selector#wakeup等,会使得当前线程进入轻量级阻塞状态,若另外的一个线程调用被阻塞线程的interrupt方法,则会打断这种阻塞,因此这种方法有时会被称为可中断方法打断一个线程并不等于该线程的生命周期结束,仅仅是打断了当前线程的阻塞状态

一旦线程在阻塞的情况下被打断,都会抛出一个称为InterruptedException的异常,这个异常就像一个signal(信号)一样通知当前线程被打断了

import java.util.concurrent.TimeUnit;

public class ThreadInterrupt {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(
                () -> {
                    try {
                        // 当前线程处于阻塞状态,异常必须捕捉处理,无法往外抛出
                        TimeUnit.MINUTES.sleep(1);
                    } catch (InterruptedException e) {
                        System.out.println(Thread.currentThread().getName() + " is interrupted !");
                    }
                }
        );

        thread.setName("Thread1");
        thread.start();

        // Short block and make sure thread is started
        TimeUnit.SECONDS.sleep(1);
        thread.interrupt();
    }
}

上面的代码创建了一个线程,并且企图休眠1分钟,不过大约在1秒之后就被主线程调用interrupt方法打断!

在一个线程内部存在着名为interrupt flag的标识,如果一个线程被interrupt,那么它的flag将被设置

处于运行期且非阻塞的状态的线程,无法被中断(没有调用可中断方法):

import java.util.concurrent.TimeUnit;

public class InterruptThread {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(
                () -> {
                    while (true) {
                        System.out.println("未被中断");
                    }
                }
        );

        thread.start();
        TimeUnit.SECONDS.sleep(1);
        thread.interrupt();
    }
}

一直输出未被中断

isInterrupted

isInterrupted是Thread的一个成员方法(非静态函数),它主要判断当前线程是否被中断,该方法仅仅是对interrupt标识的一个判断,并不会影响标识发生任何改变(只读取中断状态,不修改状态)!

import java.util.concurrent.TimeUnit;

public class ThreadisInterrupted1 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread() {
            @Override
            public void run() {
                while (true) {
                    /**
                     * sleep是可中断方法,会捕获到中断信号,从而干扰结果
                     * 因为可中断方法捕获到了中断信号(signal)之后会擦除掉interrupt的标识
                     * 所以这里暂不使用可中断方法
                     */
                }
            }
        };

        // 在线程启动前,将其设置为守护线程。防止JVM无法退出
        thread.setDaemon(true);
        thread.start();

        TimeUnit.SECONDS.sleep(1);
        System.out.printf("Thread is interrupted ? %s\n", thread.isInterrupted());
        System.out.println("线程thread调用interrupt前的状态:" + thread.getState());

        thread.interrupt();
        System.out.printf("Thread is interrupted ? %s\n", thread.isInterrupted());
        System.out.println("线程thread调用interrupt后的状态:" + thread.getState());
    }
}

输出结果:

Thread is interrupted ? false
线程thread调用interrupt前的状态:RUNNABLE
Thread is interrupted ? true
线程thread调用interrupt后的状态:RUNNABLE

处于运行期且非阻塞的状态的线程,无法被中断(没有调用可中断方法),但中断标志位显示:线程被调用了中断方法,只是线程状态没有发生改变

中断方法捕获到了中断信号(signal) 之后,也就是捕获了InterruptedException异常之后会擦除掉interrupt的标识

import java.util.concurrent.TimeUnit;

public class ThreadisInterrupted {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread() {
            @Override
            public void run() {
                while (true) {
                    /**
                     * 因为sleep是可中断方法,会捕获到中断信号
                     */
                    try {
                        TimeUnit.MINUTES.sleep(1);
                    } catch (InterruptedException e) {
                        System.out.printf(Thread.currentThread().getName() + " is interrupted ?(此时在catch语句) %s\n", isInterrupted());
                    }
                }
            }
        };

        // 在线程启动前,将其设置为守护线程。防止JVM无法退出
        thread.setDaemon(true);
        thread.setName("thread1");
        thread.start();

        TimeUnit.SECONDS.sleep(1);
        System.out.printf(thread.getName() + " is interrupted ? %s\n", thread.isInterrupted());

        thread.interrupt();
        TimeUnit.SECONDS.sleep(1);
        System.out.printf(thread.getName() + " is interrupted ? %s\n", thread.isInterrupted());
    }
}

输出结果:

thread1 is interrupted ? false
thread1 is interrupted ?(此时在catch语句) false
thread1 is interrupted ? false

由于在run方法中使用了sleep这个可中断方法,它会捕获到中断信号,并且会擦除interrupt标识,因此程序的执行结果都会是false!

可中断方法捕获到了中断信号之后(已经抛出异常,“重新做人了”——如果线程此时恰好处于WAITING或者TIMED_WAITING状态,就会抛出一个InterruptedException,并且线程被唤醒。),为了不影响线程中其他方法的执行,将线程的interrupt标识复位是一种很合理的设计。

interrupted

interrupted是一个静态方法,虽然其也用于判断当前线程是否被中断,但是它和成员方法isInterrupted有很大的区别的,调用该方法会直接擦除掉线程的interrupt标识

如果当前线程被打断了,那么第一次调用interrupted方法会返回true,并且立即擦除interrupt标识;第二次包括以后的调用永远都会返回false,除非在此期间线程又一次地被打断

import java.util.concurrent.TimeUnit;

public class Threadinterrupted {
    public static void main(String[] args) throws InterruptedException {

        Thread thread = new Thread() {
            @Override
            public void run() {
                while (true) {
                    System.out.printf(Thread.currentThread().getName() + " is interrupted ? %s\n", interrupted());
                }
            }
        };

        thread.setDaemon(true);
        thread.start();

        // Short block makes sure that the thread is started
        TimeUnit.SECONDS.sleep(1);
        thread.interrupt();
    }
}

输出结果:

...
Thread-0 is interrupted ? false
Thread-0 is interrupted ? false
Thread-0 is interrupted ? true
Thread-0 is interrupted ? false
Thread-0 is interrupted ? false
Thread-0 is interrupted ? false
...

当你可能要被大量中断,并且你想确保只处理一次中断时,就可以使用这个方法。

如果一个线程,在没有执行可中断方法之前就被打断,那么其接下来将执行可中断方法,比如sleep会发生什么样的情况呢?

import java.util.concurrent.TimeUnit;

public class Test {
    public static void main(String[] args) {
        // 1. 判断当前线程是否被中断
        System.out.println("main thread is interrupted ? " + Thread.interrupted());
        // 2. 中断当前线程
        Thread.currentThread().interrupt();
        // 3. 判断当前线程是否被中断
        // 此时线程设置的interrupt标识为true
        System.out.println("main thread is interrupted ? " + Thread.currentThread().isInterrupted());

        try {
            // 4. 当前线程执行可中断方法
            // 如果一个线程设置了interrupt标识,那么接下来的可中断方法会立即中断
            TimeUnit.MINUTES.sleep(3);
        } catch (InterruptedException e) {
            // 5. 捕获中断信号
            System.out.println("捕获中断信号");
        }
    }
}

输出结果:

main thread is interrupted ? false
main thread is interrupted ? true
捕获中断信号 // 立马输出而不是执行TimeUnit.MINUTES.sleep(3)
import java.util.concurrent.TimeUnit;

public class Test {
    public static void main(String[] args) {
        // 1. 判断当前线程是否被中断
        System.out.println("main thread is interrupted ? " + Thread.interrupted()); // false
        // 2. 中断当前线程
        Thread.currentThread().interrupt();
        // 3. 判断当前线程是否被中断
        System.out.println("main thread is interrupted ? " + Thread.currentThread().isInterrupted());  // true
        System.out.println("main thread is interrupted ? " + Thread.interrupted());  // true
        System.out.println("main thread is interrupted ? " + Thread.interrupted());  // false

        try {
            // 10秒后程序结束
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            System.out.println("捕获中断信号");
        }
    }
}

中断机制的使用场景

  • 点击某个桌面应用中的关闭按钮时(比如你关闭 IDEA,不保存数据直接中断好吗?);
  • 某个操作超过了一定的执行时间限制需要中止时;
  • 多个线程做相同的事情,只要一个线程成功其它线程都可以取消时;
  • 一组线程中的一个或多个出现错误导致整组都无法继续时;
  • ThreadPoolExecutor中的shutdownNow方法会遍历线程池中的工作线程并调用线程的interrupt方法来中断线程。

ThreadGroup

Java中用ThreadGroup来表示线程组,可以使用线程组对线程进行批量控制

ThreadGroup和Thread的关系:每个Thread必然存在于一个ThreadGroup中,Thread不能独立于ThreadGroup存在。执行main()方法线程的名字是main,如果在new Thread时没有显式指定,那么默认将父线程当前执行new Thread的线程)线程组设置为自己的线程组。

在Thread的构造函数中,可以显式地指定线程的Group,也就是ThreadGroup:

public class Thread implements Runnable {
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
        // ...
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            /* Determine if it's an applet or not */

            /* If there is a security manager, ask the security manager what to do. */
            if (security != null) {
                g = security.getThreadGroup();
            }

            /* If the security doesn't have a strong opinion of the matter use the parent thread group. */
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }
    }
}

如果在构造Thread的时候没有显示地指定一个ThreadGroup,那么子线程将会被加入父线程所在的线程组

public class ThreadGroupConstruction {
    public static void main(String[] args) {
        // 创建线程thread1,不指定其所在ThreadGroup
        Thread thread1 = new Thread("thread1");

        // 创建ThreadGroup
        ThreadGroup group = new ThreadGroup("TestGroup");

        // 创建线程thread2,并指定其所在ThreadGroup
        Thread thread2 = new Thread(group, "thread2");

        ThreadGroup mainThreadGroup = Thread.currentThread().getThreadGroup();

        System.out.println("main thread belongs to group: " + mainThreadGroup.getName());
        System.out.println("thread1 belongs to group: " + thread1.getThreadGroup());
        System.out.println("thread2 belongs to group: " + thread2.getThreadGroup().getName());
        System.out.println();

        System.out.println("thread1 and main thread belong to the same ThreadGroup ? " + (thread1.getThreadGroup() == mainThreadGroup));
        System.out.println("thread2 and main thread belong to the same ThreadGroup ? " + (thread2.getThreadGroup() == mainThreadGroup));
        System.out.println("thread1 and thread2 belong to the same ThreadGroup ? " + (thread1.getThreadGroup() == thread2.getThreadGroup()));
    }
}

结果输出:

main thread belongs to group: main
thread1 belongs to group: java.lang.ThreadGroup[name=main,maxpri=10]
thread2 belongs to group: TestGroup

thread1 and main thread belong to the same ThreadGroup ? true
thread2 and main thread belong to the same ThreadGroup ? false
thread1 and thread2 belong to the same ThreadGroup ? false

默认情况下,新的线程都会被加入到main线程所在的group中,main线程的group名字同线程名:

  • main线程所在的ThreadGroup称为main;
  • 构造一个线程的时候如果没有显式地指定ThreadGroup,那么它将会和父线程同属于一个ThreadGroup。
import java.util.Arrays;

public class ThreadGroupActive {
    public static void main(String[] args) {
        Thread thread = new Thread(
                () -> {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
        );
        thread.start();

        ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
        System.out.println(threadGroup.activeCount());

        Thread[] threads = new Thread[threadGroup.activeCount()];
        threadGroup.enumerate(threads);
        Arrays.asList(threads).forEach(System.out::println);
    }
}

输出结果:

3
Thread[main,5,main]
Thread[Monitor Ctrl-Break,5,main]
Thread[Thread-0,5,main]
posted @ 2021-03-14 11:35  chenzufeng  阅读(152)  评论(0)    收藏  举报