阅读Thread类源码,熟悉常用方法

148 阅读14分钟

1. 创建一个线程

Thread 类共有9个构造方法,其中一个是包内访问,其余8个都是 public 修饰。

+ Thread()
+ Thread(Runnable)
~ Thread(Runnable, AccessControlContext)
+ Thread(Runnable, String)
+ Thread(String)
+ Thread(ThreadGroup, Runnable)
+ Thread(ThreadGroup, Runnable, String)
+ Thread(ThreadGroup, Runnable, String, long)
+ Thread(ThreadGroup, String)

查看 Thread 类源码,这些构造方法最终调用了第365行的init方法,该方法的定义(去掉了方法中的注释)如下。

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
    if (name == null) {
        throw new NullPointerException("name cannot be null");
    }

    this.name = name;

    Thread parent = currentThread();
    SecurityManager security = System.getSecurityManager();
    if (g == null) {
        if (security != null) {
            g = security.getThreadGroup();
        }

        if (g == null) {
            g = parent.getThreadGroup();
        }
    }

    g.checkAccess();

    if (security != null) {
        if (isCCLOverridden(getClass())) {
            security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
        }
    }

    g.addUnstarted();

    this.group = g;
    this.daemon = parent.isDaemon();
    this.priority = parent.getPriority();
    if (security == null || isCCLOverridden(parent.getClass()))
        this.contextClassLoader = parent.getContextClassLoader();
    else
        this.contextClassLoader = parent.contextClassLoader;
    this.inheritedAccessControlContext =
            acc != null ? acc : AccessController.getContext();
    this.target = target;
    setPriority(priority);
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    /* Stash the specified stack size in case the VM cares */
    this.stackSize = stackSize;

    /* Set thread ID */
    tid = nextThreadID();
}

2. 线程名称

init 方法首先判断线程名称是否为空,如果为空,则会抛出NullPointerException异常。线程名称可以通过构造函数传进来,也可以由 Thread 类默认生成,其生成规则为Thread-<线程编号>,线程编号是由 nextThreadNum 函数生成的,该方法是私有、静态并由 synchronized 修饰的,相关代码如下:

private static int threadInitNumber;

private static synchronized int nextThreadNum() {
    return threadInitNumber++;
}

除了通过构造函数设置线程名称以外,Thread 提供了setName(String)方法来设置线程名称,并提供getName()方法获取线程名称。如果线程处于非 NEW (新建)状态时,将调用原生方法setNativeName(String)更新线程名称。

public final synchronized void setName(String name) {
    checkAccess();
    if (name == null) {
        throw new NullPointerException("name cannot be null");
    }

    this.name = name;
    if (threadStatus != 0) {
        // setNativeName 是一个原生方法
        setNativeName(name);
    }
}

public final String getName() {
    return name;
}

3. 父线程

Threa 类中通过调用 native 方法currentThread()获取创建 Thread 对象的线程作为父线程。

public static native Thread currentThread();

4. 线程组

ThreadGroup 是 Java 中用来管理一组线程的类,它允许开发者将一组线程归并到一个组中,便于对这些线程统一管理。线程组是父子结构的,一个线程组可以集成其他线程组,同时也可以拥有其他子线程组。

在 init 方法中,如果传入 ThreadGroup 为空,则会首先通过System.getSecurityManager()获取当前使用的安全管理器实例,如果该实例不为空,则将其所属的 ThreadGroup 设置为当前线程的 ThreadGroup,否则设置当前线程的 ThreadGroup 为线程所属的 ThreadGroup。

Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
    if (security != null) {
        g = security.getThreadGroup();
    }

    if (g == null) {
        g = parent.getThreadGroup();
    }
}

g.checkAccess();

if (security != null) {
    if (isCCLOverridden(getClass())) {
        security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
    }
}

g.addUnstarted();

this.group = g;

