我正在 Java 中试验多线程,更具体地说,是线程池。作为测试,我编写了一个应用程序,它使用多线程来简单地更改图像的颜色以提高速度。但是,出于某种我不知道的原因,根据我设置此测试的方式,我得到了损坏的结果。下面我将描述测试应用程序如何与完整的源代码一起工作。
非常欢迎任何帮助!谢谢!
测试应用
我有一个用深蓝色初始化的 400x300 像素图像缓冲区,如下所示:
程序必须用红色完全填满它。
虽然我可以简单地遍历所有像素,依次用红色着色每个像素,但为了提高性能,我决定利用并行性。因此,我决定用单独的线程填充每个图像行。由于行数(300 行)远大于可用 CPU 内核的数量,我创建了一个线程池(包含 4 个线程),它将消耗 300 个任务(每个任务负责填充一行)。
程序组织如下:
- RGB 类:将像素颜色保存在 double 的 3 元组中。
- RenderTask 类:用红色填充图像缓冲区的给定行。
- 渲染器类:
- 创建图像缓冲区。
- 使用“newFixedThreadPool”创建线程池。
- 创建 300 个任务供线程池使用。
- 完成线程池服务。
- 将图像缓冲区写入 PPM 文件。
您可以在下面找到完整的源代码(我将此代码称为版本 1):
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.io.*;
class RGB {
RGB() {}
RGB(double r, double g, double b) {
this.r = r;
this.g = g;
this.b = b;
}
double r;
double g;
double b;
}
class RenderTask implements Runnable {
RenderTask(RGB[][] image_buffer, int row_width, int current_row) {
this.image_buffer = image_buffer;
this.row_width = row_width;
this.current_row = current_row;
}
@Override
public void run() {
for(int column = 0; column < row_width; ++column) {
image_buffer[current_row][column] = new RGB(1.0, 0.0, 0.0);
}
}
RGB[][] image_buffer;
int row_width;
int current_row;
}
public class Renderer {
public static void main(String[] str) {
int image_width = 400;
int image_height = 300;
// Creates a 400x300 pixel image buffer, where each pixel is RGB triple of doubles,
// and initializes the image buffer with a dark blue color.
RGB[][] image_buffer = new RGB[image_height][image_width];
for(int row = 0; row < image_height; ++row)
for(int column = 0; column < image_width; ++column)
image_buffer[row][column] = new RGB(0.0, 0.0, 0.2); // dark blue
// Creates a threadpool containing four threads
ExecutorService executor_service = Executors.newFixedThreadPool(4);
// Creates 300 tasks to be consumed by the threadpool:
// Each task will be in charge of filling one line of the image buffer.
for(int row = 0; row < image_height; ++row)
executor_service.submit(new RenderTask(image_buffer, image_width, row));
executor_service.shutdown();
// Saves the image buffer to a PPM file in ASCII format
try (FileWriter fwriter = new FileWriter("image.ppm");
BufferedWriter bwriter = new BufferedWriter(fwriter)) {
bwriter.write("P3\n" + image_width + " " + image_height + "\n" + 255 + "\n");
for(int row = 0; row < image_height; ++row)
for(int column = 0; column < image_width; ++column) {
int r = (int) (image_buffer[row][column].r * 255.0);
int g = (int) (image_buffer[row][column].g * 255.0);
int b = (int) (image_buffer[row][column].b * 255.0);
bwriter.write(r + " " + g + " " + b + " ");
}
} catch (IOException e) {
System.err.format("IOException: %s%n", e);
}
}
}
使用该代码似乎一切正常,我得到了预期的红色图像缓冲区,如下所示:
问题
但是,如果我修改 RenderTask.run() 方法,使其按顺序重复多次重复设置同一缓冲区位置的颜色,如下所示(我将此称为版本 2强>):
@Override
public void run() {
for(int column = 0; column < row_width; ++column) {
for(int s = 0; s < 256; ++s) {
image_buffer[current_row][column] = new RGB(1.0, 0.0, 0.0);
}
}
}
然后我得到以下损坏的图像缓冲区:
其实我每次运行程序的结果都不一样,但总是损坏。
据我了解,没有两个线程同时写入同一个内存位置,因此似乎看不到竞争条件。
即使在我认为不会发生的“虚假共享”的情况下,我也希望只有较低的性能,而不是损坏的结果。
因此,即使有冗余分配,我也希望得到正确的结果(即完全红色的图像缓冲区)。
所以,我的问题是:如果与版本 1 的唯一区别是赋值操作在线程范围内冗余执行,为什么程序版本 2 会发生这种情况?
是否会出现某些线程在完成之前被销毁的情况?它会是 JVM 中的错误吗? 还是我错过了一些微不足道的事情? (最强假设:)
谢谢大家!!
最佳答案
ExecutorService.shutdown() 不会等待它拥有的任务终止,它只会停止接受新任务。
调用关闭后,如果您想等待它完成,您应该在执行程序服务上调用 awaitTermination。
所以发生的情况是,当您开始将图像写入文件时,所有任务尚未执行完毕。
关于java - 多线程(即基于线程池的)Java 应用程序的损坏结果,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58263058/