你好!首先,祝贺您:您已经接触到 Java 中的多线程主题!这是一项了不起的成就——您已经取得了长足的进步。但是请做好准备:这是本课程中最困难的主题之一。并不是说我们在这里使用了复杂的类或许多方法:事实上,我们将使用不到 20 个。更重要的是你需要稍微改变一下你的想法。以前,您的程序是按顺序执行的。代码行有的来了,方法有的来了,基本就清楚了。首先,我们计算一些东西,然后在控制台上显示结果,然后程序结束。要理解多线程,最好从并行性的角度来思考。让我们从非常简单的事情开始:) 想象一下您的家人正从一所房子搬到另一所房子。收集所有书籍将是此举的重要部分。你积累了很多书,你需要把它们放在盒子里。目前,您是唯一可用的。妈妈在做饭,弟弟在收拾衣服,妹妹去商店了。独自一人,您可以以某种方式进行管理。迟早,你会自己完成任务,但这会花费很多时间。但是,您姐姐将在 20 分钟后从商店回来,她没有其他事情可做。所以她可以加入你。任务没有改变:把书放进盒子里。但它的执行速度是原来的两倍。为什么?因为工作是并行进行的。两个不同的“线程”(你和你的妹妹)同时执行相同的任务。如果什么都没有改变,那么和自己什么都自己做的情况相比,时间上会有很大的差异。如果哥哥快点完成工作,他可以帮助你,事情会进行得更快。
多线程解决的问题
发明多线程实际上是为了实现两个重要目标:-
同时做几件事。
在上面的示例中,不同的线程(家庭成员)并行执行多个操作:他们洗碗、去商店和收拾东西。
我们可以提供一个与编程更密切相关的例子。假设您有一个带有用户界面的程序。当您在程序中单击“继续”时,应该会进行一些计算,用户应该会看到以下屏幕。如果按顺序执行这些操作,则程序将在用户单击“继续”按钮后挂起。用户将看到带有“继续”按钮屏幕的屏幕,直到程序执行所有内部计算并到达刷新用户界面的部分。
好吧,我想我们会等几分钟!
或者我们可以重新编写我们的程序,或者像程序员所说的那样,将其“并行化”。让我们在一个线程上执行计算并在另一个线程上绘制用户界面。大多数计算机都有足够的资源来执行此操作。如果我们采用这条路线,那么程序就不会冻结,用户将在屏幕之间平滑移动,而不必担心内部发生的事情。一个不干扰另一个:)
-
更快地执行计算。
这里的一切都简单得多。如果我们的处理器有多个内核,而今天的大多数处理器都有,那么多个内核就可以并行处理我们的任务列表。显然,如果我们需要执行 1000 个任务并且每个任务需要一秒钟,那么一个核心可以在 1000 秒内完成列表,两个核心可以在 500 秒内完成,三个核心可以在 333 秒多一点,等等。
public class MyFirstThread extends Thread {
@Override
public void run() {
System.out.println("I'm Thread! My name is " + getName());
}
}
要创建和运行线程,我们需要创建一个类,让它继承java.lang。Thread类,并覆盖其run()方法。最后一个要求非常重要。正是在run()方法中,我们定义了线程执行的逻辑。现在,如果我们创建并运行MyFirstThread的一个实例,run()方法将显示带有名称的一行:getName ()方法显示线程的“系统”名称,该名称是自动分配的。但我们为什么要试探性地说话呢?让我们创建一个并找出答案!
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
MyFirstThread thread = new MyFirstThread();
thread.start();
}
}
}
控制台输出: 我是线程!我的名字是 Thread-2 我是 Thread!我的名字是 Thread-1 我是 Thread!我的名字是 Thread-0 我是 Thread!我的名字是 Thread-3 我是 Thread!我的名字是 Thread-6 我是 Thread!我的名字是 Thread-7 我是 Thread!我的名字是 Thread-4 我是 Thread!我的名字是 Thread-5 我是 Thread!我的名字是 Thread-9 我是 Thread!我的名字是 Thread-8 让我们创建 10 个线程(继承Thread的MyFirstThread对象)并通过在每个对象上调用start()方法来启动它们。调用start()方法后,会执行 run()方法中的逻辑。注意:线程名称没有顺序。奇怪的是它们不是按顺序排列的:、Thread-1、Thread-2等等?碰巧的是,这是一个不适合“顺序”思维的时代的例子。问题是我们只提供了创建和运行 10 个线程的命令。线程调度器,一种特殊的操作系统机制,决定了它们的执行顺序。它的精确设计和决策策略是我们现在不会深入讨论的深入讨论的主题。要记住的主要事情是程序员无法控制线程的执行顺序。要了解情况的严重性,请尝试多次运行上面示例中的 main() 方法。第二次运行时的控制台输出: 我是线程!我的名字是 Thread-0 我是 Thread!我的名字是 Thread-4 我是 Thread!我的名字是 Thread-3 我是 Thread!我的名字是 Thread-2 我是 Thread!我的名字是 Thread-1 我是 Thread!我的名字是 Thread-5 我是 Thread!我的名字是 Thread-6 我是 Thread!我的名字是 Thread-8 我是 Thread!我的名字是 Thread-9 我是 Thread!我的名字是 Thread-7 第三次运行的控制台输出: 我是 Thread!我的名字是 Thread-0 我是 Thread!我的名字是 Thread-3 我是 Thread!我的名字是 Thread-1 我是 Thread!我的名字是 Thread-2 我是 Thread!我的名字是 Thread-6 我是 Thread!我的名字是 Thread-4 我是 Thread!我的名字是 Thread-9 我是 Thread!我的名字是 Thread-5 我是 Thread!我的名字是 Thread-7 我是 Thread!我的名字是 Thread-8
多线程产生的问题
在我们的书籍示例中,您看到多线程解决了非常重要的任务并且可以使我们的程序更快。通常快很多倍。但是多线程被认为是一个困难的话题。事实上,如果使用不当,它会产生问题而不是解决问题。当我说“制造问题”时,我并不是指某种抽象意义上的问题。多线程可以产生两个特定的问题:死锁和竞争条件。死锁是多个线程都在等待彼此占有的资源,没有一个可以继续运行的情况。我们将在后续课程中详细讨论它。下面的示例现在就足够了: 假设 Thread-1 与某个 Object-1 交互,而 Thread-2 与 Object-2 交互。此外,该程序是这样编写的:- Thread-1 停止与 Object-1 交互并切换到 Object-2,只要 Thread-2 停止与 Object-2 交互并切换到 Object-1。
- Thread-2 停止与 Object-2 交互并切换到 Object-1,只要 Thread-1 停止与 Object-1 交互并切换到 Object-2。
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();
}
}
}
现在想象一下,该程序负责运行一个烹饪食物的机器人! Thread-0 从冰箱中取出鸡蛋。Thread-1 打开炉子。Thread-2 拿了一个平底锅放在炉子上。Thread-3 点燃炉子。Thread-4 将油倒入锅中。Thread-5 打碎鸡蛋并将它们倒入锅中。Thread-6 将蛋壳扔进了垃圾桶。Thread-7 从燃烧器中取出煮熟的鸡蛋。Thread-8 将煮熟的鸡蛋放在盘子里。Thread-9 洗碗。 看我们程序的执行结果: 线程执行:Thread-0 线程执行:Thread-2 线程执行 Thread-1 线程执行:Thread-4 线程执行:Thread-9 线程执行:Thread-5 线程执行:Thread-8 Thread执行:Thread-7 执行的线程:Thread-3 这是喜剧套路吗?:) 这一切都是因为我们程序的工作取决于线程的执行顺序。只要稍微违反规定的顺序,我们的厨房就会变成地狱,一个疯狂的机器人会摧毁周围的一切。这也是多线程编程中的通病。你会不止一次听到它。在结束本课时,我想推荐一本关于多线程的书。 “Java Concurrency in Practice”写于 2006 年,但并没有失去它的相关性。它致力于多线程 Java 编程——从基础知识到最常见的错误和反模式。如果有一天您决定成为多线程专家,那么这本书是必读的。 下节课见!:)
GO TO FULL VERSION