Java 中多线程并发执行的基本逻辑
2026/7/8 14:26:55 网站建设 项目流程
// Runnable 方式实现多线程 class MyRunnable01 implements Runnable { /** * 重写run方法,实现线程的执行逻辑 */ @Override public void run() { // 循环输出当前线程的名称 while (true) { System.out.println(Thread.currentThread().getName() + ":run()方法在运行"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } public class Example02 { public static void main(String[] args) throws InterruptedException { // 创建1个自定义线程任务对象 MyRunnable01 mr = new MyRunnable01(); //创建2个线程对象 Thread t1 = new Thread(mr, "线程1"); Thread t2 = new Thread(mr, "线程2"); // 启动线程 t1.start(); t2.start(); // 在主方法中执行while循环 while (true) { System.out.println("main()方法在运行"); //让当前线程暂停1000毫秒 Thread.sleep(1000); } } }

1. 任务定义 (MyRunnable01)

程序定义了一个实现Runnable接口的类。这相当于定义了一个名为“任务”的蓝图,规定了线程启动后要做的具体事情:不断输出线程名,并暂停 1 秒。

2. 多线程的启动 (main方法)

main方法中,程序创建了三个独立的“执行流”:

  • t1:一个执行mr任务的线程,被命名为“线程1”。
  • t2:一个执行mr任务的线程,被命名为“线程2”。
  • main 线程:这是程序的“总指挥”,执行main方法中的循环。

3. 并发执行机制

当执行到t1.start()t2.start()时,JVM 会通知操作系统创建两个新的线程。此时,程序处于并发运行状态:

  • 调度器轮换:这三个线程(t1, t2, main)在 CPU 上并不是绝对同步的。操作系统会由调度器在它们之间快速切换,因此你会看到控制台交替打印出“线程1”、“线程2”和“main()方法”的信息。
  • 独立的死循环:每个线程内部都有while(true),这意味着它们一旦启动就不会自动停止。Thread.sleep(1000)的作用是让该线程暂时让出 CPU 使用权,进入阻塞状态一秒钟,这样其他线程就有机会获得执行权。

4. 总结:三个线程的关系

你可以把这个程序想象成三个人在同一间办公室工作

  • 每个人(线程)手里都拿着同一份工作说明书(MyRunnable01的逻辑)。
  • 每个人每做完一步(输出信息)后,都会休息 1 秒钟(sleep)。
  • 虽然大家按相同的规则工作,但在任何一个瞬间,谁在说话(输出)是由“办公室主管”(系统 CPU)来分配的,所以输出顺序是不固定的,看起来是交织在一起的。

// 使用 Callable 和 FutureTask 实现多线程 import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; class MyCallable01 implements Callable<String> { @Override public String call() throws InterruptedException { // 循环输出当前线程的名称 while (true) { String str = Thread.currentThread().getName() + ":call()方法在运行"; System.out.println(str); Thread.sleep(1000); return str; } } } public class Example03 { public static void main(String[] args) throws Exception { // 创建Callable线程任务对象 Callable<String> call01 = new MyCallable01(); Callable<String> call02 = new MyCallable01(); // 通过FutureTask对象封装Callable对象 FutureTask<String> f1 = new FutureTask<>(call01); FutureTask<String> f2 = new FutureTask<>(call02); // 创建线程对象 Thread t1 = new Thread(f1, "线程1"); Thread t2 = new Thread(f2, "线程2"); // 启动线程 t1.start(); t2.start(); while (true) { if (f1.isDone()) { System.out.println(f1.get()); } if (f2.isDone()) { System.out.println(f2.get()); } System.out.println("main()"); Thread.sleep(1000); } } }

相比于之前看到的Runnable,这个版本最核心的区别在于:它能从线程中获取返回值

1.Callable接口:有“回执”的任务

Runnablerun()方法没有返回值,而Callablecall()方法是有返回值的(这里返回String)。这就像是你交给快递员任务,Callable不仅会帮你运东西,还能把任务的结果(比如签收回执)带回来。

2.FutureTask:任务的“代理人”

Thread构造函数只接受Runnable接口,不直接支持Callable。因此,我们需要FutureTask来作为中间人:

  • 它封装了Callable任务。
  • 它实现了RunnableFuture接口(既能被执行,又能存结果)。
  • 当线程执行完call()方法后,结果会自动保存在FutureTask中,主程序可以通过f1.get()随时领取这个结果。

3. 主线程的“状态检查”机制 (isDone)

main方法的循环中,你看到了一段很有趣的逻辑:

if (f1.isDone()) { System.out.println(f1.get()); }
  • isDone():这是一个询问机制,类似于主线程在问:“任务完成了没?”。
  • get():只有当isDone()返回true时,调用get()才会立即拿到结果。如果任务还没完成,get()方法默认会阻塞(暂停等待),直到结果出来。

4. 程序执行过程的变化

这个程序和上一个Runnable示例表现上有显著不同:

  • 只输出一次:注意你的MyCallable01中虽然有while(true),但在循环内部执行了一次打印和休息后,紧接着就是return str;。这会导致线程在执行一次任务后直接结束(终止)。
  • 任务结束后不再执行:因为MyCallable01执行完一次就返回了,所以t1t2线程会很快走向死亡(线程状态变为TERMINATED)。
  • main 线程持续工作main线程会不断轮询f1f2的状态。你会观察到:
    1. 前 1 秒:线程 1 和线程 2 输出结果,main 线程输出main()
    2. 1 秒后:任务结束,之后的循环里f1.isDone()f2.isDone()持续为true,主线程会一直打印之前拿到的那个结果值。

总结比较

特性Runnable 方式Callable 方式
返回值有 (通过 FutureTask 获取)
异常处理只能在 run 内部捕获可以抛出,由调用者接收
适用场景简单的后台任务需要获取计算结果的场景

核心提示:这个程序展示了异步获取结果的模式。在大型应用中,我们通常会让线程去计算复杂的数据,计算完成后,主线程再通过FutureTask统一收集结果。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询