讨论几个Thread的常用API
本文针对
Thread类中常用的API进行介绍。以下是一些常见的方法:
| 方法 | 功能 |
|---|---|
| public void start() | 启动⼀个新线程;Java虚拟机调⽤此线程的run()⽅法 |
| public void run() | 线程启动后调⽤该⽅法 |
| public void setName(String name) | 给当前线程取名字 |
| public void getName() | 获取当前线程的名字。默认⼦线程是Thread-索引,主线程是main |
| public static Thread currentThread() | 获取当前线程对象,代码在哪个线程中执⾏ |
| public static native void yield() | 提示线程调度器让出当前线程对 CPU的使⽤ |
| public static void sleep(long time) | 让当前线程休眠多少毫秒再继续执⾏ |
| public final int getPriority() | 返回此线程的优先级 |
| public final void setPriority(int priority) | 更改此线程的优先级,常⽤1、5、10 |
| public void interrupt() | 中断这个线程,异常处理机制 |
| public static boolean interrupted() | 判断当前线程是否被打断,清除打断标记 |
| public boolean isInterrupted() | 判断当前线程是否被打断,不清除打断标记 |
| public final void join() | 等待这个线程结束 |
| public final void join(long millis) | 等待这个线程死亡millis毫秒,0 意味着永远等待 |
| public final native boolean isAlive() | 线程是否存活(还没有运⾏完毕) |
| public final void setDaemon(boolean on) | 将此线程标记为守护线程或⽤户线程 |
以上为一些常见方法的概览,接下来将针对部分方法进行详细讨论。
1. start() 和 run()
start()方法为native方法,用于申请资源并开启新线程,用于运行run()方法中的任务。具体在[[开启线程有几种方法?#^be73e1 | 开启线程有几种方法?]] 一文中有描述。
2. sleep()与yield()
2.1 sleep()
sleep()方法可以让当前线程从Running状态进入 Timed Waiting状态;- 其他线程可以使用
interrupt()方法打断其睡眠状态,此时,sleep()方法将会抛出Interrupted Exception异常; - 睡眠结束后,线程将从Timed Waiting状态转为Runnable状态,若能争抢到资源,则会进入Running状态。因此,可知睡眠结束后,线程也未必会立刻执行。
应用场景:
- 暂停当前现场执行,用于实现线程的定时等待;
- 对于死循环的轮训场景中,防止空转造成CPU占用过高,浪费CPU资源。
2.2 yield()
- 此方法可以让当前线程从Running状态进入 Runnable状态。可以理解为“谦让”,若“谦让”后没有其他线程使用资源,那么当前线程依然会进入Running状态继续运行;
- 具体的实现取决于操作系统的任务调度器。
应用场景
与sleep相似,也是用于让出资源。如ConcurrentHashMap类中的initTable()方法。如下:
1 | private final Node<K,V>[] initTable() { |
由于yield()方法的让出时间不由用户指定,只能保证当前会让出CPU调度。类似于一个建议,力度稍弱于sleep()。
3. join()
Waiting for the finalization of a thread.
In some situations, we will have to wait for the finalization of a thread. For example, we may have a program that will begin initializing the resources it needs before proceeding with the rest of the execution. We can run the initialization tasks as threads and wait for its finalization before continuing with the rest of the program. For this purpose, we can use the join() method of the Thread class. When we call this method using a thread object, it suspends the execution of the calling thread until the object called finishes its execution.
以上摘自《Java 7 Concurrency Cookbook》,意思是:等待线程的完成。在某些情况下,我们必须等待线程的完成。例如,我们可能有一个程序,该程序将在继续执行其余部分之前开始初始化所需的资源。我们可以将初始化任务作为线程运行,并等待其完成,然后再继续程序的其余部分。为此,我们可以使用 Thread 类的 join() 方法。当我们使用线程对象调用此方法时,它会挂起调用线程的执行,直到被调用的对象完成执行。
简单点来说,就是异步转同步。代码如下:
1 |
|
关于异步与同步, 可以简单的按以下方式理解:
- 同步: 执行后,需要等待到结果才继续向下执行;
- 异步:执行后,无需等待结果返回就能继续向下执行。
此外,还有一个带参的join(long timeout)方法。就是有一个超时时间,超过timeout后,将不会继续等待并向下执行。其实join()方法调用的就是有参的join,参数为0。
源码概览
1 | public final void join(long millis) throws InterruptedException { |
整体逻辑清晰明了。
其中有一点需要注意:以上代码中
wait(delay)方法,可以让程序阻塞等待。但是是谁来唤醒它的呢?
这块在JVM中的机制是,当一个线程终止时,会调用自身的notifyAll()方法,会通知所有等待在该线程对象上的线程。可以用代码进行验证:
1 |
|
4. interrupt()
对于interrupt,首先要对其有一个正确的认识。打断可以理解为线程中的一个标志位属性,表示一个运行中的线程是否被其他线程执行了打断操作。如下为源码:
1 | public void interrupt() { |
打断线程可能会有以下两种情况:
- 打断运行中的线程:打断操作并不会影响线程的正常执行,只会将打断标志位置为
true,由线程自己的代码逻辑进行处理; - 打断阻塞过程中的线程:会让线程抛出一个
InterruptedException异常,结束线程的运行。可以捕获此异常对其进行处理。
需要注意的是,当
sleep()、wait()、join()等方法使线程阻塞时被打断会抛出异常,在其抛出异常前,打断标志位会被清除,此时调用isInterrupted()方法会返回false结果。代码如下:
1 | public static void main(String[] args) { |
结果如下:
1 | 2024-04-27 16:49:20.461 [Thread-0] WARN fun.keepon.juc.Demo8 - 线程被打断了 |
此外,打断
LockSupport.park()后,打断状态并不会清空。
4.1 应用于安全终止线程
通过对interrupt()的讨论可知,打断状态是线程的一个标志位,终端操作是一个非常简便的线程间交互的方式,而这种交互方式非常适用于用来取消或者停止任务。代码如下:
1 |
|
整体流程如下:

这样可以更加优雅地停止任务,给任务线程一个进行收尾工作的机会,防止粗暴关闭线程。
此外,通过以上方案不难发现,也可以使用一个普通变量实现两阶段终止的优雅关闭。但是需要注意线程间的可见性,在后期讨论线程间可见性时会继续使用此案例。