目录
[一、Java中的 Thread 类 与 操作系统线程 的关系](#一、Java中的 Thread 类 与 操作系统线程 的关系)
[2.1 第一个多线程程序:继承自 Thread 类的子类](#2.1 第一个多线程程序:继承自 Thread 类的子类)
[2.2 实现 Runnable 接口并重写run方法](#2.2 实现 Runnable 接口并重写run方法)
[2.3 利用匿名内部类创建线程](#2.3 利用匿名内部类创建线程)
[2.4 匿名内部类解耦合创建线程](#2.4 匿名内部类解耦合创建线程)
[2.5 通过 Lambda 表达式创建线程](#2.5 通过 Lambda 表达式创建线程)
[三、Thread 类的常见方法](#三、Thread 类的常见方法)
[3.1 Thread 类常见构造方法](#3.1 Thread 类常见构造方法)
[3.2 Thread 类常见属性](#3.2 Thread 类常见属性)
[3.2.1 isDaemon 方法](#3.2.1 isDaemon 方法)
[3.2.2 isAlive 方法](#3.2.2 isAlive 方法)
[3.2.3 终止线程的方法](#3.2.3 终止线程的方法)
一、Java中的 Thread 类 与 操作系统线程 的关系
线程是操作系统中的概念。操作系统内核实现了线程这种机制,并对用户层提供了一些 API (Application Programming Interface)供用户使用。
但是,操作系统提供的原生线程API是C语言的,因此,Java对操作系统提供的API进行了封装并将其导入标准库(java.lang.)中供用户使用,这就是Thread类。
二、多线程程序
如何启动线程?
使用 start () 方法在操作系统层面创建线程并开始执行。
注意,创建 Thread 类对象并不意味着类对象所对应的在操作系统中的线程也被创建。
2.1 第一个多线程程序:继承自 Thread 类的子类
在Thread类中,run方法是可以被重写的,以供线程按照程序员自己规定的逻辑运行。
java
// 继承Thread类的子类,并重写run方法
class MyThread extends Thread {
@Override
public void run() {
while (true) {
System.out.println("This is MyThread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class Demo01_subclass {
public static void main(String[] args) throws InterruptedException {
Thread t = new MyThread();
t.start(); // 创建线程
// 主线程
while (true) {
System.out.println("This is main");
Thread.sleep(1000);
}
}
}
在上面的代码中,我们通过创建继承Thread类的子类MyThread来创建一个线程并运行,与此同时,main方法中的主线程也在运行,这就形成了一个"并发"执行的多线程程序。
这里使用sleep语句时的异常处理使用 try...catch语句而不是用throws语句的原因是父类Thread的run方法并没有使用throws,因此只能使用 try...catch语句来处理异常。

执行代码之后,发现MyThread类和main两个内部的循环都在进行中,也就是说,主线程main和线程t是"并发"执行的。

可以看到,控制台输出的看似是main线程先执行然后再执行t线程,实际上操作系统对线程的调度采用"抢占式执行",其顺序是随机的,而这种随机调度是无法被程序员所干预的。
我们可以通过第三方工具(java进程)来观察进程的详细情况:

通过这个程序我们可以连接正在IDE运行的程序中进程的详细情况




这里看到t线程被自动命名为 "Thread-0" 了,实际上Thread类是可以自定义线程名称的,以便程序员辨认自己写的线程。当程序员没有提供自定义的线程名称时,JVM就会自动给线程按照顺序命名。
2.2 实现 Runnable 接口并重写run方法
创建线程的写法不止有通过子类这一种方法,还可以通过实现Runnable接口来创建线程。
java
// 实现runnable接口,解耦合
class MyRunnable implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("This is Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class Demo02_runnable {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = new MyRunnable();
Thread t = new Thread(runnable);
t.start(); // 创建线程
// 主线程
while (true) {
System.out.printf("This is main");
Thread.sleep(1000);
}
}
}
我们实现的Runnable类中重写了run方法,将线程所要执行的任务放在run方法中,而Thread类则只负责创建线程,当将来线程要执行的任务发生改变时直接修改runnable中的ren方法即可。
这种方法是是一种 解耦合 方法。
在代码中,我们一般希望 高内聚,低耦合,这样代码的可维护性就会比较高(较方便修改)。
(高内聚:在一个模块之内,把有关联的事物放在一块,比如功能相类似的函数等;低耦合:每个模块之间的影响和依赖尽可能的小)
2.3 利用匿名内部类创建线程
我们这次在 main 方法中创建一个匿名内部类,并在其中重写 run 方法:
java
public class Demo03_anoInnerClass {
public static void main(String[] args) throws InterruptedException {
// 创建对象的时候使用了Thread的匿名(一次性)子类,并重写run方法
Thread t = new Thread() {
@Override
public void run() {
while (true) {
System.out.println("This is Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
};
t.start(); // 创建线程
// 主线程
while (true) {
System.out.println("This is main");
Thread.sleep(1000);
}
}
}
这样就可以少定义一些类。
2.4 匿名内部类解耦合创建线程
考虑到降低耦合,我们可以做出一些调整:实现一个 Runnable 的匿名内部类并创建实例,然后再将 runnable 作为参数传给 Thread 创建线程对象,以实现解耦合。
java
public class Demo04_anoInnerClassRunnable {
public static void main(String[] args) throws InterruptedException {
// 在Runnable这里创建匿名内部类,将任务和线程执行分离
Runnable runnable = new Runnable() {
@Override
public void run() {
while (true) {
System.out.println("This is Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
};
Thread t = new Thread(runnable);
t.start(); // 创建线程
// 主线程
while (true) {
System.out.println("This is main");
Thread.sleep(1000);
}
}
}
2.5 通过 Lambda 表达式创建线程
lambda表达式本质是一个"回调函数",通过实现函数式接口创建子类和对应的实例(编译器自动重写了方法),这样不仅简洁、清晰,还可以利用编译器加快效率。
java
public class Demo05_lambda {
public static void main(String[] args) throws InterruptedException {
// 使用lambda表达式
Thread t = new Thread(() -> {
while (true) {
System.out.println("This is Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start(); // 创建线程
// 主线程
while (true) {
System.out.println("This is main");
Thread.sleep(1000);
}
}
}
三、Thread 类的常见方法
在Java中,Thread 类是 JVM 专门用来管理线程的一个类。
Thread 类对象是用来描述一个线程执行流的,每一个线程都有唯一的 Thread 类对象与之对应,而 JVM 会将这些 Thread 类对象组织起来用于线程调度和线程管理。
3.1 Thread 类常见构造方法
| 构造方法 | 说明 |
|---|---|
| Thread () | 创建线程对象,需要重写 run 方法 |
| Thread (Runnable runnable) | 使用 Runnable 对象创建线程对象,不需要重写 run 方法 |
| Thread (String name) | 创建线程对象并命名 |
| Thread (Runnable runnable, String name) | 使用 Runnable 对象创建线程对象并命名 |
| 【了解】Thread (ThreadGroup group, Runnable runnable) | 用于对线程进行分组管理 |
- Thread t1 = new Thread();
- Thread t2 = new Thread(new MyRunnable());
- Thread t3 = new Thread("MyThread");
- Thread t4 = new Thread(new MyRunnable(), "MyThread");
3.2 Thread 类常见属性
| 属性 | 说明 | 获取方法 |
|---|---|---|
| ID | ID是线程的唯一标识,不可重复 | getId () |
| 名称 | 名称方便程序员进行调试 | getName () |
| 状态 | 状态表示线程当前所处的情况 | getState () |
| 优先级 | 优先级高的线程理论上更易被调度执行 | getPriority () |
| 是否为后台线程 | JVM 在一个进程的所有非后台线程结束之后才停止执行 | isDaemon () |
| 是否存活 | 当前线程是否执行结束 | isAlive () |
| 是否被中断 | 终止线程 | isInterrupted () |
3.2.1 isDaemon 方法
后台线程也可以理解为"守护线程",这些"守护线程"只会默默地在非后台线程运行的时候陪伴:
- 当非后台线程全部结束,他们也随之结束;
- 当非后台线程没全部结束时,"守护线程"结不结束都不会影响非后台线程。
非后台线程其实就是我们所创建的线程(包括主线程),而后台线程则是 JVM 在执行进程时的一些自带线程,当进程结束(非后台线程全部结束),后台进程也就结束了。
当我们想将自己创建的线程改成后台线程,可以使用 setDaemon () 方法。
java
public class Demo07_daemonThread {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
while (true) {
System.out.println("thread t");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
// 使用 setDaemon 方法可以将我们创建的线程修改为后台线程
// 但是需要在 start 方法之前使用
t.setDaemon(true);
t.start();
// 主线程
for (int i = 0; i < 3; i++) {
System.out.println("main");
Thread.sleep(1000);
}
// 线程 t 已被修改为后台线程,当主线程结束,线程 t 也随之结束
System.out.println("main-结束");
}
}
3.2.2 isAlive 方法
虽然 java 中,一个系统线程和 Thread 类对象存在一对一的对应关系,但是 Thread 类对象的生命周期和系统中线程的生命周期是不同的。
java
public class Demo08_isAliveFunc {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
for (int i = 0; i < 3; i++) {
System.out.println("thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
System.out.println(t.isAlive());
// 当还未调用 start 方法时,线程 t 还没有被创建出来(但是 t 对象存在),输出的结果为 false
t.start();
// 当调用 start 方法之后才真正创建线程
while (true) {
System.out.println(t.isAlive());
Thread.sleep(1000);
}
// 3秒过后线程 t 已被销毁,但是 t 对象还在(能够调用 isAlive 方法)
}
}
3.2.3 终止线程的方法
- 我们可以自己定义一个布尔类型的变量作为线程是否中断的标志:
java
class Test {
public int value = 0;
}
public class Demo10_isFinishedFunc {
private static boolean isFinished = false;
public static void main(String[] args) throws InterruptedException {
// 若 isFinished 定义在此处,会编译报错
// 因为 lambda 是有"变量捕获"的------即使用自身所在类之外的变量
// 此处的 isFinished 是局部变量,由于 lambda 是回调函数,在线程创建之后才执行
// 等到 lambda 执行,此时主线程早已执行完毕,isFinished 已被销毁
//boolean isFinished = false;
// 将 isFinished 改成成员变量,就不会触发"变量捕获"语法
Test test = new Test();
Thread t = new Thread(() -> {
while (!isFinished) {
System.out.println("thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("thread-结束");
});
t.start();
Thread.sleep(3000);
isFinished = true;
}
}
- 我们可以使用 interrupted 方法,Thread 类中含有一个布尔类型的变量充当线程是否中断的标志。
| 方法 | 说明 |
|---|---|
| public void interrupt () | 主动中断 Thread 类对象关联的线程,若线程处于阻塞状态,就以异常的方式通知,否则设置标志位 |
| public static boolean interrupted () | 判断当前线程的中断标志位,调用后清除标志位 |
| public boolean isInterrupted () | 判断对象关联的线程的标志位,调用后不清除标志位 |
需要注意 interrupted () 和 isInterrupted () 的区别:
| 特性 | interrupted() | isInterrupted() |
| 方法类型 | 静态方法 | 实例方法 |
| 检测对象 | 当前线程(类名调用) | 调用方法的线程对象 (对象调用) |
| 清除中断状态 | 是 | 否 |
| 典型场景 | 当前线程中断后需清除状态 | 监控其他线程或保留中断标志 |
|---|
- 当我们使用 interrupted () :
若线程因 wait/join/sleep 方法处于阻塞状态时,抛出 InterruptedException 异常通知并清除中断标志(当抛出异常后线程需要执行 catch 语句:可以抛出异常包装类异常终止线程;也可以 break 跳出循环正常终止线程)
- 当我们使用 isInterrupted () :
通过 Thread.currentThread().isInterrupted() 来判断线程的中断标志且不清除中断标志。(这种方式通知更及时)
java
public class Demo11_isInterruptedFunc {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
//while (t.isInterrupted()) -> 此处 lambda 中使用 t 的时机是在 Thread 创建 t 对象之前
// 使用 currentThread 方法获取到当前线程的引用 t ,其作用相当于 this 关键字
while (!Thread.currentThread().isInterrupted()) {
System.out.println("thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//throw new RuntimeException(e);
// 1. 当主线程调用 interrupt 方法强制终止 t 线程,此时 t 线程正在 sleep,sleep 被中断就抛出异常
// 然后执行"throw new RuntimeException(e)"抛出包装异常,由于没有外层异常处理器(run方法没有try...catch处理异常语句),线程 t 异常终止
// 2. (空语句)
// 当什么都不写,t 线程不会被终止:当调用 interrupt 方法时 sleep 被提前唤醒并抛出异常,JVM 此时将终止标志改回 false
break; // 3. 若添加 break 语句,当 sleep 在执行过程中被打断并抛出异常后,异常被 catch 语句捕获,跳出循环,不再检查终止标志(此时终止标志已被重置为 false)
// 程序将正常终止
}
}
System.out.println("thread-结束");
});
t.start();
Thread.sleep(3000);
System.out.println("main线程尝试终止t线程");
t.interrupt();
}
}
我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=f5hn3bezp2r
完