Java: 使用 ExecutorService 实现并发
Java: Using ExecutorService for concurrency
我想并行执行一些任务,所以我在 Java 中搜索多线程并找到了这个 class,ExecutorService。我尝试了简单的例子,但我不确定它是否是 运行 并行。
long startTime = System.currentTimeMillis();
List<Callable<String>> callables = new ArrayList<Callable<String>>();
ExecutorService executor = Executors.newCachedThreadPool();
callables.add(new Callable<String>() {
public String call() {
for (int i=0; i<100000; i++) {
System.out.println("i "+i);
}
return "Task 1";
}
}
);
callables.add(new Callable<String>() {
public String call() {
for (int j=0; j<100000; j++) {
System.out.println("j "+j);
}
return "Task 2";
}
}
);
callables.add(new Callable<String>() {
public String call() {
for (int k=0; k<100000; k++) {
System.out.println("k "+k);
}
return "Task 3";
}
}
);
try {
List<Future<String>> futureLst = executor.invokeAll(callables);
for (Future future : futureLst) {
try {
System.out.println(future.get());
} catch (ExecutionException e) {
e.printStackTrace();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
executor.shutdown();
System.out.println("Time Taken - "+ (System.currentTimeMillis() - startTime));
以上代码按预期工作正常并打印计数和 returns 字符串。上面程序执行的时间是“3229”毫秒。
我将上面的程序重写如下,
long startTime = System.currentTimeMillis();
for (int i=0; i<100000; i++) {
System.out.println("i "+i);
}
for (int j=0; j<100000; j++) {
System.out.println("j "+j);
}
for (int k=0; k<100000; k++) {
System.out.println("k "+k);
}
System.out.println("Time Taken - "+ (System.currentTimeMillis() - startTime));
这个程序花费的时间是“3104”毫秒,比并行执行编码的时间要短。
那么我在第一个程序中做错了什么吗?我的第一个程序 运行 任务是并行的吗,因为我看到没有线程花费的时间更少?
第一个是顺序的,第二个是并行的 -- 没关系。
看起来 运行 时间主要由 System.out.println
call 中缓慢的控制台写入操作组成(参见 Why is System.out.println so slow?)。
如果改为写入文件,您应该观察到不同的行为(和并行版本加速)。
您的代码是正确的。结果是因为计算太简单,运行太快了。大多数 运行 时间取决于控制台输出的速度,因此多线程的好处无法弥补设置和关闭线程池的开销。此外,try-catch 设置还增加了一个小的开销(非常小,但足以在这个简单的场景中混淆结果)。
--> 您可以尝试增加循环大小(提示:使用静态变量 MAX_LOOP 进行更快的测试),您会看到不同的结果。
您的任务主要是使用 System.out.println
调用(即 PrintStream.println
方法)写入标准输出。它最终调用 PrintStream.write
同步主体的方法。因此,您只是将时间浪费在创建线程和同步开销上。由于PrintStream
本质上是顺序的,不能并行输出,所以当单流写的时候,其他人只是等待。有些操作可以并行化,例如 for 循环和字符串与数字的连接,但它们比输出到标准输出要快得多。
要从并发中获得额外的性能,您应该避免使用共享顺序资源。在您的情况下,它是标准输出流。
我想并行执行一些任务,所以我在 Java 中搜索多线程并找到了这个 class,ExecutorService。我尝试了简单的例子,但我不确定它是否是 运行 并行。
long startTime = System.currentTimeMillis();
List<Callable<String>> callables = new ArrayList<Callable<String>>();
ExecutorService executor = Executors.newCachedThreadPool();
callables.add(new Callable<String>() {
public String call() {
for (int i=0; i<100000; i++) {
System.out.println("i "+i);
}
return "Task 1";
}
}
);
callables.add(new Callable<String>() {
public String call() {
for (int j=0; j<100000; j++) {
System.out.println("j "+j);
}
return "Task 2";
}
}
);
callables.add(new Callable<String>() {
public String call() {
for (int k=0; k<100000; k++) {
System.out.println("k "+k);
}
return "Task 3";
}
}
);
try {
List<Future<String>> futureLst = executor.invokeAll(callables);
for (Future future : futureLst) {
try {
System.out.println(future.get());
} catch (ExecutionException e) {
e.printStackTrace();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
executor.shutdown();
System.out.println("Time Taken - "+ (System.currentTimeMillis() - startTime));
以上代码按预期工作正常并打印计数和 returns 字符串。上面程序执行的时间是“3229”毫秒。
我将上面的程序重写如下,
long startTime = System.currentTimeMillis();
for (int i=0; i<100000; i++) {
System.out.println("i "+i);
}
for (int j=0; j<100000; j++) {
System.out.println("j "+j);
}
for (int k=0; k<100000; k++) {
System.out.println("k "+k);
}
System.out.println("Time Taken - "+ (System.currentTimeMillis() - startTime));
这个程序花费的时间是“3104”毫秒,比并行执行编码的时间要短。
那么我在第一个程序中做错了什么吗?我的第一个程序 运行 任务是并行的吗,因为我看到没有线程花费的时间更少?
第一个是顺序的,第二个是并行的 -- 没关系。
看起来 运行 时间主要由 System.out.println
call 中缓慢的控制台写入操作组成(参见 Why is System.out.println so slow?)。
如果改为写入文件,您应该观察到不同的行为(和并行版本加速)。
您的代码是正确的。结果是因为计算太简单,运行太快了。大多数 运行 时间取决于控制台输出的速度,因此多线程的好处无法弥补设置和关闭线程池的开销。此外,try-catch 设置还增加了一个小的开销(非常小,但足以在这个简单的场景中混淆结果)。 --> 您可以尝试增加循环大小(提示:使用静态变量 MAX_LOOP 进行更快的测试),您会看到不同的结果。
您的任务主要是使用 System.out.println
调用(即 PrintStream.println
方法)写入标准输出。它最终调用 PrintStream.write
同步主体的方法。因此,您只是将时间浪费在创建线程和同步开销上。由于PrintStream
本质上是顺序的,不能并行输出,所以当单流写的时候,其他人只是等待。有些操作可以并行化,例如 for 循环和字符串与数字的连接,但它们比输出到标准输出要快得多。
要从并发中获得额外的性能,您应该避免使用共享顺序资源。在您的情况下,它是标准输出流。