掌握Thread类是学习Java并发编程的基础,本文结合源码,带你了解Thread的一些实现。本文中源码来自Java8。
Thread是Runnable接口的子类,既是任务本身,又是执行者。
在使用线程池时,将任务与执行者进行了解耦,更加灵活。
1 线程状态
hread.State枚举类,定义了Java层面线程的状态集。包括:NEW, RUNNABLE, BLOCKED, WAITING,TIMED_WAITING, TERMINATED
源码中注释描述如下:
- 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唯一。
3 启动线程
在new Thread()创建线程后,其volatile int threadStatus = 0,表示尚未启动,调用start()才会启动它。
从源码注释可以读到:
start()导致该线程开始执行,Java虚拟机调用这个线程的run方法;- 两个线程并发运行:当前线程(从调用start方法返回)和另一个线程(执行其run方法);
- 多次启动一个线程是不合法的。
该方法被
synchronized,避免并发。线程只能被start一次,否则抛出IllegalThreadStateException。
4 run方法
Thread的run(),其实执行的是target属性的run()。
如果Thread没有设置target属性,线程启动后将不做任何事情并返回。
// 不做任何事
Thread thread = new Thread();
thread.start();
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结束");
}
线程退出时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());
}
- 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());
}
- 如果线程的打断标记已经是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();
}
interrupt源码
interrupt() :仅设置标记
interrupted():static方法会将标记重置为false,而非静态的方法不会。