【问题标题】:Java fixed thread pool race condition?Java固定线程池竞争条件?
【发布时间】:2020-10-08 01:26:07
【问题描述】:

考虑以下代码:

private static final Object LOCK = new Object();
private static final ExecutorService executorService = Executors.newFixedThreadPool(10); // Also used for a few other tasks.

public static void save(Object o) {
    String s = serialize(o);
    executorService.submit(() -> {
        // Do we have a race condition here?
        synchronized (LOCK) {
            saveToFile(s, new File("test.txt")); // overwrites the file
        }
    });
}

public static void main(String[] args) {
    save(args[0]);
    save(args[1]);
}

save(Object o) 仅在主线程上调用。我知道线程池按顺序处理内部队列中提交的任务,但理论上会不会发生,在达到synchronized (LOCK) 之前存在竞争条件并且文件输出为args[0]

如果是,如何避免这种情况?我知道单线程执行器肯定会解决这个问题,但如果可能的话我想使用这个线程池。

编辑:我认为一种可能的方法是使用Queue

private static final Queue<String> queue = new ConcurrentLinkedQueue<>();

public static void save(Object o) {
    queue.add(serialize(o));
    executorService.submit(() -> {
        synchronized (LOCK) {
            saveToFile(queue.remove(), new File("test.txt"));
        }
    });
}

【问题讨论】:

  • " 在达到synchronized (LOCK) 之前存在竞争条件" --> 是的。 " 文件输出是args[0]" --> 并非总是如此,这取决于比赛的结果。但是,你想避免什么?
  • @boobalan 竞争条件。输出应保证为args[1]
  • 如果args[1] 是你想要的输出,你为什么叫args[0]?或者你需要输出args[1],只是因为它是最后一个提交的?
  • 我想我找到了一种方法,但是我想知道是否有更好的解决方案。
  • 您可以创建一个锁、一个条件和一个通用计数器,每次程序都会等待最后一个完成。

标签: java multithreading thread-safety race-condition synchronized


【解决方案1】:

在像您这样的生产者/消费者模式中,您通常通过a blocking queue 在(多个)生产者和(单个)消费者之间转移任务。有一个消费者这一事实确保了执行的顺序。

所以在伪代码中应该是这样的:

val queue = ArrayBlockingQueue() //could be another BlockingQueue
val executor = ...

main {
  executor.submit(consumer)
  queue.put(args[0])
  queue.put(args[1])
}

consumer() {
  try {
    while (true) {
      val file = queue.take() //blocks until a file is added to the queue
      save(file)
    }
  } catch (InterruptedException e) {
    Thread.currentThread(interrupt()) //restore the interrupt flag
    //do nothing, just exit
  }
}

如果你添加更多的消费者,你不能保证文件会被按顺序处理。但是您可以根据需要添加任意数量的生产者(即从不同的线程添加到队列中)。

【讨论】:

  • 我建议将您的方法名称更改为 consumer(),因为该方法正在消耗而不是产生。
  • 很有趣,但这样做会一直“占用”池中的一个线程。不完全是我要找的。但我喜欢这个主意。谢谢。
  • 什么是“占用一个线程”?线程要么正在做一些工作,保存文件,要么处于等待状态(不使用任何 CPU 资源)。
  • 我知道它在等待时没有使用 CPU。但是池有固定数量的线程。然后其中一个不再可用于其他任务。
  • 为什么不让消费者获取一个元素,而不是循环,并在元素被放入队列时多次提交消费者?这样可以避免阻塞线程,并且仍然保证按队列顺序写入。
猜你喜欢
  • 1970-01-01
  • 2016-10-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-04
  • 1970-01-01
  • 2010-11-21
相关资源
最近更新 更多