如何正确停止线程
新建线程
新建线程的方式有多种
- 实现Runnable接口,重写run()方法
- 继承Thread类,重写run()方法
- 实现callable接口,重写call方法
- 使用线程池
- 使用定时器
- …
代码举例-Runnable接口
Runnable接口重写run方法
public class NewRunnable implements Runnable{
@Override
public void run() {
System.out.println("I am NewRunnable Thread");
}
public static void main(String[] args) {
NewRunnable newRunnable = new NewRunnable();
Thread thread = new Thread(newRunnable);
thread.start();
}
}
// 结果
/*
I am NewRunnable Thread
*/
上述代码中新建NewRunnable
类实现了Runnable
接口后重写了run方法,实现新建一个线程,可能这样体现不出来多线程,可以加个循环。
public class NewThread {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(200);
System.out.println("i am "+ i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
thread.start();
try {
Thread.sleep(500);
System.out.println("i am mainThread");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
运行结果
i am 0
i am 1
i am mainThread
i am 2
i am 3
i am 4
使用了匿名内部类重写了Runnable接口run方法,然后循环五次,每次睡眠200毫秒,主线程中睡眠500毫秒,所以当子线程循环两次后,在第三次打印中间主线程开始打印。
代码举例-Thread类
继承Thread类
public class NewThread extends Thread {
@Override
public void run() {
System.out.println("I am NewThread!");
}
public static void main(String[] args) {
NewThread newThread = new NewThread();
newThread.start();
}
}
/*
I am NewThread!
*/
新建线程总结
无论是实现Runnable接口还是继承Thread类,发现最终都是通过Thread类的start()方法开启的线程,而不是通过run()方法,看一下run方法和start方法的源代码
run()方法
public class Thread implements Runnable {
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
@Override
public void run() {
if (target != null) {
target.run();
}
}
}
发现Thread也实现了Runnable接口,并重写了run方法,如上所示,我们在使用实现Runnable接口,并把对象当作参数传给Thread类中 public Thread(Runnable target);构造器时,这时候run()方法执行的就是传入对象的run()方法,run()方法中也只有这三行代码,所以并没有实现多线程。
start()方法
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
可以看到首先run()方法if (threadStatus != 0)
判断线程状态,而后调用了start0方法,而start0方法private native void start0();
可以看到是一个native方法,并非是由java实现
搜索后得知
在Java中,Thread类是用于实现多线程编程的类。当我们创建一个新线程并启动它时,实际上是调用了Thread类中的start()方法,该方法会执行一个本地方法start0()。start0()方法会启动一个新的系统线程,并调用run()方法。run()方法是线程执行的代码,当run()方法执行完毕后,线程就会自动结束。
start0()方法是一个本地方法(native method),它是由JVM实现的。本地方法是指使用C/C++等低级语言编写的方法,它们通常由JVM加载并在本地系统上执行。start0()方法在底层实现了线程的创建和启动,其具体实现会依赖于不同的操作系统和JVM实现。在JVM中,start0()方法的实现是由native层面提供的,因此我们无法直接查看其源代码。
所以我们可以得知,在java中真正实现多线程的是Thread类中的start()方法。
补充 callable接口
public class NewCallable implements Callable {
@Override
public String call() throws Exception {
String s = "I am newCallable!";
System.out.println(Thread.currentThread().getName());
return s;
}
public static void main(String[] args) {
NewCallable newCallable = new NewCallable();
FutureTask futureTask = new FutureTask<>(newCallable);
Thread thread = new Thread(futureTask);
thread.start();
try {
Object o = futureTask.get();
System.out.println(o);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("i am mainThread");
System.out.println(Thread.currentThread().getName());
}
}
/*
Thread-0
I am newCallable!
i am mainThread
main
*/
可以看到实现callable接口也是通过Thread类创建的新线程,不同的是使用了FutureTask
类接收了返回值
简要说一下futureTask
可以看到该类间接继承了Runnable接口,所以依然可以理解成callable接口就是实现Runnbale
接口,重写了run方法的方式实现了多线程,其他方式不在过多阐释。
停止线程
如何停止线程?
- stop()?
- destroy()?
- suspend()?
- interrupt()?
查看 API,我们会看到 java.lang.Thread
类型提供了一系列的方法如 start()、stop()、resume()、suspend()、destroy()等方法来管理线程。但是除了 start() 之外,其它几个方法都被声名为已过时(deprecated)
详细原因可看jdk
文档的解释
https://docs.oracle.com/javase/1.5.0/docs/guide/misc/threadPrimitiveDeprecation.html
正确停止线程的方式是使用interrupt()方法
Interrupt()方法
在 Java 中,Thread.interrupt()
方法用于中断线程,其作用是设置线程的中断标志位为 true
。被中断的线程可以通过检查自身的中断标志位来判断是否被中断,然后执行适当的操作。
如果线程被阻塞在某些操作上(如等待 I/O 操作、sleep()
等),调用 interrupt()
方法会中断该线程的阻塞状态,抛出 InterruptedException
异常,并清除中断标志位。如果线程没有被阻塞,则调用 interrupt()
方法只是设置线程的中断标志位为 true
,线程仍然可以继续运行。
举个例子
public class MyThread extends Thread {
public void run() {
try {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("MyThread is running...");
Thread.sleep(1000);
}
} catch (InterruptedException e) {
System.out.println("MyThread is interrupted!");
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
myThread.interrupt();
}
}
// 结果
/*
MyThread is running...
MyThread is running...
MyThread is running...
MyThread is running...
MyThread is running...
MyThread is interrupted!
*/
新建了一个线程,在主线程中,我们睡眠5S后将新建线程中断,这是新线程捕获中断,并打印了MyThread is interrupted!
,
中断标志位
上方代码中,如果我们把while (!Thread.currentThread().isInterrupted())
换成 while (true)
发现运行结果是一样的,为什么呢?这就牵扯到中断标志位了,如上方所言 **如果线程被阻塞在某些操作上(如等待 I/O 操作、sleep()
等),调用 interrupt()
方法会中断该线程的阻塞状态,抛出 InterruptedException
异常,并清除中断标志位。**上方代码中5s中断线程时,线程在sleep状态,这时候程序抛出 InterruptedException
异常,并清除中断标志位。在抛出异常时线程已经停止了。
public class MyThread extends Thread {
public void run() {
int i =0;
while (!Thread.currentThread().isInterrupted()){
System.out.println("正在打印:"+ ++i);
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
int sum = 0;
for (int i = 0; i < 1000000; i++) {
sum += i;
}
myThread.interrupt();
}
}
// 结果
/*
正在打印:0
正在打印:1
正在打印:2
正在打印:3
........
正在打印:104
*/
可以看到我们通过判断程序的中断信号来执行代码,当程序被中断时,跳出while循环,新线程运行结束
循环中使用注意事项
如果我们将try catch 放在循环中发现程序响应中断后并未停止,这是因为sleep()清除了中断标志位,所以不能将try catch语句放在循环中,而应该放在循环外。
public class MyThread extends Thread {
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
System.out.println("MyThread is running...");
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
myThread.interrupt();
}
}
结果
/*
MyThread is running...
MyThread is running...
MyThread is running...
MyThread is running...
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at threadToStop.MyThread.run(MyThread.java:8)
MyThread is running...
MyThread is running...
*/