多线程学习之路
多线程的创建方式主要有以下几种:
1. 继承 Thread 类
通过继承 Thread 类并重写 run() 方法来创建线程。
class MyThread extends Thread {
@Override
public void run() {
// 线程执行的任务
System.out.println("Thread is running");
}
}
public class Main {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start(); // 启动线程
}
}
2. 实现 Runnable 接口
通过实现 Runnable 接口并实现 run() 方法,然后将 Runnable 对象传递给 Thread 类的构造函数。
class MyRunnable implements Runnable {
@Override
public void run() {
// 线程执行的任务
System.out.println("Thread is running");
}
}
public class Main {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start(); // 启动线程
}
}
3. 实现 Callable 接口
通过实现 Callable 接口并实现 call() 方法,Callable 可以返回结果并抛出异常。通常与 ExecutorService 一起使用。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
// 线程执行的任务
return "Thread is running";
}
}
public class Main {
public static void main(String[] args) {
ExecutorService executor = Executors.newSingleThreadExecutor();
MyCallable myCallable = new MyCallable();
Future<String> future = executor.submit(myCallable);
try {
String result = future.get(); // 获取线程执行结果
System.out.println(result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
} finally {
executor.shutdown();
}
}
}
4. 使用线程池
通过 ExecutorService 和 Executors 工具类创建线程池,提交任务给线程池执行。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
Runnable task = new MyRunnable();
executor.execute(task);
}
executor.shutdown();
}
}
5. 使用 CompletableFuture(Java 8+)
CompletableFuture 提供了更高级的异步编程方式,支持链式调用和组合多个异步任务。
import java.util.concurrent.CompletableFuture;
public class Main {
public static void main(String[] args) {
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
// 异步执行的任务
System.out.println("Thread is running");
});
future.join(); // 等待任务完成
}
}
6. 使用 Timer 和 TimerTask
Timer 和 TimerTask 可以用于创建定时任务,TimerTask 实现了 Runnable 接口。
import java.util.Timer;
import java.util.TimerTask;
public class Main {
public static void main(String[] args) {
Timer timer = new Timer();
TimerTask task = new TimerTask() {
@Override
public void run() {
// 定时执行的任务
System.out.println("Thread is running");
}
};
timer.schedule(task, 0, 1000); // 延迟0毫秒,每隔1000毫秒执行一次
}
}
7. 使用 ForkJoinPool(Java 7+)
ForkJoinPool 是用于并行执行任务的线程池,特别适合分治算法和递归任务。
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;
class MyRecursiveAction extends RecursiveAction {
@Override
protected void compute() {
// 并行执行的任务
System.out.println("Thread is running");
}
}
public class Main {
public static void main(String[] args) {
ForkJoinPool pool = new ForkJoinPool();
MyRecursiveAction task = new MyRecursiveAction();
pool.invoke(task);
}
}
8.list.parallelStream()
import java.util.Arrays;
import java.util.List;
public class ParallelStream {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
list.parallelStream().forEach(System.out::println);
}
}
parallelStream 是基于 Fork/Join 框架 实现的,它允许将任务拆分成多个子任务并行处理,最后将结果合并。以下是 parallelStream 的底层实现原理:
- Fork/Join 框架
parallelStream 的并行处理依赖于 Java 7 引入的 Fork/Join 框架。该框架的核心思想是:
分治(Divide and Conquer):将一个大任务拆分成多个小任务(Fork),并行执行这些小任务,最后将结果合并(Join)。
工作窃取(Work Stealing):每个线程维护一个任务队列,当某个线程完成任务后,可以从其他线程的队列中“窃取”任务来执行,从而提高 CPU 利用率。 - 并行流的执行流程
当调用 parallelStream() 时,Java 会创建一个并行流,其底层执行流程如下:
(1) 任务拆分
数据源(如集合)会被拆分成多个子任务。
拆分的方式取决于数据源的类型:
对于 ArrayList 等可拆分的数据结构,会按照索引范围拆分成多个子任务。
对于 HashSet 等不可直接拆分的数据结构,会先转换为数组再进行拆分。
(2) 并行执行
每个子任务会被提交到 Fork/Join 线程池(ForkJoinPool.commonPool())中执行。
默认情况下,线程池的大小为 CPU 核心数 - 1(可以通过 -Djava.util.concurrent.ForkJoinPool.common.parallelism 参数调整)。
(3) 结果合并
每个子任务执行完成后,结果会被合并(Join)。
合并的方式取决于流的操作类型(如 reduce、collect 等)。 - 并行流的性能优化
数据量:并行流适合处理大规模数据,小数据量时串行流(stream())可能更快,因为并行化会引入额外的开销(如任务拆分、线程调度等)。
数据结构:数据源的结构影响拆分效率。ArrayList 和数组等支持随机访问的数据结构更适合并行流,而 LinkedList 等链表结构则不适合。
操作类型:某些操作(如 filter、map)适合并行化,而某些操作(如 limit、findFirst)可能会限制并行化的效果。
总结
继承 Thread 类:简单直接,但Java不支持多继承,灵活性较差。
实现 Runnable 接口:更灵活,推荐使用。
实现 Callable 接口:可以返回结果和抛出异常,适合需要返回值的场景。
线程池:高效管理线程资源,适合大量异步任务。
CompletableFuture:支持复杂的异步编程。
Timer 和 TimerTask:适合定时任务。
ForkJoinPool:适合并行计算和分治任务。
根据具体需求选择合适的方式。