通过源码看Java的Thread类

288 阅读3分钟

掌握Thread类是学习Java并发编程的基础,本文结合源码,带你了解Thread的一些实现。本文中源码来自Java8。

Thread是Runnable接口的子类,既是任务本身,又是执行者。 image.png 在使用线程池时,将任务与执行者进行了解耦,更加灵活。

1 线程状态

hread.State枚举类,定义了Java层面线程的状态集。包括:NEW, RUNNABLE, BLOCKED, WAITING,TIMED_WAITING, TERMINATED image.png 源码中注释描述如下:

  • RUNNABLE:处于可运行状态的线程正在Java虚拟机中执行,但它可能正在等待来自操作系统(如处理器)的其他资源。
  • BLOCKED:等待监视器锁的线程的线程状态。即被synchronized阻塞;
  • WAITING:处于等待状态的线程正在等待另一个线程执行特定的操作。调用这些方法时会进入WAITING:
    • Object.wait with no timeout
    • Thread.join with no timeout
    • LockSupport.park
  • TIMED_WAITING:与WAITING相似,只是带超时时间

注意,BLOCKED指等待Monitor锁,而不是CPU、网络等资源。线程被操作系统阻塞,在Java层面仍是RUNNABLE的。

2 Thread ID

是由threadSeqNumber递增得到的。在同一个进程中,每个线程的ID唯一。 image.png image.png

3 启动线程

new Thread()创建线程后,其volatile int threadStatus = 0,表示尚未启动,调用start()才会启动它。

从源码注释可以读到:

  1. start()导致该线程开始执行,Java虚拟机调用这个线程的run方法;
  2. 两个线程并发运行:当前线程(从调用start方法返回)和另一个线程(执行其run方法)
  3. 多次启动一个线程是不合法的image.png 该方法被synchronized,避免并发。线程只能被start一次,否则抛出IllegalThreadStateExceptionimage.png

4 run方法

Thread的run(),其实执行的是target属性的run()。 image.png 如果Thread没有设置target属性,线程启动后将不做任何事情并返回。

// 不做任何事
Thread thread = new Thread();  
thread.start();

image.png

5 join原理

用于线程间同步。如下,在主线程中调用t1.join(),会让主线程阻塞;结束阻塞的条件是:join超时或t1线程结束。

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(3L);
            } catch (InterruptedException e) {
            }
        });
        t1.start();

        log.info("join开始");
        t1.join();
        log.info("join结束");
    }

image.png

线程退出时notifyAll

注意,wait(0)将一直阻塞,最终是谁调用了t1.notify()或t1.notifyAll()?
JDK在线程结束前会调用notifyAll()方法。 查看openjdk16源码(github地址)的 src/hotspot/share/runtime/thread.cpp 文件,可以看到:在JavaThread::exit会调用notify_all

void Thread::call_run() {
  ...
  this->pre_run();
  this->run();
  this->post_run();
  ...
}

void JavaThread::post_run() {
  this->exit(false);
  ...
}

void JavaThread::exit(bool destroy_vm, ExitType exit_type) {
  ...
  ensure_join(this);
  ... 
}

static void ensure_join(JavaThread* thread) {
  // 看这儿
  lock.notify_all(thread);
  ...
}

6 interrupt原理

Thread静态方法interrupted(),会清除打断标记;而isInterrupted()不会清除打断标记。

  • 如果线程正在sleep、wait、join等方法的阻塞中,被打断时会抛出InterruptedException,并清除打断标记;
  • 如果打断的正在运行的线程,只是设置打断标记为true,是否继续执行取决于你自己
    private static void testInterrupt() throws InterruptedException {
        Thread worker = new Thread(() -> {
            while (true) {
                Thread current = Thread.currentThread();
                boolean interrupted = current.isInterrupted();
                // 检查interrupted标记
                if (interrupted) {
                    log.debug(" 打断状态: {}", interrupted);
                    break;
                }
                // do something
                log.debug("正常执行");
            }
        }, "worker");
        worker.start();

        TimeUnit.MICROSECONDS.sleep(500);
        worker.interrupt();
        log.debug(" 打断状态: {}", worker.isInterrupted());
    }

image.png

  • park中的线程被打断,不会抛出异常,设置打断标记为true,结束阻塞继续执行;
private static void testPark() throws InterruptedException {
    Thread t1 = new Thread(() -> {
        log.debug("park...");
        LockSupport.park();
        log.debug("继续执行...");
    }, "t1");
    t1.start();


    TimeUnit.SECONDS.sleep(2);
    t1.interrupt();
    log.debug("t1的打断状态:{}", t1.isInterrupted());
}

image.png

  • 如果线程的打断标记已经是true, 则调用LockSupport.park()不会阻塞;
private static void test4() throws InterruptedException {
    Thread t1 = new Thread(() -> {
        for (int i = 0; i < 3; i++) {
            log.debug("park...");
            LockSupport.park();
            log.debug("打断状态:{}", Thread.currentThread().isInterrupted());
        }
    });
    t1.start();

    TimeUnit.SECONDS.sleep(2);
    t1.interrupt();
}

image.png

interrupt源码

interrupt() :仅设置标记 image.png interrupted():static方法会将标记重置为false,而非静态的方法不会。 image.png image.png