基础方法
暂停线程
Sleep 方法
作用:[让正在运行的线程进入睡眠状态,虚拟机/处理器在此期间不会调度该线程]{.red}
使用:
[JDK 1.0 默认提供睡眠方法:默认时间单位是毫秒(ms)]{.blue}
核心方法:
Thread.sleep()
代码演示
public class ThreadSleep
{
public static void main(String[] args) throws InterruptedException
{
Thread t1 = new Thread(() ->{
try{
log.debug("thread sleep...");
Thread.sleep(1000);
}
catch (InterruptedException e){
e.printStackTrace();
}
}, "t1");
// 线程开始执行
t1.start();
// 状态转换: 线程从 Runnable -> Timed_Waiting
log.debug("T1 state {}", t1.getState());
Thread.sleep(500);
log.debug("T1 state {}", t1.getState());
// 主线程中断线程
log.debug("开始执行中断 {}", System.currentTimeMillis());
t1.interrupt();
// 状态转换: 线程从 Runnable -> Timed_Waiting
log.debug("T1 state {}", t1.getState());
Thread.sleep(500);
log.debug("T1 state {}", t1.getState());
}
}
[JDK 1.5 提供的新的睡眠方法:单位可以根据需要使用]{.blue}
核心方法:
Time.Unit.SECONDS.sleep()
代码演示
public class ThreadSleep
{
public static void main(String[] args) throws InterruptedException
{
// 睡眠 1000 秒
TimeUnit.SECONDS.sleep(1000);
// 睡眠 1000 微秒
TimeUnit.MICROSECONDS.sleep(1000);
// 睡眠 1000 毫秒
TimeUnit.MILLISECONDS.sleep(1000);
// 睡眠 1000 纳秒
TimeUnit.NANOSECONDS.sleep(1000);
// 睡眠 1000 分钟
TimeUnit.MINUTES.sleep(1000);
// 睡眠 1000 小时
TimeUnit.HOURS.sleep(1000);
// 睡眠 1000 天
TimeUnit.DAYS.sleep(1000);
}
}
特点:
- [Sleep 方法会让线程主动交出处理器的使用权,也就是放弃自己的时间片]{.red}
- [Sleep 方法不会释放锁的所有权,也就是持有锁的线程睡眠时,别的线程依旧无法获取锁]{.red}
- [Sleep 方法会让线程从 RUNNABLE 转为 TIMED_WAITING 状态]{.red}
- [正在休眠的线程是可以被中断的,线程被中断之后会默认抛出异常,并且将中断标志重新置为 false]{.pink}
- 休眠结束的线程需要重新和其他线程竞争处理器的使用权
中断方法和锁相关内容之后会详细说明
Yield 方法
作用:[提示正在运行的线程主动放弃处理器的使用权,从而让其他线程执行]{.red}
使用:(这个方法的效果不是很明显,不过也有办法测试)
测试方式
- 第一条线程不做任何礼让,持续执行增加数量的操作;第二条线程会边进行礼让边执行增加数量的操作
- 观察到的结果应该是第一条线程输出语句更多,第二条线程输出语句更少,因为进行了礼让
:::warning
① 线程上下文切换和线程礼让都会导致运行的线程发生改变
② 所以只用一次 Thread.Yield 是没有办法判断究竟是线程切换还是线程礼让造成的
:::
测试代码
public class ThreadYield
{
public static void main(String[] args) throws InterruptedException
{
new Thread(()->{
int count = 0;
while (true)
{
log.debug("t1---> {}", ++count);
}
}, "t1").start();
new Thread(()->{
int count = 0;
while (true)
{
// 礼让其他线程
Thread.yield();
log.debug(" t2---> {}", ++count);
}
}, "t2").start();
}
}
特点:
- [正在运行的线程可能放弃也可能拒绝放弃自己对于处理器的使用权,完全取决于线程自己]{.red}
- Yield 方法的效果和线程优先级的效果类似,都是没有办法强制控制线程的
- [Yield 方法会让线程从 RUNNABLE(running)转换为 RUNNABLE(ready)状态]{.red}
- [Yield 方法也不会释放锁的所有权]{.red}
- [正在运行的线程可能放弃也可能拒绝放弃自己对于处理器的使用权,完全取决于线程自己]{.red}
Join 方法
作用:[调用线程阻塞式等待正在运行的线程结束]{.red}
使用:
核心方法:
thread.join()
(这里的thread
是等待的线程对象而不是调用线程或者Thread
类)[无限期阻塞等待:调用线程会等待线程运行结束才开始运行]{.pink}
public class ThreadJoin
{
public static void main(String[] args) throws InterruptedException
{
Thread t1 = new Thread(() ->
{
log.debug("{} 开始执行...", Thread.currentThread().getName());
MySleep.sleep(1);
log.debug("{} 结束执行...", Thread.currentThread().getName());
}, "t1");
// 线程开始执行
t1.start();
// 主线程(调用线程)等待 Thread-1 线程结束
t1.join();
// 主线程开始执行
log.debug("主线程执行...")
}
}有限期阻塞等待
[如果等待的线程提前结束,那么调用线程也会提前结束]{.red}
[如果调用线程已经等待了设定的时间,等待的线程依然没有结束,那么调用线程就会不再等待]{.red}
public class ThreadJoin
{
public static void main(String[] args) throws InterruptedException
{
Thread t1 = new Thread(() ->
{
try{
// 线程休眠 2 秒
TimeUnit.SECONDS.sleep(2);
}
catch (InterruptedException e){
e.printStackTrace();
}
}, "t1");
long start = System.currentTimeMillis();
// 线程开始启动
t1.start();
// 主线程等待线程结束
// 如果线程超过 4 秒还没结束, 就不再等待;如果少于 4 秒, 主线程也会提前结束等待
t1.join(4000);
log.debug("cost = {}", System.currentTimeMillis() - start);
}
}
特点:
[Join 方法的本质就是 Wait 方法]{.red}
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 方法进行等待
wait(0);
}
} else {
// 这里是带有时延的等待
}
}[调用线程 从 RUNNABLE 转换为 WAITING 状态]{.red}
[调用线程到的阻塞等待过程是可以被其余线程中断的]{.red}
[调用线程阻塞等待多个线程时,等待的时间取决执行时间最长的那个线程]{.pink}
- 调用线程等待 Thread-1、Thread-2 两个线程,Thread-1 执行 1秒,Thread-2 执行 2秒
- 调用线程阻塞式等待这两个线程,那么调用线程最终只会等待 2 秒,而不是 3 秒
- 多核处理的情况下线程基本都是并行,所以显然只需要等待 2 秒
public class ThreadJoin
{
private static int count = 0;
private static int number = 0;
// 节省代码篇幅, 不再重复写相同的代码
public static void main(String[] args) throws InterruptedException
{
Thread t1 = new Thread(() ->{
// 线程休眠 1 秒
}, "t1");
Thread t2 = new Thread(() ->{
// 线程休眠 2 秒
}, "t2");
// 记录开始时间
long start = System.currentTimeMillis();
// 启动两个线程
t1.start();
t2.start();
// 等待两个线程结束
t1.join(4000);
t2.join(4000);
// 打印主线程等待的时间
log.debug("count = {}, number = {}, cost = {}", count, number,
System.currentTimeMillis() - start);
}
}
Park & Unpark 方法
中断线程
Interrupt 方法
作用:[设置需要中断的线程的中断标志位 true,线程根据中断标志 自行决定 之后的行为]{.red}
使用:
核心方法:
thread.interrupt()
代码演示
public class ThreadInterrupt
{
public static void main(String[] args) throws InterruptedException
{
Thread t1 = new Thread(() ->
{
log.debug("{} 开始休眠...", Thread.currentThread().getName());
try{
// 休眠 5 秒
TimeUnit.SECONDS.sleep(5);
}
catch (InterruptedException e){
// 默认会抛出异常
e.printStackTrace();
}
}, "t1");
// 启动线程
t1.start();
// 主线程休眠 1 秒, 确保在子线程启动之后再进行打断
TimeUnit.SECONDS.sleep(1);
log.debug("主线程准备打断线程...");
// 打断线程的休眠
t1.interrupt();
// 查看线程被中断之后的状态
// 注意:sleep 被中断后中断标志位会被重新置为 false, 所以你看到的结果是 false 而不是true
log.debug("线程状态的打断标记 {}", t1.isInterrupted());
}
}
特点:
- 中断标志默认设置为 false
- [Interrupt 方法可以中断 Sleep、Join、Wait 方法造成的等待过程,默认将会抛出异常]{.red}
- [Interrupt 方法配合 IsInterrupted \ Interrupted 方法可以实现优雅地停止线程]{.red}
- [Sleep、Join、Wait 方法在被中断之后默认将中断标志重新设置为 false]{.pink}
IsInterrupted 方法
作用:[获取线程的中断标志位的状态]{.red}
细节:
[IsInterrupted 方法 默认 不会清除中断标志位,但是可以设置清除中断标志位]{.pink}
// 传入为真的布尔变量就可以清除中断标志位了
private native boolean isInterrupted(boolean ClearInterrupted);[IsInterrupted 方法获取的 调用该方法的线程对象 的中断标志位状态,而不是调用线程的]{.red}
使用
核心方法:
thread.isInterrupted()
(实例方法)代码演示:[Interrupt + IsInterrupted 实现优雅的停止线程]{.red}
public class ThreadInterrupt
{
public static void main(String[] args) throws InterruptedException
{
Thread t2 = new Thread(()->{
// 子线程持续运行, 每次都判断自己是否被中断
while (true){
// 利用中断标记决定线程是继续运行还是结束
// Thread.currentThread() 获取当前运行的线程对象
if (Thread.currentThread().isInterrupted()){
log.debug("线程结束...");
break;
}
}
}, "t2");
// 线程开始启动
t2.start();
// 主线程休眠 1 秒
TimeUnit.SECONDS.sleep(1);
log.debug("主线程准备打断线程...");
// 打断线程的休眠
t2.interrupt();
}
}
Interrupted 方法
作用:[获取线程的中断标志位状态,并且重新将中断标志位置为 false]{.red}
细节:
[返回的结果是被 重置之前 的结果,而不是重置之后的结果]{.pink}
[IsInterrupted 方法获取的是 调用线程 的状态,而不是其他线程对象的状态]{.red}
public static boolean interrupted() {
// 返回的是当前运行的线程的中断标志位, 内部调用的就是 isInterrupted 方法
return currentThread().isInterrupted(true);
}
使用:
核心方法:
Thread.isInterrupted()
(静态方法)代码演示:[区别两种获取中断标志位状态的方法]{.aqua}
public class ThreadInterrupt
{
public static void main(String[] args) throws InterruptedException
{
Thread thread = new Thread(() -> {
while (true) {
// 线程持续运行
}
});
// 启动线程
thread.start();
// 设置线程的中断标志位
thread.interrupt();
// 获取中断标志位
log.debug("{}", thread.isInterrupted());
// 这里获取的依然是主线程的中断标志位
// 因为 interrupted 是静态方法, 所以使用对象调用或者类调用是相同的
log.debug("{}", thread.interrupted());
// 这里获取的就是主线程的中断标志位, 而不是子线程的
log.debug("{}", Thread.interrupted());
// 再次获取中断标志位, 上面两个方法都没有清除子线程的中断标志位, 所以依然是 true
log.debug("{}", thread.isInterrupted());
}
}