我有一个包含JSON文件的目录,需要对其进行迭代才能获得文档dName
的名称。多个JSON文件可以具有相同的dName
。然后,需要从名为output/dName/match
的文件夹中为该JSON文件创建符号链接。线程首先检查dName
文件夹是否存在,如果不存在则先创建它们。我有以下代码创建符号链接。
protected static void copyFile(String docName, Path tFilePath) throws IOException {
final String docFolderName = "output" + docName.substring(0, docName.lastIndexOf("."));
final String opDir = docFolderName + "match";
path = Paths.get(opDir);
if (Files.notExists(path)) {
Files.createDirectories(path);
outputAnnotationDirs.add(path.toString());
}
try {
Files.createSymbolicLink(Paths.get(opDir).resolve(tFilePath.getFileName()), tFilePath);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
protected static void Mapper(String Dir,int numThreads) throws Exception {
final ExecutorService executorService = Executors.newFixedThreadPool(numThreads);
final ConcurrentLinkedQueue<Future<?>> futures = new ConcurrentLinkedQueue<Future<?>>();
final JsonParser parser = new JsonParser();
try {
Files.walkFileTree(Paths.get(Dir), new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(final Path tFile, BasicFileAttributes attrs) throws IOException {
futures.add((Future<String>) executorService.submit(new Runnable() {
public void run() {
JsonObject jsonObject = null;
FileReader reader = null;
try {
reader = new FileReader(tFile.toFile());
jsonObject = (JsonObject) parser.parse(reader);
JsonArray instancesArray = (JsonArray) jsonObject.get("instances");
String dName = instancesArray.get(0).getAsJsonObject().get("dname").toString();
copyFile(dName, tFile);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
try {
if (reader != null)
reader.close();
} catch (IOException e) {
logger.error(e);
}
}
}
}));
return FileVisitResult.CONTINUE;
}
});
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
Future<?> future;
while ((future = futures.poll()) != null) {
try {
future.get();
} catch (Exception e) {
for (Future<?> f : futures)
f.cancel(true);
if (executorService != null)
executorService.shutdown();
throw new Exception(e);
}
}
if (executorService != null)
executorService.shutdown();
}
}
但是,在创建符号链接的行中会发生异常。
线程中的异常&#34; main&#34; java.lang.Exception:java.util.concurrent.ExecutionException:java.lang.RuntimeException:java.nio.file.NoSuchFileException:`
例如:output/document1/match/ok.json
如果我正确,只有在执行该行后才会创建符号链接。那为什么会出现错误?由线程创建的单个符号链接为什么会导致concurrent.ExecutionException
?
答案 0 :(得分:2)
那么为什么会发生错误?
发生错误是因为您的"parent directory creation"
在创建符号链接之前未创建所有父目录。例如:如果你有"dname": "a/b/c/somedname1.txt"
的json条目 - 似乎没有创建文件夹a/b/c
。这就是抛出NoSuchFileException
的原因。现在,您已经有了创建目录的逻辑,但为什么这不起作用?如果你在一个线程中运行它,那本来可以正常工作。为什么不在多线程?
因为,path
变量在所有线程中共享,并且同时被许多线程修改。
path = Paths.get(opDir);
if (Files.notExists(path)) {
Files.createDirectories(path);
outputAnnotationDirs.add(path.toString());
}
当在多个线程中运行时,例如,一个线程有dname:a/b/c/dname1.txt
,第二个线程有dname:e/f/g/dname2.txt
。第一个线程可能最终创建e / f / g而不是/ b / c目录。经典的并发问题。使path
成为局部变量将立即解决您的问题。或者在一个线程中运行您的进程。
java.io.FileNotFoundException
。java.nio.file.FileAlreadyExistsException
。 java.nio.file.NoSuchFileException
,例如DELETE。或者当您没有父文件夹时尝试创建文件/符号链接时。 由线程创建的单个符号链接为什么会导致 concurrent.ExecutionException?
NoSuchFileException
被RunTimeException
包裹,ExecutionException
被get
包裹future
。因为,RunTimeException发生在另一个线程上,并且您的下面调用发生在主线程上。所以Executor包装Exception并触发从主线程调用的下面的调用。
future.get();
谢谢。
答案 1 :(得分:1)
如我的评论中所述:
path = Paths.get(opDir);
是一种竞争条件。
答案 2 :(得分:0)
简单..有竞争条件。将相对路径变量的范围更改为局部变量。
答案 3 :(得分:-3)
对我来说,看起来NoSuchFileException告诉你问题是什么。要创建文件的符号链接,该文件应该存在。