// 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 CallableString { 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线程任务对象 CallableString call01 new MyCallable01(); CallableString call02 new MyCallable01(); // 通过FutureTask对象封装Callable对象 FutureTaskString f1 new FutureTask(call01); FutureTaskString 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接口有“回执”的任务Runnable的run()方法没有返回值而Callable的call()方法是有返回值的这里返回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执行完一次就返回了所以t1和t2线程会很快走向死亡线程状态变为TERMINATED。main 线程持续工作main线程会不断轮询f1和f2的状态。你会观察到前 1 秒线程 1 和线程 2 输出结果main 线程输出main()。1 秒后任务结束之后的循环里f1.isDone()和f2.isDone()持续为true主线程会一直打印之前拿到的那个结果值。总结比较特性Runnable 方式Callable 方式返回值无有 (通过 FutureTask 获取)异常处理只能在 run 内部捕获可以抛出由调用者接收适用场景简单的后台任务需要获取计算结果的场景核心提示这个程序展示了异步获取结果的模式。在大型应用中我们通常会让线程去计算复杂的数据计算完成后主线程再通过FutureTask统一收集结果。
Java 中多线程并发执行的基本逻辑
发布时间:2026/6/26 19:44:26
// 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 CallableString { 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线程任务对象 CallableString call01 new MyCallable01(); CallableString call02 new MyCallable01(); // 通过FutureTask对象封装Callable对象 FutureTaskString f1 new FutureTask(call01); FutureTaskString 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接口有“回执”的任务Runnable的run()方法没有返回值而Callable的call()方法是有返回值的这里返回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执行完一次就返回了所以t1和t2线程会很快走向死亡线程状态变为TERMINATED。main 线程持续工作main线程会不断轮询f1和f2的状态。你会观察到前 1 秒线程 1 和线程 2 输出结果main 线程输出main()。1 秒后任务结束之后的循环里f1.isDone()和f2.isDone()持续为true主线程会一直打印之前拿到的那个结果值。总结比较特性Runnable 方式Callable 方式返回值无有 (通过 FutureTask 获取)异常处理只能在 run 内部捕获可以抛出由调用者接收适用场景简单的后台任务需要获取计算结果的场景核心提示这个程序展示了异步获取结果的模式。在大型应用中我们通常会让线程去计算复杂的数据计算完成后主线程再通过FutureTask统一收集结果。