System.getSecurityManager()是 Java 中的一个方法,用于获取当前正在使用的 安全管理器(SecurityManager) 实例。如果返回值为 null,则说明当前没有启用安全管理器。安全管理器在 Java 应用程序中用于限制和控制程序的行为,特别是在执行不受信任代码时,它可以控制程序是否有权限访问系统资源,如文件、网络、系统属性等。在标准 Java 应用程序中,默认不会启用安全管理器,可以通过 JVM 启动参数启用安全管理器并指定权限策略文件。从 Java 17 开始,SecurityManager 被标记为 过时(deprecated),并计划在未来版本中移除。安全管理器的许多功能现在通过现代的安全框架(如容器化和操作系统级别的安全机制)实现。

默认情况下 Java 应用程序不会启动安全管理器,第13-19行代码不会被执行。线程通过安全校验后,调用addUnstarted()方法,当前线程组未启动线程个数加1,该方法一个线程安全的。然后,将线程组实例赋值给 group 属性。

void addUnstarted() {
    synchronized(this) {
        if (destroyed) {
            throw new IllegalThreadStateException();
        }
        nUnstartedThreads++;
    }
}

Thread 类提供了getThreadGroup()方法来获取当前线程所属的线程组。

public final ThreadGroup getThreadGroup() {
    return group;
}

5. 守护线程

守护线程是指为其他线程服务的线程,守护线程的运行不会依赖于其他线程,在 JVM 中,所有非守护线程都执行完毕后,无论有没有守护线程,虚拟机都会自动退出,即使守护线程还没有运行完成,JVM 都会直接终止它们。守护线程适合做一些辅助性或后台任务,例如:内存管理、日志输出或者定时任务等。

Java 在创建 Thread 对象时,默认根据父线程来设置当前线程是否为守护线程,在线程启动前(调用 start() 方法之前) 可以通过调用setDaemon(boolean)方法设置守护线程。

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
    // ...
    this.daemon = parent.isDaemon();
    // ...
}

// Tests if this thread is alive. A thread is alive if it has been started and has not yet died.
public final native boolean isAlive();

public final void setDaemon(boolean on) {
    checkAccess();
    if (isAlive()) {
        throw new IllegalThreadStateException();
    }
    daemon = on;
}

Thread 类也提供了isDeamon()方法来获取当前线程是否为守护线程。

public final boolean isDaemon() {
    return daemon;
}

6. 线程优先级

线程优先级是用于指示线程调度器应该如何安排线程的一种属性,在多线程环境中,线程优先级可以影响线程获取 CPU 执行时间的机会,但不能一直保证优先级高的线程一定会优先运行。

Java 创建 Thread 对象时默认获取父线程的线程优先级,然后调用setPriority(int)方法来设置线程优先级。

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
    // ...
    this.priority = parent.getPriority();
    setPriority(priority);
    // ...
}

Thread 对象创建后,可以通过setPriority(int)方法更改线程优先级,通过getPriority()方法获取当前线程的优先级。

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;
}
  1. Thread 类中通过两个常量MAX_PRIORITYMIN_PRIORITY设置线程优先级的最大(10)、最小值(1),如果设置的线程优先级的值超出这个范围,将抛出IllegalArgumentException异常。
  2. 如果当前线程所属的线程组不为空(一般都不为空)并且设置的线程优先级大于线程组的优先级,则会使用线程组的最大优先级来配置当前线程的线程优先级,然后调用原生方法setPriority0更新线程优先级。

7. 线程状态

Thread 类内置了线程状态静态枚举类 State,共包含了 NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED 等6种状态,使用私有的原子变量threadStatus来记录当前线程的状态,并提供getState()方法获取对应的线程状态。

public State getState() {
    // get current thread state,将int类型的值转为State枚举类型
    return sun.misc.VM.toThreadState(threadStatus);
}

8. 线程控制

8.1. yield

Thread 类提供了静态本地方法yield()方法,可以使得当前线程主动让出 CPU 使用权,让操作系统的线程调度器重新选择一个线程来运行,当前线程不会进入阻塞状态而是重回 RUNNABLE 状态,操作系统的线程调度器可能重新选择当前线程运行,也有可能选择其他线程,通常用于优化线程的调度,避免某个线程长时间占用 CPU 资源。

