0. 环境:
电脑:Windows10
Android Studio: 2024.3.2
编程语言: Java
Gradle version:8.11.1
Compile Sdk Version:35
Java 版本:Java11
1. 先熟悉JVM虚拟机的线程
----------以下都是系统线程,由JVM管理,通常无需直接操作,仅需了解即可。----------
先创建一个Java module(创建方法可以看我这篇文章的3.1)
3.1 创建module
首先,创建一个module:菜单--file--new--new module...
注意左侧选择Java or Kotlin Library,右侧填写相应信息。点finish
然后执行以下代码:
java
public static void main(String[] args) {
// 虚拟机线程管理的接口
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
// 取得线程信息
ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
for (ThreadInfo threadInfo : threadInfos) {
String logInfo = String.format("[%s] + %s", threadInfo.getThreadId(), threadInfo.getThreadName());
// 打印线程Id和线程名称
System.out.println(logInfo);
}
}
运行后看结果:
java
[1] + main
[9] + Reference Handler
[10] + Finalizer
[11] + Signal Dispatcher
[12] + Attach Listener
[19] + Notification Thread
[20] + Common-Cleaner
|---------------------|----------------------------------|
| ---线程名称--- | ---作用--- |
| main | 主线程,程序入口 |
| Reference Handler | 处理引用对象 |
| Finalizer | 执行对象的finalize()
方法 |
| Signal Dispatcher | 处理操作系统信号 |
| Attach Listener | 支持动态附加(Attach)到JVM |
| Notification Thread | 处理JMX(Java管理扩展)通知 |
| Common-Cleaner | 替代Finalizer
的轻量级清理线程(Java 9+引入) |
(没有看到GC线程,是因为目前没有资源需要回收。当出现资源需要回收时,才会触发JVM的GC线程。此时才有GC线程来回收资源。也就是GC回收机制)
----------以上都是系统线程,由JVM管理,通常无需直接操作,仅需了解即可。----------
2. 运行线程的方式
三种方式:
|----------|-------------------------------|
| --名称-- | 备注 |
| Thread | 线程 |
| Runable | 任务,需要通过thread来运行该任务 |
| Callable | 有返回值的任务,需要通过futureTask来挂载 |
示例代码:
java
// 第一种方式:thread
private static class Thread1 extends Thread {
@Override
public void run() {
super.run();
System.out.println("--- run thread1 ---");
}
}
// 第二种方式:runnable
private static class Runnable2 implements Runnable {
@Override
public void run() {
System.out.println("--- run runnable2 ---");
}
}
// 第三种方式:callable
private static class Callable3 implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("--- run callable3 ---");
return "[result]: run success";
}
}
运行方式如下:
java
public static void main(String[] args) {
// 方法一的线程可以通过start()方法直接运行
Thread1 thread1 = new Thread1();
thread1.start(); // 需要通过start()来启动线程。run()函数不是启动线程,只是函数调用
// 方法二的任务,需要通过线程来执行
Runnable2 runnable2 = new Runnable2();
Thread thread = new Thread(runnable2);
thread.start();
// 方法三的有返回值的任务,需要挂载在FutureTask,再通过线程来执行
Callable3 callable3 = new Callable3();
// 将callable挂载在futureTask上
FutureTask<String> futureTask = new FutureTask<>(callable3);
// 通过线程来执行futureTask任务
Thread callableThread = new Thread(futureTask);
callableThread.start();
// 此时我们可以获取callable的返回值
try {
System.out.println("callable return: " + futureTask.get());
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
运行后,可以看到结果:
java
--- run thread1 ---
--- run runnable2 ---
--- run callable3 ---
callable return: [result]: run success
3. 停止线程的方式
1. stop()停止(不推荐!)
非常不推荐 使用stop()来停止线程,过时了。该函数属于暴力停止,有一定的危险性。例如来不及释放、会产生碎片等问题。甚至会被其他程序员鄙视(开玩笑的)
你就想吧,你在KTV里唱歌,唱到一半被人切歌了。你是不是想给他一拳
2. interrupt()中断
优雅的停止线程的方式,应该是让run()函数执行完之后,不再执行。
interrupt()是中断信号的函数
java
private static class InterruptThread extends Thread {
@Override
public void run() {
super.run();
String threadInfo = String.format("[%s] %s", Thread.currentThread().getId(), Thread.currentThread().getName());
String isRunning = String.format("%s is Running", threadInfo);
String isStopped = String.format("%s is Stopped", threadInfo);
while (!isInterrupted()) { // 注意这里,需要增加 中断信号的判断
System.out.println(isRunning);
}
System.out.println(isStopped);
// -------------以下为错误示范---------------
/**
while (true) { // 如果判断条件为true时,即使调用了thread.interrupt(). 该线程也无法停止
System.out.println(isRunning);
}
System.out.println(isStopped);
*/
}
}
执行代码方法如下:
java
InterruptThread thread = new InterruptThread();
thread.start(); // 开启线程
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
thread.interrupt(); // 发起中断信号
运行后,可以看到结果:
java
[21] Thread-0 is Running
[21] Thread-0 is Running
·····
[21] Thread-0 is Running
[21] Thread-0 is Running
[21] Thread-0 is Stopped
最后确实停止了。
中断后,没有重启的概念。只有线程启动、线程中断(也就是停止)。要想再次启动线程,需要再次使用线程启动。
注意:
如果在run()函数中使用了sleep(),会导致interrupt信号被清除。如果需要使用sleep()函数,需要在抛异常部分,再次发送一次interrupt(); 这样即可中断线程。
参考示例:
java
private static class Thread1 extends Thread {
@Override
public void run() {
super.run();
try {
System.out.println("--- run thread1 ---");
Thread.sleep(1000);
} catch (InterruptedException e) {
interrupt(); // 再次发送中断信号
e.printStackTrace();
}
}
}
扩展:
如果不是继承Thread,而是实现Runnable,需要怎么改呢?
请看代码:
java
private static class InterruptThread2 implements Runnable {
@Override
public void run() {
String threadInfo = String.format("[%s] %s", Thread.currentThread().getId(), Thread.currentThread().getName());
String isRunning = String.format("%s is Running", threadInfo);
String isStopped = String.format("%s is Stopped", threadInfo);
// 通过Thread.currentThread(),来获取线程
while (!Thread.currentThread().isInterrupted()) {
System.out.println(isRunning);
}
System.out.println(isStopped);
}
}
4. 补充一个join()函数
join()的意思是放弃当前线程的执行,并返回对应的线程。
如果不是用join()会导致线程之间随机执行,无法做到控制顺序。
应用场景:执行完线程1后,需要执行线程2,则可以对线程1执行join()函数,再对线程2执行start()函数。达到先执行线程1,再执行线程2,从而达到控制线程执行顺序的效果。
贴代码
java
private static class InterruptThread extends Thread {
public InterruptThread(String s) {
super(s);//传入线程名称,用于区分不同线程
}
@Override
public void run() {
super.run();
String threadInfo = String.format("[%s] %s", Thread.currentThread().getId(), Thread.currentThread().getName());
for (int i = 0; i < 100; i++) {
System.out.println(i + ";" + getName() + threadInfo);
}
}
}
//执行代码:
InterruptThread thread1 = new InterruptThread("A");
InterruptThread thread2 = new InterruptThread("B");
thread1.start();
// thread1.join();//注意这个join。可以做到先执行thread1
thread2.start();
执行后看结果:
java
0;B[22] B
1;B[22] B
2;B[22] B
3;B[22] B
4;B[22] B
5;B[22] B
6;B[22] B
0;A[21] A
7;B[22] B
1;A[21] A
8;B[22] B
9;B[22] B
10;B[22] B
11;B[22] B
12;B[22] B
13;B[22] B
14;B[22] B
15;B[22] B
2;A[21] A
16;B[22] B
3;A[21] A
17;B[22] B
4;A[21] A
18;B[22] B
···
可以看到线程A和线程B混在一起,无法做到先后执行。如果使用了thread1.join(); 再运行之后,就可以做到执行完线程A后,再开始执行线程B。
运行结果:
java
0;A[21] A
1;A[21] A
2;A[21] A
3;A[21] A
4;A[21] A
···
97;A[21] A
98;A[21] A
99;A[21] A
// 可以看到,完全执行完线程A之后,才会执行线程B
0;B[22] B
1;B[22] B
2;B[22] B
3;B[22] B
4;B[22] B
···
97;B[22] B
98;B[22] B
99;B[22] B
5. sleep()和wait()的区别
|--------------|--------------------|
| sleep | wait |
| 是休眠 | 是等待 |
| 可以无条件休眠 | 特殊原因需要等待 (例如:资源不足) |
| 休眠需要带参: 休眠时间 | 调用不用带参 |
| 休眠结束后,立刻有执行权 | 需要手动唤醒,才有执行权 |
|----------------|--------------------|
| sleep | wait |
| 是休眠 | 是等待 |
| 可以无条件休眠 | 特殊原因需要等待 (例如:资源不足) |
| 休眠需要带参: 休眠时间 | 调用不用带参 |
| 休眠结束后,立刻有执行权 | 需要手动唤醒,才有执行权 |
| 会清除interrupt信号 | |
执行权并不代表马上执行。什么时候执行 取决于操作系统的调度。
写在最后
至此,我们就学会了线程的启动和停止。顺便学会了顺序执行线程。