6

说,我有一个数据对象:

class ValueRef { double value; }

每个数据对象都存储在主集合中:

Collection<ValueRef> masterList = ...;

我还有一个作业集合,其中每个作业都有一个本地数据对象集合(其中每个数据对象也出现在 中masterList):

class Job implements Runnable { 
     Collection<ValueRef> neededValues = ...; 
     void run() {
         double sum = 0;
         for (ValueRef x: neededValues) sum += x;
         System.out.println(sum);
     } 
}

用例:

  1. for (ValueRef x: masterList) { x.value = Math.random(); }

  2. 用一些作业填充作业队列。

  3. 唤醒一个线程池

  4. 等到每个作业都被评估过

注意:在工作评估过程中,所有的值都是不变的。然而,线程可能在过去评估过作业,并保留缓存的值。

问题: 确保每个线程看到最新值所需的最小同步量是多少?

我从监视器/锁的角度理解同步,我不理解从缓存/刷新的角度同步(即,在同步块的进入/退出时内存模型保证了什么)。

对我来说,感觉就像我需要在更新值的线程中同步一次以将新值提交到主内存,并在每个工作线程中同步一次,以刷新缓存以便读取新值。但我不确定如何最好地做到这一点。

我的方法:创建一个全局监视器:static Object guard = new Object(); 然后,同步guard,同时更新主列表。最后,在启动线程池之前,为池中的每个线程一次,guard在一个空块中同步。

这真的会导致该线程读取的任何值被完全刷新吗?或者只是在同步块内触及的值?在这种情况下,也许我应该在循环中读取每个值一次,而不是一个空块?

谢谢你的时间。


编辑:我认为我的问题归结为,一旦我退出同步块,每次第一次读取(在那之后)都会进入主内存吗?不管我同步了什么?

4

3 回答 3

3

线程池的线程过去评估过一些作业并不重要。

JavadocExecutor说:

内存一致性效果:在将 Runnable 对象提交给 Executor 之前,线程中的操作发生在其执行开始之前,可能在另一个线程中。

因此,只要您使用标准线程池实现并在提交作业之前更改数据,您就不必担心内存可见性影响。

于 2012-06-26T20:47:44.493 回答
2

你的计划听起来就足够了。这取决于您计划如何“唤醒线程池”。

Java 内存模型规定,线程在进入synchronized块之前执行的所有写入对于随后在该锁上同步的线程都是可见的。

因此,如果您确定在更新主列表期间工作线程在wait()调用中被阻塞(必须在synchronized块内),当它们唤醒并变为可运行时,主线程所做的修改将对这些线程。

但是,我鼓励您在java.util.concurrent包中应用更高级别的并发实用程序。这些将比您自己的解决方案更强大,并且是在深入研究之前学习并发性的好地方。


澄清一下:如果不使用同步块来检查工作线程是否有要实现的任务,几乎不可能控制工作线程。因此,控制器线程对作业所做的任何更改都发生在工作线程唤醒之前。您需要一个synchronized块,或者至少一个volatile变量来充当内存屏障;但是,我想不出您将如何使用其中之一来创建线程池。

作为使用java.util.concurrency包的优点的示例,请考虑以下情况:您可以使用其中包含调用的synchronized块,使用变量的忙等待循环。由于线程之间上下文切换的开销,繁忙的等待实际上可以在某些条件下执行得更好——这并不一定是乍一看可能会假设的可怕想法。wait()volatile

如果您使用 Concurrency 实用程序(在这种情况下,可能是ExecutorService),则可以为您做出适合您特定情况的最佳选择,同时考虑环境、任务的性质以及给定时间其他线程的需求。自己实现这种优化水平是很多不必要的工作。

于 2012-06-26T20:52:40.843 回答
1

在发布对集合的引用后,为什么不制作Collection<ValueRef>ValueRef不可变的,或者至少不修改集合中的值。然后你就不用担心同步了。

那就是当您想要更改集合的值,创建一个新集合并将新值放入其中时。一旦设置了值,就会传递集合引用新的作业对象。

不这样做的唯一原因是如果集合的大小太大以至于它几乎无法放入内存并且您不能拥有两个副本,或者集合的交换会给垃圾收集器带来太多的工作(证明在将可变数据结构用于线程代码之前,其中之一是一个问题)。

于 2012-06-26T21:19:36.210 回答