public static native void yield();

8.2. sleep

Thread 类提供了2种 sleep() 方法:静态本地的sleep(long)、静态方法sleep(long, int),其中,第1个参数单位为毫秒、第2个参数表示纳秒。调用 sleep 方法会阻塞线程并切换为 TIMED_WAITING 状态,此时线程不会消耗 CPU 状态。等时间到了以后,线程会自动转为 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 方法会抛出 InterruptedException 异常,触发条件是,在线程睡眠期间,其他线程调用当前线程的 interrupt 方法,此时,JVM 会立刻唤醒当前线程,结束其休眠状态,抛出 InterruptedException 异常,然后擦除线程的中断状态,也就说,在 catch InterruptedException 异常 时,此时当前线程的 isInterrupted 方法返回值为 false。

public class SleepExample {
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                System.out.println("子线程被中断,其中断状态为:" + Thread.currentThread().isInterrupted());
            }
        });

        thread1.start();

        Thread.sleep(1000);

        thread1.interrupt();
    }
}

8.2.1. sleep 和 yield 方法对比

特性Thread.yieldThread.sleep
行为暂时让出CPU,但不会阻塞线程阻塞线程,进入TIMED_WAITING状态
时间长度不可控,完全有线程调度器来决定有明确的时间长度
线程状态切回 RUNNABLE切回 TIMED_WAITING
是否消耗CPU可能消耗不消耗
使用场景调度优化,给其他线程机会运行控制线程运行节奏;模拟耗时行为

8.3. join

Thread 类提供了 join 方法,让当前线程等待另一个线程结束后再运行,例如,在线程 A 上调用线程 B 的 join() 方法后,线程 A 就会进入 WAITING 或者 TIMED_WAITING 状态,直到线程 B 执行完成或者达到设置的超时时间。另外,join 方法应该在 start 方法后调用,否则 join 方法不会起作用。

public final synchronized void join(long millis) throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;

    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (millis == 0) {
        while (isAlive()) {
            wait(0);
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

public final synchronized void join(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++;
    }

    join(millis);
}

public final void join() throws InterruptedException {
    join(0);
}

通过join(long millis)函数,可以了了解到,join 方法的实现本质是调用了 Object.wait 方法实现的。当前线程运行结束后,JVM 会通知调用 join 方法的线程,举个例子,如果线程 A 的运行耗时需要1秒钟,但是线程B调用 join 方法时设置等待时间为2秒钟,则系统在运行1秒钟后会执行线程B剩余的代码。

public class JoinExample {
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread1();

        System.out.println("当前系统时间:" + System.currentTimeMillis());
        thread1.start();
        thread1.join(5L * 1000);
        System.out.println("当前系统时间:" + System.currentTimeMillis());
    }
}

