我有一个spring应用程序,我有一个接受多个文件的端点,所以你可以上传多个文件。我试图通过利用线程使它工作得更快,所以我所做的是创建一个ThreadPool并尝试异步处理每个文件(在不同的线程中)。实际上,我在youtube上找到了一个教程,它几乎是我想要的,我想尝试他的代码,看看它是否有效,但它也不适合我。这是教程的链接。
https://www.youtube.com/watch?v=3rJBLFA95Io我相信他没有得到错误,因为在他的情况下总是文件数量和线程匹配。我发现的是,如果任务池中的任务数等于或小于可用线程数,那么一切都工作得很好,但如果任务数或大于可用线程数,我会得到一个错误,这是:
java.io.FileNotFoundException: /private/var/folders/41/81526n295q1cptcb1tbrs544h504m8/T/tomcat.9191.5294201821824312569/work/Tomcat/localhost/ROOT/upload_ff67c7fe_2f2f_44c6_8eb9_3250c8a8739b_00000003.tmp (No such file or directory)
at java.base/java.io.FileInputStream.open0(Native Method) ~[na:na]
at java.base/java.io.FileInputStream.open(FileInputStream.java:216) ~[na:na]
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:157) ~[na:na]
at org.apache.tomcat.util.http.fileupload.disk.DiskFileItem.getInputStream(DiskFileItem.java:198) ~[tomcat-embed-core-9.0.63.jar:9.0.63]
at org.apache.catalina.core.ApplicationPart.getInputStream(ApplicationPart.java:100) ~[tomcat-embed-core-9.0.63.jar:9.0.63]
at org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile.getInputStream(StandardMultipartHttpServletRequest.java:251) ~[spring-web-5.3.20.jar:5.3.20]
at com.mhndev.springexecutor.service.UserService.parseCSVFile(UserService.java:47) ~[classes/:na]
at com.mhndev.springexecutor.service.UserService.saveUsers(UserService.java:29) ~[classes/:na]
at com.mhndev.springexecutor.service.UserService$$FastClassBySpringCGLIB$$c14fedc2.invoke(<generated>) ~[classes/:na]
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.3.20.jar:5.3.20]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:793) ~[spring-aop-5.3.20.jar:5.3.20]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.3.20.jar:5.3.20]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763) ~[spring-aop-5.3.20.jar:5.3.20]
at org.springframework.aop.interceptor.AsyncExecutionInterceptor.lambda$invoke$0(AsyncExecutionInterceptor.java:115) ~[spring-aop-5.3.20.jar:5.3.20]
at org.springframework.aop.interceptor.AsyncExecutionAspectSupport.lambda$doSubmit$3(AsyncExecutionAspectSupport.java:278) ~[spring-aop-5.3.20.jar:5.3.20]
at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1768) ~[na:na]
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136) ~[na:na]
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) ~[na:na]
at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]
所以我怀疑的是,当一个任务仍然在池中,多部分文件被删除之前,任务可以有一个分配的线程,由于某种原因。
my controller:
package com.mhndev.springexecutor.controller;
import com.mhndev.springexecutor.entity.User;
import com.mhndev.springexecutor.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@RestController
public class UserController {
@Autowired
private UserService userService;
@PostMapping(value = "/users", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
public ResponseEntity saveUsers(@RequestParam(value = "files") MultipartFile[] files) throws Exception {
for(MultipartFile file: files) {
userService.saveUsers(file);
}
return ResponseEntity.status(HttpStatus.CREATED).build();
}
@GetMapping(value = "/users", produces = "application/json")
public CompletableFuture<ResponseEntity> findAllUsers() {
return userService.findAllUsers().thenApply(ResponseEntity::ok);
}
@GetMapping(value = "/getUsersByThread", produces = "application/json")
public ResponseEntity getUsers(){
CompletableFuture<List<User>> users1=userService.findAllUsers();
CompletableFuture<List<User>> users2=userService.findAllUsers();
CompletableFuture<List<User>> users3=userService.findAllUsers();
CompletableFuture.allOf(users1,users2,users3).join();
return ResponseEntity.status(HttpStatus.OK).build();
}
}
和我的服务:
package com.mhndev.springexecutor.service;
import com.mhndev.springexecutor.entity.User;
import com.mhndev.springexecutor.repository.UserRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Service
public class UserService {
@Autowired
private UserRepository repository;
Logger logger = LoggerFactory.getLogger(UserService.class);
@Async("taskExecutor")
public CompletableFuture<List<User>> saveUsers(MultipartFile file) throws Exception {
long start = System.currentTimeMillis();
List<User> users = parseCSVFile(file);
logger.info("saving list of users of size {}, thread name : {}", users.size(), "" + Thread.currentThread().getName());
users = repository.saveAll(users);
long end = System.currentTimeMillis();
logger.info("Total time {}", (end - start));
return CompletableFuture.completedFuture(users);
}
@Async("taskExecutor")
public CompletableFuture<List<User>> findAllUsers(){
logger.info("get list of user by " + Thread.currentThread().getName());
List<User> users = repository.findAll();
return CompletableFuture.completedFuture(users);
}
private List<User> parseCSVFile(final MultipartFile file) throws Exception {
final List<User> users = new ArrayList<>();
try {
try (final BufferedReader br = new BufferedReader(new InputStreamReader(file.getInputStream()))) {
String line;
while ((line = br.readLine()) != null) {
final String[] data = line.split(",");
final User user = new User();
user.setName(data[0]);
user.setEmail(data[1]);
user.setGender(data[2]);
users.add(user);
}
return users;
}
} catch (final IOException e) {
logger.error("Failed to parse CSV file", e);
throw new Exception("Failed to parse CSV file {}", e);
}
}
}
和配置threadpool:
package com.mhndev.springexecutor.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean(name = "taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(3);
executor.setMaxPoolSize(3);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("User-Thread-");
executor.initialize();
return executor;
}
}
我通过使用CompletableFuture修复了这个问题。所以我改变了我的控制器如下:
@PostMapping(value = "/users", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
public ResponseEntity saveUsers(@RequestParam(value = "files") MultipartFile[] files) throws Exception {
var res = new ArrayList<CompletableFuture<List<User>>>();
for(MultipartFile file: files) {
res.add(userService.saveUsers(file));
}
for(CompletableFuture<List<User>> item: res) {
System.out.println(item.get().size());
}
return ResponseEntity.status(HttpStatus.CREATED).build();
}
似乎请求的会话结束,然后在请求的会话结束后删除临时文件。你知道我是怎么发现的吗?我在控制器体中添加了一个睡眠,它工作了,所以我确定这是因为请求结束了,但线程池队列中有一些未完成的任务,控制器不等待任务完成,似乎我不得不使用可完成的未来。
我知道你已经发帖一年了,但这里有一个替代你的问题。也许有人已经经历过了,还没有找到出路。
您可以将MultipartFile保存到目录中的文件中。下面是相应的代码:
public List<Path> saveFile(MultipartFile[] files) {
List<Path> files = new ArrayList<>();
for(MultipartFile file: files) {
try {
Path root = Paths.get(System.getProperty("java.io.tmpdir") + "/upload/");
Files.createDirectories(root);
Path filePath = this.root.resolve(""file.getOriginalFilename());
Files.delete(filePath);
Files.copy(file.getInputStream(), filePath);
files.add(filePath)
} catch (Exception e) {
if (e instanceof FileAlreadyExistsException) {
throw new RuntimeException("A file of that name already exists.");
}
throw new RuntimeException(e.getMessage());
}
}
return files;
}
保存后使用保存的文件而不是MultipartFile
@PostMapping(value = "/users", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
public ResponseEntity saveUsers(@RequestParam(value = "files") MultipartFile[] files) throws Exception {
var res = new ArrayList<CompletableFuture<List<User>>>();
List<Path> pathFiles = saveFile(files);
for(Path file: pathFiles) {
res.add(userService.saveUsers(file));
}
for(CompletableFuture<List<User>> item: res) {
System.out.println(item.get().size());
}
return ResponseEntity.status(HttpStatus.CREATED).build();
}