一、如果你想使用 Thread 类手动创建线程,可以按照以下步骤进行。在这个例子中,我将 uploadUrl
方法中的任务分割成三个子任务,每个子任务由一个线程执行。
import java.util.ArrayList;
import java.util.List;
public class BookingPhoneMarkServiceImpl {
public void uploadUrl(UrlParamReq urlParamReq, String fileName, List<FlightInfo> marketReqs) throws IOException {
List<FlightInfo> list = new ArrayList<>();
int numThreads = 3; // 你可以根据需求调整线程数量
// 计算每个线程处理的数据范围
int batchSize = marketReqs.size();
int chunkSize = (int) Math.ceil((double) batchSize / numThreads);
// 创建线程数组
Thread[] threads = new Thread[numThreads];
for (int i = 0; i < numThreads; i++) {
int startIdx = i * chunkSize;
int endIdx = Math.min((i + 1) * chunkSize, batchSize);
// 创建并启动线程
threads[i] = new Thread(() -> {
processBatch(marketReqs.subList(startIdx, endIdx), urlParamReq, list);
});
threads[i].start();
}
// 等待所有线程执行完成
for (Thread thread : threads) {
try {
thread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
}
excel(list, urlParamReq, fileName);
}
private void processBatch(List<FlightInfo> batch, UrlParamReq urlParamReq, List<FlightInfo> resultList) {
for (FlightInfo marketReq : batch) {
String startDate = marketReq.getStartTime();
String endDate = marketReq.getEndTime();
LocalDate start = LocalDate.parse(startDate);
LocalDate end = LocalDate.parse(endDate);
while (!start.isAfter(end)) {
marketReq.setStartTime(start.toString());
searchFlight(marketReq, urlParamReq);
FlightInfo flightInfo = doPost(marketReq, urlParamReq);
resultList.add(flightInfo);
start = start.plusDays(1);
}
}
}
// 其他方法保持不变
}
不用实现run方法吗
实际上,我在给你的代码中使用了 lambda 表达式,其中的 () -> { ... }
部分相当于 run
方法的实现。这是 Java 8 引入的一种简化语法,用于创建匿名内部类的实例。
在上述代码中,以下部分是线程的主体:
threads[i] = new Thread(() -> {
processBatch(marketReqs.subList(startIdx, endIdx), urlParamReq, list);
});
在这里,() -> { ... }
表达式就是 run
方法的实现。它定义了线程的执行逻辑。在这个例子中,这部分代码相当于实现了 run
方法。
如果你更喜欢传统的写法,你也可以使用下面的方式:
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
processBatch(marketReqs.subList(startIdx, endIdx), urlParamReq, list);
}
});
这是使用匿名内部类的传统方式,实现了 Runnable
接口的 run
方法。这两种写法是等价的。希望这样解释更加清晰。
Thread.join()
在Java中,Thread.join()
方法用于等待线程终止。具体来说,join
方法的调用会使当前线程等待被调用线程(在这里就是thread
)执行完成后再继续执行。
在你的例子中,通过调用thread.join()
,主线程(即调用join
的线程)会等待thread
线程执行完成后再继续执行下面的代码。这样做的目的是为了确保在主线程中等待所有的子线程执行完毕,然后再执行主线程的后续逻辑,以防止并发问题。
具体而言,在你的代码中是这样的
// 创建并启动线程
threads[i] = new Thread(() -> {
processBatch(marketReqs.subList(startIdx, endIdx), urlParamReq, list);
});
threads[i].start();
// 等待所有线程执行完成
for (Thread thread : threads) {
try {
thread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
}
这段代码中,threads[i].start()
启动了子线程,然后在 for
循环中,通过 thread.join()
确保主线程等待每个子线程执行完成。如果子线程中发生异常,InterruptedException
会被捕获并处理。
这样的处理方式可以确保主线程在所有子线程执行完毕后再继续执行后续逻辑,保证了线程之间的协同。
int chunkSize = (int) Math.ceil((double) batchSize / numThreads)
这行代码的作用是计算每个线程处理的数据量。让我解释一下:
batchSize
是要处理的数据总量,即marketReqs.size()
。numThreads
是线程数量,表示你希望将数据划分成多少份由多少个线程并行处理。(double) batchSize / numThreads
计算了每个线程平均处理的数据量,这里使用了浮点数,确保计算结果是浮点数。Math.ceil(...)
用于向上取整,确保每个线程处理的数据量是一个整数,不会出现小数部分。- 最后,
(int)
将结果转换为整数,得到了每个线程处理的数据量chunkSize
。
这个计算确保了将数据均匀地划分给每个线程,即使不能整除,也会向上取整,确保不会有线程处理更多的数据。这样可以更好地利用多线程并行处理数据。
二、如果你想在 uploadUrl
方法中开启三个线程并行处理业务,你可以按照下面的方式修改代码。以下示例假设你使用 Java 8 的 CompletableFuture
来实现异步任务:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class BookingPhoneMarkServiceImpl {
public void uploadUrl(UrlParamReq urlParamReq, String fileName, List<FlightInfo> marketReqs) throws IOException {
List<FlightInfo> list = new ArrayList<>();
int numThreads = 3; // 你可以根据需求调整线程数量
// 计算每个线程处理的数据范围
int batchSize = marketReqs.size();
int chunkSize = (int) Math.ceil((double) batchSize / numThreads);
// 创建 CompletableFuture 数组
CompletableFuture<Void>[] futures = new CompletableFuture[numThreads];
for (int i = 0; i < numThreads; i++) {
int startIdx = i * chunkSize;
int endIdx = Math.min((i + 1) * chunkSize, batchSize);
// 提交任务到 CompletableFuture
futures[i] = CompletableFuture.runAsync(() -> {
processBatch(marketReqs.subList(startIdx, endIdx), urlParamReq, list);
});
}
// 等待所有 CompletableFuture 完成
CompletableFuture.allOf(futures).join();
excel(list, urlParamReq, fileName);
}
private void processBatch(List<FlightInfo> batch, UrlParamReq urlParamReq, List<FlightInfo> resultList) {
for (FlightInfo marketReq : batch) {
String startDate = marketReq.getStartTime();
String endDate = marketReq.getEndTime();
LocalDate start = LocalDate.parse(startDate);
LocalDate end = LocalDate.parse(endDate);
while (!start.isAfter(end)) {
marketReq.setStartTime(start.toString());
searchFlight(marketReq, urlParamReq);
FlightInfo flightInfo = doPost(marketReq, urlParamReq);
resultList.add(flightInfo);
start = start.plusDays(1);
}
}
}
// 其他方法保持不变
}
在这个例子中,uploadUrl
方法会将数据划分成三个子集,每个子集由一个 CompletableFuture 异步执行。在 processBatch
方法中,处理每个子集的业务逻辑。最后,通过等待所有 CompletableFuture 完成来确保所有线程执行完成。请根据你的实际需求调整线程数量和逻辑。
三、使用线程池可以更有效地管理和重用线程,提高程序的性能。以下是使用线程池的一般步骤:
步骤:
-
创建线程池: 使用
Executors.newFixedThreadPool
方法创建一个固定大小的线程池。可以根据需要选择其他类型的线程池,例如CachedThreadPool
或SingleThreadExecutor
。ExecutorService executorService = Executors.newFixedThreadPool(numThreads);
numThreads
是线程池的大小,即同时运行的线程数。 -
提交任务: 使用线程池的
submit
方法提交任务。任务可以是实现了Runnable
接口或Callable
接口的对象。for (int i = 0; i < numThreads; i++) { int startIdx = i * chunkSize; int endIdx = Math.min((i + 1) * chunkSize, batchSize); Runnable task = new YourTask(marketReqs.subList(startIdx, endIdx), urlParamReq, list); executorService.submit(task); }
YourTask
是你自己实现的任务类,需要实现Runnable
接口,并在run
方法中编写具体的处理逻辑。 -
关闭线程池: 在所有任务完成后,关闭线程池。
executorService.shutdown();
示例:
下面是一个简化的示例,假设你有一个Task
类实现了Runnable
接口:
public class YourTask implements Runnable {
private List<FlightInfo> data;
private UrlParamReq urlParamReq;
private List<FlightInfo> result;
public YourTask(List<FlightInfo> data, UrlParamReq urlParamReq, List<FlightInfo> result) {
this.data = data;
this.urlParamReq = urlParamReq;
this.result = result;
}
@Override
public void run() {
// 处理数据的逻辑
processBatch(data, urlParamReq, result);
}
}
在主方法中,你可以使用线程池执行任务:
int numThreads = 3;
int batchSize = marketReqs.size();
int chunkSize = (int) Math.ceil((double) batchSize / numThreads);
ExecutorService executorService = Executors.newFixedThreadPool(numThreads);
for (int i = 0; i < numThreads; i++) {
int startIdx = i * chunkSize;
int endIdx = Math.min((i + 1) * chunkSize, batchSize);
Runnable task = new YourTask(marketReqs.subList(startIdx, endIdx), urlParamReq, list);
executorService.submit(task);
}
executorService.shutdown();
这样,你就可以使用线程池来管理并发执行的任务,提高效率。注意,线程池的具体配置和使用可以根据实际需求进行调整。
是在run方法中处理业务吗
是的,通常在run
方法中处理业务逻辑。在Runnable
接口中,run
方法定义了线程的执行体,即线程启动后要执行的代码块。所以,在YourTask
类中,你需要在run
方法中编写具体的业务逻辑,以完成任务的处理。
例如,在你的场景中,YourTask
类可能包含了上传URL的业务逻辑:
public class YourTask implements Runnable {
private List<FlightInfo> data;
private UrlParamReq urlParamReq;
private List<FlightInfo> result;
public YourTask(List<FlightInfo> data, UrlParamReq urlParamReq, List<FlightInfo> result) {
this.data = data;
this.urlParamReq = urlParamReq;
this.result = result;
}
@Override
public void run() {
// 在这里编写处理数据的具体业务逻辑
processBatch(data, urlParamReq, result);
}
}
sBatch
方法,该方法包含了实际的业务逻辑。这样,每个线程在执行时会调用run
方法,从而执行你定义的业务逻辑。
确保 processBatch
方法中的逻辑是线程安全的,因为多个线程会同时执行这个方法。如果涉及到共享资源,需要考虑同步机制来保证线程安全。