JAVA 多线程的三种方式
1. 參考文章
2. 通过继承 Thread 类创建线程类
2.1. 步驟
- 定义一个类继承 Thread 类,并重写 Thread 类的 run()方法,run()方法的方法体就是线程要完成的任务,因此把 run()称为线程的执行体;
- 创建该类的实例对象,即创建了线程对象;
- 调用线程对象的 start()方法来启动线程;
2.2. 实现样例
public class DecryptThread extends Thread {
private String batchNo;
YmlConfig ymlConfig;
private TaskRepository taskRepository;
private final Logger logger = LoggerFactory.getLogger(DecryptThread.class);
public DecryptThread(String batchNo, YmlConfig ymlConfig, TaskRepository taskRepository) {
this.batchNo = batchNo;
this.ymlConfig = ymlConfig;
this.taskRepository = taskRepository;
}
public void run() {
decrypt(batchNo, ymlConfig,taskRepository);
}
}
public static void main(String[] args) {
DecryptThread decryptThread = new DecryptThread(batchNo,ymlConfig,taskRepository);
decryptThread.start();
}
2.3. 优点
- 简单直接,代码清晰。
- 适用于不需要共享资源的场景。每个线程的创建都要创建不同的子类对象,导致两个线程不能共享成员变量;
2.4. 缺点
- 由于 Java 不支持多继承,如果继承了 Thread 类,就不能继承其他类。
- 不适用于需要共享资源的场景,因为每个线程都是独立的 Thread 实例。
3. 实现 Runnable 接口
3.1. 步骤
- 创建一个类实现 java.lang.Runnable 接口。
- 实现 run()方法,将线程执行的任务放入 run()方法中。
- 创建 Runnable 实现类的实例;
- 创建 Thread 类的实例,并将 Runnable 实例传递给 Thread 构造函数。
- 调用 Thread 实例的 start()方法启动线程。
- Runnable 实现类里包含的 run()方法仅仅作为线程执行体,而实际的线程对象依然是 Thread 实例;
3.2. 实现样例
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("Runnable running: " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Main {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
}
}
3.3. 优点
- 可以实现资源共享,适用于多个线程访问同一个资源的场景。可以共同访问 run()方法中的成员变量;TODO 线程安全问题;
- 灵活性更高,因为可以通过实现接口来继承其他类。
3.4. 缺点
- 需要额外创建一个 Thread 实例,代码相对稍微复杂;
4. 使用 Callable 和 Future
4.1. 步骤
- 创建一个类实现 java.util.concurrent.Callable 接口。
- 实现 call()方法,将线程执行的任务和返回结果放入 call()方法中。
- 创建 Callable 实现类的实例。
- 创建 FutureTask 实例,并将 Callable 实例传递给 FutureTask 构造函数。
- 创建 Thread 类的实例,并将 FutureTask 实例传递给 Thread 构造函数。
- 调用 Thread 实例的 start()方法启动线程。
- 调用 FutureTask 实例的 get()方法获取线程执行结果。
4.2. 实现样例
- Future 接口来代表 Callable 接口里 call()方法的返回值,并为 Future 接口提供了一个 FutureTask 实现类,该类实现了 Future 接口,并实现了 Runnable 接口,所以 FutureTask 可以作为 Thread 类的 target,同时也解决了 Callable 对象不能作为 Thread 类的 target 这一问题。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
class MyCallable implements Callable<Integer> {
@Override
public Integer call() {
int sum = 0;
for (int i = 0; i < 5; i++) {
sum += i;
System.out.println("Callable running: " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return sum;
}
}
public class Main {
public static void main(String[] args) {
MyCallable myCallable = new MyCallable();
FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
Thread thread = new Thread(futureTask);
thread.start();
try {
Integer result = futureTask.get();
System.out.println("Result: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
4.3. 优点
- Callable 接口,该接口是 Runnable 接口的增强版,Callable 接口提供了一个 call()方法可以作为线程执行体,
- call()方法可以有返回值,可以声明抛出异常;
- 可以在线程任务完成后获取返回结果。
- 可以抛出异常并进行处理。
4.4. 缺点
- 相对来说比 Runnable 接口实现稍微复杂一些。
- 需要额外的 FutureTask 来包装 Callable 实例。
5. 总结
- 继承 Thread 类适用于简单的线程任务实现;
- 而实现 Runnable 接口则更灵活,适用于需要共享资源的场景。
- 使用 Callable 和 Future 接口不仅可以获取线程执行结果,还可以处理任务执行中的异常,更适合复杂的线程任务管理。