你好!今天我们继续聊多线程。让我们看一下 Thread 类以及它的几个方法是如何工作的。以前,当我们研究类方法时,我们通常会简单地这样写:“方法的名称”->“它的作用”。
这不适用于 Thread 方法:) 它们的逻辑比较复杂,没有几个例子是不可能理解的。
它将作为极好的附加材料!最后,在概述了这些方法之后,它准确地告诉了我们接下来将在课程中学习的内容:) 祝你好运!
Thread.start() 方法
让我们从重复开始。Thread
您可能还记得,您可以通过从类继承您的类并重写其中的方法来创建线程run()
。但是,当然,它不会自行开始。为此,我们调用对象上的方法start()
。 让我们记住上一讲的例子:
public class MyFirstThread extends Thread {
@Override
public void run() {
System.out.println("Выполнен поток " + 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()
! 这是一个很容易犯的错误,尤其是在第一次学习多线程时。如果在我们的示例中,您调用对象上的方法 10 次run()
而不是start()
,结果将如下所示:
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
MyFirstThread thread = new MyFirstThread();
thread.run();
}
}
}
线程 0 线程执行 线程 1 线程执行 线程 2 线程执行 线程 3 线程执行 线程 4 线程执行 线程 5 线程执行 线程 6 线程执行 线程 7 线程执行 线程 8 线程执行 线程 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(" - Сколько я проспал? \n - " + ((System.currentTimeMillis()-start)) / 1000 + " секунды");
}
}
控制台输出: - 我睡了多长时间?- 3 秒 请注意:该方法sleep()
是静态的:它将当前线程置于睡眠状态。也就是说,目前正在工作的那个。另一个重要的细微差别:睡眠状态下的流程可以被中断。这样的话,程序就会出现异常InterruptedException
。我们将看下面的一个例子。顺便问一下,线程“醒来”后会发生什么?它会立即从中断处继续执行吗?不。线程唤醒后(当作为参数传递的时间到期时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("Начало работы потока " + getName());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Поток " + getName() + " завершил работу.");
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
ThreadExample t1 = new ThreadExample();
ThreadExample t2 = new ThreadExample();
t1.start();
/*Второй поток t2 начнет выполнение только после того, How будет завершен
(or бросит исключение) первый поток - t1*/
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
//Главный поток продолжит работу только после того, How t1 и t2 завершат работу
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Все потоки закончor работу, программа завершена");
}
}
我们创建了一个简单的类ThreadExample
。它的任务是在屏幕上显示一条关于工作开始的消息,然后休眠 5 秒钟,最后通知工作完成。没什么复杂的。主要逻辑包含在类中Main
。看评论:利用该方法,join()
我们成功控制了线程的执行顺序。如果您还记得主题的开头,线程调度程序就是这样做的。他自行决定推出它们:每次都不同。在这里,使用该方法,我们确保首先启动并执行线程t1
,然后是线程t2
,只有在它们之后才是程序执行的主线程。前进。在实际程序中,经常会遇到需要中断某个线程执行的情况。例如,我们的线程正在运行,但它正在等待某个事件或条件得到满足。如果发生这种情况,它就会停止。如果有类似的方法,这可能是合乎逻辑的stop()
。然而,一切并没有那么简单。曾几何时,Thread.stop()
Java 中确实存在一种方法,它允许您中断线程的工作。但后来它被从 Java 库中删除了。您可以在 Oracle 文档中查找它,并看到它被标记为deprecated。为什么?因为它只是停止了流动,没有任何额外的工作。例如,线程可以处理数据并更改其中的某些内容。stop()
然后他在工作中突然被打倒了——就是这样。没有正确的关闭,没有释放资源,甚至没有错误处理——这一切都没有发生。夸张地说,这个方法stop()
简直摧毁了它所经过的一切。其操作可以与某人从插座上拔下插头来关闭计算机进行比较。是的,您可以达到预期的结果。但每个人都明白,几周后计算机将不会为此说“谢谢”。为此,Java中中断线程的逻辑发生了变化,现在使用了一种特殊的方法—— interrupt()
.
Thread.interrupt() 方法
如果调用线程上的方法interrupt()
会发生什么?有 2 个选项:
- 如果该对象当时处于等待状态,例如
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("Работа потока была прервана");
break;
}
System.out.println("Tik");
}
}
}
在我们的例子中,时钟每秒开始计时。在第 10 秒我们中断时钟流。如您所知,如果我们尝试中断的线程处于等待状态之一,则会导致InterruptedException
. 这种类型的异常是受检查的异常,因此可以很容易地捕获它并执行我们的程序终止逻辑。这就是我们所做的。 Here is our result: Tik Tik Tik Tik Tik Tik Tik Tik Tik The thread's work was interrupted. This concludes our introduction to the main methods of the class Thread
. 为了巩固您的知识,您可以观看有关多线程的视频讲座:
GO TO FULL VERSION