如何使用 ExecutorService 在多个线程中正确地将元素添加到 ConcurrentHashMap
How to properly add elements to ConcurrentHashMap in multiple threads using ExecutorService
我正在尝试使用多个线程将图像从某个文件夹加载到 ConcurrentHashMap 以节省时间。不幸的是,一些线程 'getting stuck' 在尝试加载图像并将其放入我的地图时。结果,当调用 shutdown() 程序时,即使一些线程没有执行它们的任务,程序也会走得更远。当我将 ExecutorService 线程 pule 设置为 1 时,一切正常,但我浪费了很多时间等待加载所有图像。在我看来,存在一些竞争问题,但据我所知,ConcurrentHashMap 对于多线程操作是安全的。我仍然是初学者,所以请让我了解问题出在哪里以及我做得不好。
这是代码:
public abstract class ImageContainer {
private final static Map<String, BufferedImage> imageMap = loadImages();
private static long loadingTime;
public static Map<String, BufferedImage> loadImages() {
loadingTime = System.currentTimeMillis();
ConcurrentHashMap<String, BufferedImage> imageMap = new ConcurrentHashMap<>();
ExecutorService es = Executors.newFixedThreadPool(5);
File imageDirectory = new File("Images/");
if (!imageDirectory.isDirectory()) {
System.out.println("Image directory error");
}
File[] files = imageDirectory.listFiles();
if (files != null) {
for (File file : files) {
if (file.isFile()) {
es.submit(new Runnable(){
@Override
public void run() {
try{
if(file.getAbsolutePath().contains(".jpg")) {
imageMap.put(file.getName().replace(".jpg",""),ImageIO.read(file));
}
else if (file.getAbsolutePath().contains(".png")) {
imageMap.put(file.getName().replace(".png",""),ImageIO.read(file));
}
}
catch (IOException e)
{
System.out.println("Cannot load image");
}
}
});
}
}
}
else
{
System.out.println("Image folder empty!");
}
es.shutdown();
try {
if(!es.awaitTermination(5L, TimeUnit.SECONDS)) {
System.out.println("Images did not load successfully!");
es.shutdownNow();
}
loadingTime = System.currentTimeMillis() - loadingTime;
}
catch(InterruptedException e) {
System.out.println("Loading images interrupted!");
}
System.out.println(imageMap.size());
return imageMap;
}
};
问题很可能与 ConcurrentHashMap
无关。每次,您 put
映射中的某些内容,其他线程将无法同时 put
。所以也许,一些线程将不得不等到另一个线程完成put
,但这不会导致任何竞争条件。
我在我的机器上执行了你的代码,一切正常。 (没有错误消息,打印加载图像的数量)。也许你的电脑加载图片的速度没有我的快,因此awaitTermination
超时。
据我所知,我不知道您的方法(使用多线程加载图像)是否是个好主意。您的硬盘(或 SSD)将成为瓶颈,您的线程将最终等待硬盘(声明 ImageIO.read
)。
此外,启动一个执行程序服务(分别启动新线程)并不是很便宜,所以也许你最好不要多线程。特别是因为您只需要加载一次图像(之后,它们被缓存在地图中),所以加速可能永远不会显着。我会考虑按顺序加载图像。
ImageIO 非常慢并且 I/O 密集,因此在典型的 PC 上添加许多线程通常无济于事。您确定不需要为 awaitTermination 超时添加大量数字吗?
另一种选择是对线程池使用长度有限的 LinkBlockingQueue,这样当消费者变慢时,您的主应用程序线程就会变慢。这意味着最后 5L 秒的时间延迟对于允许正在进行的呼叫结束是现实的。
请参阅 JDK newFixedThreadPool(n) 的源代码,在 LinkedBlockingQueue()
的构造函数中尝试 qSize = 比如说 2 或 3 个 nthreads
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
我正在尝试使用多个线程将图像从某个文件夹加载到 ConcurrentHashMap 以节省时间。不幸的是,一些线程 'getting stuck' 在尝试加载图像并将其放入我的地图时。结果,当调用 shutdown() 程序时,即使一些线程没有执行它们的任务,程序也会走得更远。当我将 ExecutorService 线程 pule 设置为 1 时,一切正常,但我浪费了很多时间等待加载所有图像。在我看来,存在一些竞争问题,但据我所知,ConcurrentHashMap 对于多线程操作是安全的。我仍然是初学者,所以请让我了解问题出在哪里以及我做得不好。 这是代码:
public abstract class ImageContainer {
private final static Map<String, BufferedImage> imageMap = loadImages();
private static long loadingTime;
public static Map<String, BufferedImage> loadImages() {
loadingTime = System.currentTimeMillis();
ConcurrentHashMap<String, BufferedImage> imageMap = new ConcurrentHashMap<>();
ExecutorService es = Executors.newFixedThreadPool(5);
File imageDirectory = new File("Images/");
if (!imageDirectory.isDirectory()) {
System.out.println("Image directory error");
}
File[] files = imageDirectory.listFiles();
if (files != null) {
for (File file : files) {
if (file.isFile()) {
es.submit(new Runnable(){
@Override
public void run() {
try{
if(file.getAbsolutePath().contains(".jpg")) {
imageMap.put(file.getName().replace(".jpg",""),ImageIO.read(file));
}
else if (file.getAbsolutePath().contains(".png")) {
imageMap.put(file.getName().replace(".png",""),ImageIO.read(file));
}
}
catch (IOException e)
{
System.out.println("Cannot load image");
}
}
});
}
}
}
else
{
System.out.println("Image folder empty!");
}
es.shutdown();
try {
if(!es.awaitTermination(5L, TimeUnit.SECONDS)) {
System.out.println("Images did not load successfully!");
es.shutdownNow();
}
loadingTime = System.currentTimeMillis() - loadingTime;
}
catch(InterruptedException e) {
System.out.println("Loading images interrupted!");
}
System.out.println(imageMap.size());
return imageMap;
}
};
问题很可能与 ConcurrentHashMap
无关。每次,您 put
映射中的某些内容,其他线程将无法同时 put
。所以也许,一些线程将不得不等到另一个线程完成put
,但这不会导致任何竞争条件。
我在我的机器上执行了你的代码,一切正常。 (没有错误消息,打印加载图像的数量)。也许你的电脑加载图片的速度没有我的快,因此awaitTermination
超时。
据我所知,我不知道您的方法(使用多线程加载图像)是否是个好主意。您的硬盘(或 SSD)将成为瓶颈,您的线程将最终等待硬盘(声明 ImageIO.read
)。
此外,启动一个执行程序服务(分别启动新线程)并不是很便宜,所以也许你最好不要多线程。特别是因为您只需要加载一次图像(之后,它们被缓存在地图中),所以加速可能永远不会显着。我会考虑按顺序加载图像。
ImageIO 非常慢并且 I/O 密集,因此在典型的 PC 上添加许多线程通常无济于事。您确定不需要为 awaitTermination 超时添加大量数字吗?
另一种选择是对线程池使用长度有限的 LinkBlockingQueue,这样当消费者变慢时,您的主应用程序线程就会变慢。这意味着最后 5L 秒的时间延迟对于允许正在进行的呼叫结束是现实的。
请参阅 JDK newFixedThreadPool(n) 的源代码,在 LinkedBlockingQueue()
的构造函数中尝试 qSize = 比如说 2 或 3 个 nthreadspublic static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}