本文内容主要来自《疯狂Java讲义》
线程的生命周期
我们先来看下生命周期的图,该图来自《疯狂 Java 讲义》
- 新建:当一个线程被 new 出来后,他就处于新建状态
- 就绪:当一个线程调用 start()方法后,他就处于就绪状态
- 运行:当一个线程获得 CPU,就开始执行线程执行体即 run()方法,此时就进入了运行状态。
- 阻塞:当运行的线程遇到途中所示的条件时,就进入阻塞状态,此时线程无法继续运行,只能等待;等待时间结束或者达到某种条件后,就可以进入就绪状态,等待 CPU 继续执行剩下的任务
- 结束:正常执行完 run()或 start(),或者抛出了异常,或者线程中调用 stop(),都会使得线程直接结束
注意:
- 如果一个线程开始后,无论该线程是否已经结束,无法再对它进行调用 start(),否则会报 IllegalThreadStateException 异常
- 开启线程是调用 start(),如果直接调用 run()方法,这样无法开启一个线程,只会当作是执行一个实例中的一个方法,不是线程执行体,无法并发执行
线程控制
suspend()、stop()方法因为不安全,已被弃用,suspend()对应的 resume()也弃用了,详细原因可以查看:官方文档
1. 线程加入join
在自己的线程中加入别的线程,也就是调用别的线程的 join()方法,使得自己的线程阻塞,会等待加入的线程结束后才继续执行。
public class JoinThread extends Thread {
public JoinThread(String name){
super(name);
}
public void run(){
for (int i = 0; i < 10; i++) {
System.out.println(getName() + " " + i);
}
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
if(i == 5){
JoinThread jt = new JoinThread("被join的线程");
jt.start();
jt.join();
}
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
输出结果:
main 0
main 1
main 2
main 3
main 4
被join的线程 0
被join的线程 1
被join的线程 2
被join的线程 3
被join的线程 4
被join的线程 5
被join的线程 6
被join的线程 7
被join的线程 8
被join的线程 9
main 5
main 6
main 7
main 8
main 9
- join():等待被 join 的线程执行完成
- join(long millis):等待被 join 的线程的时间最长为 mills 毫秒。如果在 millis 毫秒内被 join 的线程还没有执行结束,就不再等待。
- join(long millis, int nanos):等待被 join 的线程的时间最长为 mills 毫秒加 nanos 毫微秒。
2. 后台线程setDaemon
setDaemon() 后台线程是一种在后台运行的,任务是为其他的线程提供服务的线程。JVM 的垃圾回收线程就是一种后台线程。
如果前台线程都结束,后台线程会自动结束。
public class DaemonThread extends Thread {
public void run(){
for (int i = 0; i < 10; i++) {
try {
sleep(500);
System.out.println(getName() + " " + i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
DaemonThread dt = new DaemonThread();
dt.setDaemon(true); //设置为后台进程
dt.start();
for (int i = 0; i < 10; i++) {
sleep(200);
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
输出结果:
main 0
main 1
Thread-0 0
main 2
main 3
Thread-0 1
main 4
main 5
main 6
Thread-0 2
main 7
main 8
Thread-0 3
main 9
- 前台线程创建的子线程默认是前台线程,后台线程创建的子线程默认是后台线程
- 设置后台线程 setDaemon()必须要在 start()方法前调用,否则报错
- setDaemon():用于设置线程为后台线程。
- isDaemon():用于判断指定线程是否是后台线程。
3. 线程睡眠sleep
sleep()使当前线程暂停一段时间,进入阻塞状态,时间结束后直接进入就绪态。
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
if(i == 5){
sleep(1000);
}
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
输出结果中,可以看到当输出到 4 时,会暂停 1s 后才继续输出。
- static void sleep(long millis):让当前正在执行的线程暂停 millis 毫秒,并进入阻塞状态,该方法受到系统计时器和线程调度器的精度与准确度的影响
- static void sleep(long millis, int nanos):让当前正在执行的线程暂停 millis 毫秒加 nanos 毫微秒,并进入阻塞状态,该方法受到系统计时器和线程调度器的精度与准确度的影响
4. 线程让步yield
yield()与 sleep()方法类似,会让当前线程暂停,但不会进入阻塞状态,而是直接进入就绪状态,重新等待系统调度。有可能调用 yield()方法后,线程调度器又将其调度出来执行。
public class YieldThread extends Thread{
public YieldThread(String name){
super(name);
}
public void run(){
for (int i = 0; i < 10; i++) {
System.out.println(getName() + " " + i);
if(i == 5){
Thread.yield();
}
}
}
public static void main(String[] args) {
YieldThread yieldThread = new YieldThread("先");
yieldThread.start();
YieldThread yieldThread1 = new YieldThread("后");
yieldThread1.start();
}
}
输出结果:
先 0
先 1
先 2
先 3
后 0
后 1
后 2
后 3
后 4
后 5
先 4
先 5
后 6
后 7
后 8
后 9
先 6
先 7
先 8
先 9
我们可以看到,执行顺序是随机的,但是当执行到 5 时,线程都会切换,就是因为 yield() 进行了暂停,使别的线程获得调度机会。
5. 线程优先级
每一个线程都有一定的优先级,其中有三个常量:
- MAX_PRIORITY:值为 10
- MIN_PRIORITY:值为 1
- NORM_PRIORITY:值为 5
也可以自己设置不同的优先级,范围是 1~10 之间的整数。
子线程的优先级都与创建的它的父线程的优先级相同,其中 main 方法默认是普通优先级为 5。
可通过 setPriority()和 getPriority()方法设置或获取当前线程的优先级。
public class PriorityThread extends Thread{
public PriorityThread(String name){
super(name);
}
public void run(){
for (int i = 0; i < 10; i++) {
System.out.println(getName() + " " + i);
if(i == 5){
Thread.yield();
}
}
}
public static void main(String[] args) {
Thread.currentThread().setPriority(6);
for (int i = 0; i < 20; i++) {
if(i == 5){
PriorityThread low = new PriorityThread("低级");
low.start();
System.out.println("低级创建之初的优先级:" + low.getPriority());
low.setPriority(Thread.MIN_PRIORITY);
}
if(i == 10){
PriorityThread high = new PriorityThread("高级");
high.start();
System.out.println("高级创建之初的优先级:" + high.getPriority());
high.setPriority(Thread.MAX_PRIORITY);
}
}
}
}
输出结果
低级创建之初的优先级:6
高级创建之初的优先级:6
高级 0
高级 1
高级 2
高级 3
高级 4
高级 5
高级 6
高级 7
高级 8
高级 9
低级 0
低级 1
低级 2
低级 3
低级 4
低级 5
低级 6
低级 7
低级 8
低级 9
我们可以看到,虽然低级线程创建的更早,但会被后来的更高级的线程抢占 CPU 执行。而且即使在高级线程中调用了 yield() 暂停回到了就绪态,调度器也还是调度了高优先级的线程先执行。
8. 线程中断
- interrupt():设置该线程的中断信号为 true
- isInterrupted():返回该线程的中断信号
- interrupted():返回该线程的中断信号,并将中断信号复位为 false
当一个线程调用 interrupt() 时,如果他是运行状态,那么没有任何影响,只会将该线程的中断信号设为 true;如果他是阻塞状态,因为阻塞状态下没有 CPU 给他设置中断信号,就会抛出异常 InterruptedException,被 catch 捕获,使得该线程提前终止阻塞状态,执行 catch 里的内容。
由此,我们就可以优雅的停止线程,我们自己控制停止的过程。
如果想让一个正在运行的线程停止,可以对中断信号进行判断,接收到中断信号后执行终止操作;
如果想让一个阻塞的线程停止,可以让他抛出异常提前终止阻塞状态,进入 catch 中执行终止操作。
示例如下:
public class InterruptThread extends Thread {
public void run() {
while (!isInterrupted()) { //判断中断信号
System.out.println("线程运行中...");
}
System.out.println("线程退出循环,停止...");
}
public static void main(String args[]) throws Exception {
InterruptThread thread = new InterruptThread();
System.out.println("线程开始...");
thread.start();
Thread.sleep(1); //让main睡眠
System.out.println("要求线程停止...");
thread.interrupt();
System.out.println("main函数结束...");
}
}
输出结果:
线程开始...
线程运行中...
线程运行中...
线程运行中...
线程运行中...
线程运行中...
线程运行中...
线程运行中...
线程运行中...
要求线程停止...
main函数结束...
线程退出循环,停止...
public class InterruptThread extends Thread {
public void run() {
System.out.println("线程运行中...");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// 接收到一个中断异常(InterruptedException),从而提早地终结被阻塞状态
System.out.println("阻塞状态线程被中断...");
}
System.out.println("线程退出循环,停止...");
}
public static void main(String args[]) throws Exception {
InterruptThread thread = new InterruptThread();
System.out.println("线程开始...");
thread.start();
Thread.sleep(1); //让main睡眠
System.out.println("要求线程停止...");
thread.interrupt();
System.out.println("main函数结束...");
}
}
输出结果:
线程开始...
线程运行中...
要求线程停止...
main函数结束...
阻塞状态线程被中断...
线程退出循环,停止...