Java 并行流中I/O代码的SecurityException
我无法解释这一点,但我在其他人的代码中发现了这种现象:Java 并行流中I/O代码的SecurityException,java,parallel-processing,java-8,java-stream,securitymanager,Java,Parallel Processing,Java 8,Java Stream,Securitymanager,我无法解释这一点,但我在其他人的代码中发现了这种现象: import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; import java.util.stream.Stream; import org.junit.Test; public class TestDidWeBreakJavaAgain { @Test public void testIoI
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.util.stream.Stream;
import org.junit.Test;
public class TestDidWeBreakJavaAgain
{
@Test
public void testIoInSerialStream()
{
doTest(false);
}
@Test
public void testIoInParallelStream()
{
doTest(true);
}
private void doTest(boolean parallel)
{
Stream<String> stream = Stream.of("1", "2", "3");
if (parallel)
{
stream = stream.parallel();
}
stream.forEach(name -> {
try
{
Files.createTempFile(name, ".dat");
}
catch (IOException e)
{
throw new UncheckedIOException("Failed to create temp file", e);
}
});
}
}
import java.io.IOException;
导入java.io.UncheckedIOException;
导入java.nio.file.Files;
导入java.util.stream.stream;
导入org.junit.Test;
公共类TestDidWebreakJavaServer
{
@试验
public void testIoInSerialStream()
{
doTest(假);
}
@试验
公共void testIoInParallelStream()
{
多斯特(真);
}
私有void doTest(布尔并行)
{
溪流=溪流,共(“1”、“2”、“3”);
if(并行)
{
stream=stream.parallel();
}
stream.forEach(名称->{
尝试
{
createTempFile(名称为“.dat”);
}
捕获(IOE异常)
{
抛出新的未选中异常(“未能创建临时文件”,e);
}
});
}
}
在启用安全管理器的情况下运行时,仅对流调用parallel()
,或从集合获取流时调用parallelStream()
,似乎可以保证所有执行I/O的尝试都将抛出SecurityException
。(最有可能的是,调用任何可以抛出SecurityException
的方法都会抛出。)
我知道parallel()
意味着它将在另一个线程中运行,而这个线程可能与我们开始使用的线程没有相同的权限,但我想框架会为我们解决这个问题
在整个代码库中删除对
parallel()
或parallelStream()
的调用可以避免风险。插入一个AccessController.doPrivileged
也可以修复它,但对我来说并不安全,至少在所有情况下都不安全。还有其他选择吗?并行流执行将使用Fork/Join框架,更具体地说,它将使用Fork/Join公共池。这是一个实现细节,但正如在本例中所观察到的,这些细节可能会以意外的方式泄露出去
请注意,当使用CompletableFuture
异步执行任务时,也可能发生相同的行为
当存在安全管理器时,Fork/Join公共池的线程工厂被设置为创建无害线程的工厂。这样一个无害线程没有被授予任何权限,不是任何已定义线程组的成员,并且在顶级Fork/Join任务完成其执行后,所有线程局部变量(如果已创建)都将被清除。这种行为确保了在共享公共池时,Fork/Join任务彼此隔离
这就是为什么在示例中会抛出SecurityException
,可能是:
java.lang.SecurityException:无法创建临时文件或目录
有两种可能的解决办法。根据安全经理使用的原因,每种变通方法都可能增加不安全的风险
第一个更一般的解决方法是通过系统属性注册Fork/Join线程工厂,告诉Fork/Join框架公共池的默认线程工厂应该是什么。例如,这里有一个非常简单的线程工厂:
public class MyForkJoinWorkerThreadFactory
implements ForkJoinPool.ForkJoinWorkerThreadFactory {
public final ForkJoinWorkerThread newThread(ForkJoinPool pool) {
return new ForkJoinWorkerThread(pool) {};
}
}
可以使用以下系统属性注册:
-Djava.util.concurrent.ForkJoinPool.common.threadFactory=MyForkJoinWorkerThreadFactory
MyForkJoinWorkerThreadFactory
的行为目前与
第二个更具体的解决方法是创建一个新的Fork/Join池。在这种情况下,
ForkJoinPool.defaultForkJoinWorkerThreadFactory
将用于不接受ForkJoinWorkerThreadFactory
参数的构造函数。任何并行流执行都需要在该池中执行的任务中执行。请注意,这是一个实现细节,可能在将来的版本中起作用,也可能不起作用。您不必担心AccessController.doPrivileged
。如果操作正确,不会降低安全性。采用单个action参数的版本将在您的上下文中执行该操作,忽略您的调用者,但存在重载方法,具有一个附加参数,一个以前记录的上下文:
private void doTest(boolean parallel)
{
Consumer<String> createFile=name -> {
try {
Files.createTempFile(name, ".dat");
}
catch (IOException e) {
throw new UncheckedIOException("Failed to create temp file", e);
}
}, actualAction;
Stream<String> stream = Stream.of("1", "2", "3");
if(parallel)
{
stream = stream.parallel();
AccessControlContext ctx=AccessController.getContext();
actualAction=name -> AccessController.doPrivileged(
(PrivilegedAction<?>)()->{ createFile.accept(name); return null; }, ctx);
}
else actualAction = createFile;
stream.forEach(actualAction);
}
private void doTest(布尔并行)
{
消费者创建文件=名称->{
试一试{
createTempFile(名称为“.dat”);
}
捕获(IOE异常){
抛出新的未选中异常(“未能创建临时文件”,e);
}
},实际情况;
溪流=溪流,共(“1”、“2”、“3”);
if(并行)
{
stream=stream.parallel();
AccessControlContext ctx=AccessController.getContext();
actualAction=name->AccessController.doPrivileged(
(PrivilegedAction)(->{createFile.accept(name);返回null;},ctx);
}
else actualAction=createFile;
forEach流(实际流量);
}
第一行是
AccessControlContext ctx=AccessController.getContext()代码>语句它记录当前安全上下文,其中包括代码和当前调用方。(请记住,有效权限是所有调用方集合的交集)。通过将结果上下文对象ctx
提供给Consumer
中的doPrivileged
方法,换句话说,您正在重新建立上下文,PrivilegedAction
将具有与单线程场景中相同的权限。请提供您获得的异常的堆栈跟踪。还请添加您的SecurityManager
代码,以便我们可以准确地重现您的问题。这可能与使用公共fork-join池的并行流有关。它是如何工作的?此问题还影响存储在ThreadLocal
中的上下文,例如Spring的SecurityContext
,请参见例如“好答案”。也是