你好!今天我们继续讲多线程。让我们检查 Thread 类及其一些方法的作用。以前我们研究类方法的时候,一般都是这么写的:<方法名> -> <方法的作用>。 这不适用于
Thread
的方法 :) 它们具有更复杂的逻辑,如果没有几个示例,您将无法理解。
Thread.start() 方法
让我们从重复自己开始。Thread
您可能还记得,您可以通过让您的类继承类并覆盖方法来创建线程run()
。但它当然不会自行启动。为此,我们调用对象的start()
方法。 让我们回忆一下上节课的例子:
public class MyFirstThread extends Thread {
@Override
public void run() {
System.out.println("Thread executed: " + getName());
}
}
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
MyFirstThread thread = new MyFirstThread();
thread.start();
}
}
}
注意:要启动一个线程,必须调用特殊的start()
方法而不是run()
方法!这是一个很容易犯的错误,尤其是当您刚开始学习多线程时。在我们的示例中,如果您调用该run()
方法 10 次而不是start()
,您将得到:
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
MyFirstThread thread = new MyFirstThread();
thread.run();
}
}
}
看我们程序的执行结果: 执行的线程:Thread-0 执行的线程:Thread-1 执行的线程:Thread-2 执行的线程:Thread-3 执行的线程:Thread-4 执行的线程:Thread-5 执行的线程:Thread-6执行的线程:Thread-7 执行的线程:Thread-8 执行的线程:Thread-9 查看输出的顺序:一切都按完美顺序进行。很奇怪吧?我们对此并不习惯,因为我们已经知道线程启动和执行的顺序是由操作系统内部的高级智能决定的:线程调度程序。也许我们只是运气好?当然,这与运气无关。您可以通过多次运行该程序来验证这一点。问题是调用run()
方法直接与多线程无关。在这种情况下,程序将在主线程上执行,即执行该main()
方法的线程。它只是在控制台上连续打印 10 行,仅此而已。10 个线程尚未启动。所以,以后要记住这一点,不断地检查自己。如果要run()
调用该方法,请调用start()
. 让我们更进一步。
Thread.sleep() 方法
要暂时暂停当前线程的执行,我们使用 方法sleep()
。 该sleep()
方法以毫秒数作为参数,它表示让线程休眠的时间量。
public class Main {
public static void main(String[] args) throws InterruptedException {
long start = System.currentTimeMillis();
Thread.sleep(3000);
System.out.println(" - How long did I sleep? \n - " + ((System.currentTimeMillis()-start)) / 1000 + " seconds");
}
}
控制台输出: - 我睡了多长时间?- 3 秒 注意:该sleep()
方法是静态的:它使当前线程休眠。也就是当前正在执行的那个。这是另一个重点:休眠线程可以被中断。在这种情况下,程序会抛出一个InterruptedException
. 我们将考虑下面的示例。顺便问一下,线程唤醒后会发生什么?它会从中断的地方继续执行吗?No. 线程唤醒后,即作为参数传递的时间已经Thread.sleep()
过去,它会转换为可运行状态。但是,这并不意味着线程调度程序会运行它。它很可能会优先选择其他一些非睡眠线程,并允许我们刚刚唤醒的线程稍后继续工作。一定要记住这一点:起床不代表马上继续工作!
Thread.join() 方法
该join()
方法暂停当前线程的执行,直到另一个线程完成。如果我们有 2 个线程,t1
并且t2
,我们写
t1.join()
然后直到完成它的工作t2
才会开始。t1
该join()
方法可用于保证线程的执行顺序。让我们join()
在以下示例中考虑该方法的工作原理:
public class ThreadExample extends Thread {
@Override
public void run() {
System.out.println("Thread started: " + getName());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread " + getName() + " is finished.");
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
ThreadExample t1 = new ThreadExample();
ThreadExample t2 = new ThreadExample();
t1.start();
/* The second thread (t2) will start running only after the first thread (t1)
is finished (or an exception is thrown) */
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
// The main thread will continue running only after t1 and t2 have finished
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("All threads have finished. The program is finished.");
}
}
我们创建了一个简单的ThreadExample
类。它的任务是显示线程已经启动的消息,然后休眠 5 秒,最后报告工作完成。小菜一碟。主要逻辑在Main
类中。看评论:我们用join()
方法成功管理了线程的执行顺序。如果您还记得我们是如何开始这个主题的,执行顺序是由线程调度程序处理的。它自行决定运行线程:每次都以不同的方式运行。这里我们使用的方法是保证t1
线程先启动先执行,然后t2
线程,只有在那之后程序的主线程才会继续。继续。在实际程序中,您经常会发现需要中断线程执行的情况。例如,我们的线程正在运行,但它正在等待某个事件或条件。如果它发生,则线程停止。如果有某种stop()
方法,这可能是有意义的。但这不是那么简单。曾几何时,Java 确实有一个Thread.stop()
方法,允许一个线程被中断。但后来从 Java 库中删除了它。你可以在 Oracle 文档中找到它并看到它被标记为已弃用. 为什么?因为它只是停止了线程,没有做任何其他事情。例如,线程可能正在处理数据并更改某些内容。然后在它工作的过程中,它突然被这个stop()
方法毫不客气地切断了。没有适当的关闭,也没有资源的释放,甚至没有错误处理——这些都没有。稍微夸张一点,这个stop()
方法简直就是摧毁了所有挡路的东西。这就像从插座上拔下电源线来关闭计算机一样。是的,你可以得到想要的结果。但是每个人都知道,几周后计算机不会感谢您以这种方式对待它。这就是为什么中断线程的逻辑在 Java 中发生了变化,现在使用了一种特殊的interrupt()
方法。
Thread.interrupt() 方法
interrupt()
如果在线程上调用该方法会发生什么?有两种可能性:
- 如果对象处于等待状态,例如,由于
join
或sleep
方法,则等待将被中断,程序将抛出一个InterruptedException
. - 如果线程处于运行状态,则将
interrupted
在对象上设置布尔标志。
Thread
类具有boolean isInterrupted()
方法的原因。让我们回到基础课程中的时钟示例。为了方便起见,我们稍微简化了它:
public class Clock extends Thread {
public static void main(String[] args) throws InterruptedException {
Clock clock = new Clock();
clock.start();
Thread.sleep(10000);
clock.interrupt();
}
public void run() {
Thread current = Thread.currentThread();
while (!current.isInterrupted())
{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("The thread was interrupted");
break;
}
System.out.println("Tick");
}
}
}
在这种情况下,时钟启动并开始每秒滴答作响。在第 10 秒,我们中断时钟的线程。 如您所知,如果我们尝试中断的线程处于其中一种等待状态,则结果为InterruptedException
. 这是一个已检查的异常,所以我们可以很容易地捕获它并执行我们的逻辑来完成程序。而这正是我们所做的。这是我们的结果: Tick Tick Tick Tcik Tick Tick Tick Tick Tick Tick 线程被中断了 我们对Thread
类最重要方法的介绍到此结束。祝你好运!