class Thread1 extends Thread {
    @Override
    public void run() {
        try {
            Thread.sleep(1000);
            System.out.println("子线程运行结束");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

如果 join 设置的超时时间小于线程运行时间,那么当超时时间一到,调用线程就会切换为 RUNNABLE 状态。

public class JoinExample {
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread1();

        thread1.start();
        thread1.join(1000);

        System.out.println("主线程状态:" + Thread.currentThread().getState());
    }
}

class Thread1 extends Thread {
    @Override
    public void run() {
        try {
            Thread.sleep(2000);
            System.out.println("子线程运行结束");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

8.4. interrupt

Thread 类提供了 interrupt 方法用来中断当前线程,对于以下几种情况对应不同的操作结果。

  1. 如果不是当前线程自己调用 interrupt() 方法,有可能抛出 SecurityException 异常;
  2. 如果当前线程调用了 wait、sleep、或者 join 方法进入阻塞状态(BLOCKED、WAITING 或者 TIMED_WAITING),则其中断状态会被清除并且将收到 InterruptedException 异常(就像之前分析 sleep 时遇到的那样);
  3. 如果当前线程在 InterruptibleChannel 上的 I/O 操作被阻塞,则通道将被关闭,线程被设置为中断状态,并且线程收到 java.nio.channels.ClosedByInterruptException 异常;
  4. 如果当前线程在 java.nio.channels.Selector 中被阻塞,线程被设置为中断状态,并且它将立即从 selection 操作中返回,可能带有非零值,就像调用了选择器的 wakeup 方法一样;
  5. 如果上述条件均不成立,则将线程设置为中断状态,会给当前线程传递中断信号,线程会根据自己业务判断是否停止运行
private volatile Interruptible blocker;
private final Object blockerLock = new Object();

void blockedOn(Interruptible b) {
    synchronized (blockerLock) {
        blocker = b;
    }
}

public void interrupt() {
    if (this != Thread.currentThread())
        checkAccess();

    synchronized (blockerLock) {
        Interruptible b = blocker;
        if (b != null) {
            interrupt0();           
            b.interrupt(this);
            return;
        }
    }
    interrupt0();
}

private native void interrupt0();

此外,Thread 类提供了 interrupted 和 isInterrupted 方法获取当前线程的中断状态,需要注意的是,除了前者是静态方法、后者是示例方法以外,调用 interrupted 方法将会检查并清除当前线程的中断状态 。

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

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

// 如果 ClearInterrupted 为 ture,则会重置线程的中断状态
private native boolean isInterrupted(boolean ClearInterrupted);

interrupt 示例

public class InterruptExample {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            while(true) {
                // if (Thread.interrupted()) {
                //     System.out.println("子线程的中断状态1:" + Thread.currentThread().isInterrupted());  // 输出为 false
                //     break;
                // }

                if (Thread.currentThread().isInterrupted()) {
                    System.out.println("子线程的中断状态1:" + Thread.currentThread().isInterrupted());  // 输出为 true
                    break;
                }
                System.out.println("running");
            }
        });

        thread.start();

        Thread.sleep(1);
        thread.interrupt();
    }
}

9. 类加载器

Thread 类提供了 setContextClassLoader 和 getContextClassLoader 方法用来设置、获取当前线程的类加载器,默认情况下,会使用父线程的类加载器作为当前线程的类加载器。

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
    // ...
    if (security == null || isCCLOverridden(parent.getClass()))
        this.contextClassLoader = parent.getContextClassLoader();
    else
        this.contextClassLoader = parent.contextClassLoader;
    // ...
}

@CallerSensitive
public ClassLoader getContextClassLoader() {
    if (contextClassLoader == null)
        return null;
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        ClassLoader.checkClassLoaderPermission(contextClassLoader,
                                               Reflection.getCallerClass());
    }
    return contextClassLoader;
}

public void setContextClassLoader(ClassLoader cl) {
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        sm.checkPermission(new RuntimePermission("setContextClassLoader"));
    }
    contextClassLoader = cl;
}

10. 其他辅助方法

10.1. 获取当前线程对象

Thread 类提供了一个静态本地方法currentThread()来获取当前线程对应的 Thread 对象。

public static native Thread currentThread();

10.2. 获取活动线程

Threa 类提供了一个静态方法 activeCount ,它以递归的方式统计当前线程所属线程组及其子组中活动线程的估计值。注意,这个方法的返回值是一个估计值,因为当递归遍历时,活动线程可能会发生变化,并且可能会收到操作系统线程调度器的影响。该方法主要用于调试或者监控。

public static int activeCount() {
    return currentThread().getThreadGroup().activeCount();
}

10.3. 检查是否锁定某个对象

Thread 类提供了 holdsLock 方法用来检测当前线程是否持有某个对象的监视器锁,如果持有,则返回 true。

public static native boolean holdsLock(Object obj);

代码示例

public class HoldsLockExample {
    private static final Object BLOCKER = new Object();

