重拾 Java 高并发:万字长文带你探索 Thread 类源码(二)

63 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 8 天,点击查看活动详情

上一篇文章重拾 Java 高并发:万字长文带你探索 Thread 类源码(一),我们一起学习了 Thread 类的类图结构、成员变量、状态定义等,今天,我们将继续深入学习 Thread 类的源码,一起来看看它的构造方法和常见方法~

Thread 类的构造方法

通过类图,Thread 类中的所有构造方法如图所示:

image.png

其底层其实都调用了 init 方法,只不过是方法签名不同罢了。接下来我们一起看看 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) {
        /* 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();
        }
    }

    /* checkAccess regardless of whether or not threadgroup is
       explicitly passed in. */
    g.checkAccess();

    /*
     * Do we have the required permissions?
     */
    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();
}
  • 线程的名称为空,抛出空指针异常
  • 获取父线程
  • 获取系统安全管理器
  • 若线程组为空,且获取系统安全管理器不为空,则从系统安全管理器中获取一个线程分组;若线程分组为空,则从父线程中获取线程分组
  • 检查线程组的访问权限
  • 当前线程继承父线程的相关属性
  • 设置线程id

Thread 类中的构造方法是被创建 Thread 对象的线程调用的,此时,调用 Thread 的构造方法创建线程的线程就是父线程,在init()方法中,新创建的 Thread 线程会继承父线程的部分属性

run() 方法

既然 Thread 类实现了 Runnable 接口,则 Thread 类就需要实现 Runnable 接口的run()方法,如下所示:

image.png

  • 可以看到,Thread 类中的 run() 方法实现非常简单,只是调用了 Runnable 对象的 run() 方法。所以,真正的任务是运行在run()方法中的。
  • 直接调用Runnable接口的run()方法不会创建新线程来执行任务,如果需要创建新线程执行任务,则需要调用Thread类的start()方法。

start() 方法

public synchronized void start() {
    if (threadStatus != 0)
        throw new IllegalThreadStateException();
    group.add(this);
    boolean started = false;
    try {
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
           
        }
    }
}

private native void start0();

start() 方法有以下几个关键点:

  • start()方法使用 synchronized 关键字修饰,说明start()方法是同步的
  • 它会在启动线程前,检查线程的状态,如果不是初始化状态,则直接抛出异常。所以,一个线程只能启动一次,多次启动是会抛出异常的。
  • 调用start()方法后,新创建的线程就会处于就绪状态(如果没有分配到CPU执行),当有空闲的CPU时,这个线程就会被分配CPU来执行,此时线程的状态为运行状态,JVM会调用线程的run()方法执行任务。

sleep() 方法

image.png

  • sleep() 方法会让当前线程休眠一定的时间,这个时间通常是毫秒值
  • 调用sleep()方法使线程休眠后,线程不会释放相应的锁

join()方法

join()方法会一直等待线程超时或者终止,代码如下所示。

image.png

join()方法的使用场景往往是启动线程执行任务的线程,调用执行线程的join()方法,等待执行线程执行任务,直到超时或者执行线程终止。