java 并发编程 (1)java中如何实现并发编程
目录
1. 继承 Thread 类
2. 实现 Runnable 接口
3. 使用 FutureTask
4. 使用 Executor 框架
5. 具体案例
1. 继承 Thread 类
概述:通过继承 Thread
类并重写其 run()
方法来创建一个新的线程。
步骤:
- 创建一个继承
Thread
类的子类。 - 重写
run()
方法,定义线程执行的任务。 - 调用
start()
方法来启动线程。
优缺点:
- 优点:实现简单,直接继承
Thread
类即可。 - 缺点:由于 Java 不支持多继承,如果已经继承了其他类,则不能再继承
Thread
类。
2. 实现 Runnable 接口
概述:通过实现 Runnable
接口并重写其 run()
方法来定义线程的任务,再通过 Thread
类来启动线程。
步骤:
- 创建一个实现
Runnable
接口的类。 - 重写
run()
方法,定义线程执行的任务。 - 使用
Thread
类来启动线程并传递Runnable
对象。
优缺点:
- 优点:相比于继承
Thread
,实现Runnable
可以避免继承的限制,可以实现多线程任务的复用。 - 缺点:需要显式地将
Runnable
对象传递给Thread
,稍微多了一些步骤。
3. 使用 FutureTask
概述:FutureTask
是一个用于表示异步计算结果的类,可以结合 Runnable
或 Callable
来创建并管理多线程任务。它实现了 Runnable
接口,因此可以用于线程的启动,并可以获取任务的执行结果。
步骤:
- 创建一个实现
Callable
接口的任务类(如果需要返回值)。 - 使用
FutureTask
包装该任务。 - 使用
Thread
或ExecutorService
来执行FutureTask
。
优缺点:
- 优点:可以返回结果并处理异常,适用于需要获取计算结果的任务。
- 缺点:相较于
Runnable
,稍微复杂一些,适用于复杂任务或异步计算。
4. 使用 Executor 框架
概述:Executor
框架提供了一种更高层次的线程池管理方式。它通过 ExecutorService
接口来管理线程池中的线程,实现任务的提交、调度、管理等。
步骤:
- 创建一个线程池,例如通过
Executors.newFixedThreadPool()
创建一个固定大小的线程池。 - 提交任务到线程池,任务可以是实现了
Runnable
或Callable
的任务。 - 通过
ExecutorService
的submit()
或execute()
方法来提交任务。
优缺点:
- 优点:线程池管理线程,减少了创建和销毁线程的开销,能够控制最大线程数,适用于大量任务的执行。
- 缺点:需要更多的配置和理解,适合中大型项目中的多线程任务管理。
5. 具体案例
package com.lirui.springbootmoduledemo.controller;
import java.util.concurrent.*;
public class test {
public static void main(String[] args) throws InterruptedException {
long startTime = System.currentTimeMillis(); // 记录开始时间
// 创建任务 1
Thread task1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Task 1 started.");
try {
Thread.sleep(2000); // 模拟任务执行2秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Task 1 finished.");
}
});
// 创建任务 2
Thread task2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Task 2 started.");
try {
Thread.sleep(1000); // 模拟任务执行1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Task 2 finished.");
}
});
// 创建任务 3
Thread task3 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Task 3 started.");
try {
Thread.sleep(1500); // 模拟任务执行1.5秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Task 3 finished.");
}
});
// 启动任务
task1.start();
task2.start();
task3.start();
// 等待所有线程执行完毕
task1.join();
task2.join();
task3.join();
long endTime = System.currentTimeMillis(); // 记录结束时间
System.out.println("All tasks finished.");
System.out.println("Total time taken: " + (endTime - startTime) + " milliseconds");
}
public static void main(String[] args) throws InterruptedException {
long startTime = System.currentTimeMillis(); // 记录开始时间
// 执行任务 1
System.out.println("Task 1 started.");
Thread.sleep(2000); // 模拟任务执行2秒
System.out.println("Task 1 finished.");
// 执行任务 2
System.out.println("Task 2 started.");
Thread.sleep(1000); // 模拟任务执行1秒
System.out.println("Task 2 finished.");
// 执行任务 3
System.out.println("Task 3 started.");
Thread.sleep(1500); // 模拟任务执行1.5秒
System.out.println("Task 3 finished.");
long endTime = System.currentTimeMillis(); // 记录结束时间
System.out.println("All tasks finished.");
System.out.println("Total time taken: " + (endTime - startTime) + " milliseconds");
}
}
从上面的案例中可以明显看出,并发执行和顺序执行在速度上的差距。具体来说,在 单线程 执行的情况下,任务是按顺序依次执行的,每个任务必须等待前一个任务完成后才能开始,因此总的执行时间是所有任务执行时间的累加。而在 多线程 执行的情况下,任务是并行执行的,多个任务可以同时进行,特别是在多核处理器上,线程可以在不同的 CPU 核心上并行运行,这大大缩短了总的执行时间。通过这种方式,任务 1、2 和 3 的执行时间是重叠的,最终的执行时间仅取决于最长的任务。因此,在并发执行的情况下,程序的总执行时间显著减少,相比顺序执行,性能提升尤为明显,尤其当任务之间的执行时间差异较大时,效果更加突出。