    static class Thread1 extends Thread {
        @Override
        public void run() {
            synchronized (BLOCKER) {
                System.out.println("当前线程是否持有锁:" + Thread.holdsLock(BLOCKER));
            }
        }
    }

    public static void main(String[] args) {
        Thread thread = new Thread1();
        thread.start();
    }
}

10.4. 获取线程的堆栈信息

Thread 类提供了三个方法来获取或打印线程的堆栈信息:dumpStack、getStackTrace 以及 getAllStackTraces。

10.4.1. dumpStack

public static void dumpStack() {
    new Exception("Stack trace").printStackTrace();
}

dumpStack 是一个静态方法,无返回值,调用该方法将在控制台中以标准错误的方式直接打印当前线程的堆栈信息,该方法是三个方法中性能消耗最小的,适合调试某一段代码的调用路径,用于快速定位问题。

public class StackTraceExample {
    public static void main(String[] args) {
        Thread thread1 = new Thread(Thread::dumpStack);

        thread1.start();
    }
}

输出结果

java.lang.Exception: Stack trace
	at java.lang.Thread.dumpStack(Thread.java:1336)
	at java.lang.Thread.run(Thread.java:748)

10.4.2. getStackTrace

public StackTraceElement[] getStackTrace() {
    if (this != Thread.currentThread()) {
        // check for getStackTrace permission
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkPermission(
                SecurityConstants.GET_STACK_TRACE_PERMISSION);
        }
        // optimization so we do not call into the vm for threads that
        // have not yet started or have terminated
        if (!isAlive()) {
            return EMPTY_STACK_TRACE;
        }
        StackTraceElement[][] stackTraceArray = dumpThreads(new Thread[] {this});
        StackTraceElement[] stackTrace = stackTraceArray[0];
        // a thread that was alive during the previous isAlive call may have
        // since terminated, therefore not having a stacktrace.
        if (stackTrace == null) {
            stackTrace = EMPTY_STACK_TRACE;
        }
        return stackTrace;
    } else {
        // Don't need JVM help for current thread
        return (new Exception()).getStackTrace();
    }
}

getStackTrace 是一个实例方法,通常用来获取当前线程的调用堆栈,返回结果是一个数组,代表线程从栈底到栈顶的调用栈信息,性能开销略高,主要用来分析、记录或处理调用堆栈。

public class StackTraceExample {
    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            for (StackTraceElement item : Thread.currentThread().getStackTrace()) {
                System.out.println(item);
            }
        });

        thread1.start();
    }
}

运行结果

java.lang.Thread.getStackTrace(Thread.java:1559)
control.StackTraceExample.lambda$main$0(StackTraceExample.java:10)
java.lang.Thread.run(Thread.java:748)

10.4.3. getAllStackTraces

public static Map<Thread, StackTraceElement[]> getAllStackTraces() {
    // check for getStackTrace permission
    SecurityManager security = System.getSecurityManager();
    if (security != null) {
        security.checkPermission(
            SecurityConstants.GET_STACK_TRACE_PERMISSION);
        security.checkPermission(
            SecurityConstants.MODIFY_THREADGROUP_PERMISSION);
    }

    // Get a snapshot of the list of all threads
    Thread[] threads = getThreads();
    StackTraceElement[][] traces = dumpThreads(threads);
    Map<Thread, StackTraceElement[]> m = new HashMap<>(threads.length);
    for (int i = 0; i < threads.length; i++) {
        StackTraceElement[] stackTrace = traces[i];
        if (stackTrace != null) {
            m.put(threads[i], stackTrace);
        }
        // else terminated so we don't put it in the map
    }
    return m;
}

getAllStackTraces 是一个静态方法,用来获取当前 JVM 中所有活动线程的调用堆栈信息,返回值是一个 HashMap,每一个键为表示一个活动的线程(包括普通线程和守护线程),每个值代表对应线程的调用堆栈信息,该方法的性能消耗最高,适用于分析系统中所有线程的状态,例如:死锁检测、性能监控等。