常见方法
start()与run()
主线程直接调用某个线程t1的run()方法,run方法也会执行,但是并不会启动新的线程,而是有主线程调用的run方法,必须使用start才能启动新线程,但是start只能调用一次。
sleep()与yield()
sleep:
- sleep可以使一个线程的状态由RUNNING变为TIMED_WAITING。
- sleep() 方法的过程中,线程不会释放对象锁
使用sleep可以防止CPU占用100%:
while(true) {
try {
Thread.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
- 可以使用wait或条件变量达到类似的效果,不同的是这两种都需要加锁,并且需要相应的唤醒操作。
- sleep适用于无锁同步的场景。
yield:
- 允许具有相同优先级的其他线程获得运行机会。
- 会放弃 CPU 资源,锁资源不会释放
join方法
当调用某个线程(t1)的join方法后,该线程(t1)抢占到CPU资源,就不再释放,直到线程执行完毕。join本质上是一个对象锁,是被synchronized修饰的。
join实现线程同步,因为会阻塞等待另一个线程的结束,才能继续向下运行
- 需要外部共享变量,不符合面向对象封装的思想
- 必须等待线程结束,不能配合线程池使用
interrupt
- public void interrupt(),打断当前线程:
- 当打断阻塞(sleep、wait、join 方法都会让线程进入阻塞状态)的线程是会抛出一个异常,其会清空打断状态,也即打断标记为false。如果在一个线程执行sleep之前就已经执行了interrupt方法,那么当他执行到sleep会立即抛出异常。
- 当打断一个正常的线程时,不会抛出异常,而且不会清空打断状态,也即打断标记为true。
- public boolean isInterrupted(),判断当前线程是否被打断,打断返回true,不清除打断标记。
- public static boolean interrupted(),判断当前线程是否被打断,打断返回true,清除打断标记,连续调用两次一定返回false。
关于interrupt有一个设计模式,即终止模式之两阶段终止模式(Two Phase Termination)
daemon
public final void setDaemon(boolean on)
:如果是 true ,将此线程标记为守护线程;在start之前调用该方法。
守护线程,只要其它非守护线程运行结束了,即使守护线程代码没有执行完,也会强制结束。守护进程是脱离于终端并且在后台运行的进程
常见的守护线程:
- 垃圾回收器线程就是一种守护线程
- Tomcat 中的 Acceptor 和 Poller 线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等待它们处理完当前请求
线程的状态
在操作系统中,有5种状态:新建、就绪、运行、阻塞、死亡。而在Java种则是由不同的定义,Thread.State中给了Java中线程的6个状态:
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
其中RUNNABLE就包括了操作系统种的就绪、运行、阻塞三种状态。
线程状态 | 导致状态发生条件 |
---|---|
NEW(新建) | 线程刚被创建,但是并未启动,还没调用 start 方法,只有线程对象,没有线程特征 |
Runnable(可运行) | 线程可以在Java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器,调用了t.start()方法:就绪(经典叫法) |
Blocked(阻塞) | 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成 Runnable 状态 |
Waiting(无限等待) | 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态,进入这个状态后不能自动唤醒,必须等待另一个线程调用 notify 或者 notifyAll 方法才能唤醒 |
Timed Waiting (限期等待) | 有几个方法有超时参数,调用将进入Timed Waiting状态,这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait |
Teminated(结束) | run方法正常退出而死亡,或者因为没有捕获的异常终止了 run 方法而死亡 |
package com.qcby.case1;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class Test5 {
public static void main(String[] args) throws InterruptedException {
// t1处于NEW
Thread t1 = new Thread("t1");
// t2一定能执行完,处于TERMINATED
Thread t2 = new Thread(() -> {
}, "t2");
// t3处于RUNNABLE
Thread t3 = new Thread(() -> {
while (true) {
}
}, "t3");
// t4和t5都尝试取获取同一个类锁,
// 那么先获取到的处于TIMED_WAITING
// 后获取到的处于BLOCKED
Thread t4 = new Thread(() -> {
synchronized (Test5.class) {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "t4");
Thread t5 = new Thread(() -> {
synchronized (Test5.class) {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "t5");
// t6进入Waiting状态,需要等待t4执行完毕,才被唤醒
Thread t6 = new Thread(() -> {
try {
t4.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "t6");
t2.start();
t3.start();
t4.start();
t5.start();
t6.start();
Thread.sleep(1000);
log.info("t1的状态是:{}", t1.getState());
log.info("t2的状态是:{}", t2.getState());
log.info("t3的状态是:{}", t3.getState());
log.info("t4的状态是:{}", t4.getState());
log.info("t5的状态是:{}", t5.getState());
log.info("t6的状态是:{}", t6.getState());
}
}