Java多线程:线程应该访问列表



我正在运行五个线程,并且我有一个对象列表(我独立于线程初始化)。列表中的对象使用布尔值作为标志,因此我知道它们是否已经被另一个线程处理过。另外,我的线程有一个整数作为它的"id"。(这样你就知道哪个线程正在工作)。

问题:第一个处理for循环的线程将处理列表中的所有对象,但我希望线程交替进行。我做错了什么?

run()方法看起来像这样:

void run() {
for (int i = 0; i < list.size(); i++) {
ListObject currentObject = list.get(i);
synchronized (currentObject) {
if (currentObject.getHandled == false) {
currentObject.setHandled(true);
System.out.println("Object is handled by " + this.getID());
} else {
continue;
}
}
}
}

TL;DR显式或隐式地在线程之间划分列表;如果真的需要,还可以同步;

问题:第一个处理for循环的线程将处理列表中的所有对象,但我希望线程交替进行。我做错了什么?

整个代码块

for (int i = 0; i < list.size(); i++) {
ListObject currentObject = list.get(i);
synchronized (currentObject) {
....
}
}

基本上是顺序执行的,因为每个线程在每次迭代中使用ObjectcurrentObject隐式锁同步。所有五个线程都进入run方法,但是其中一个线程首先进入synchronized (currentObject),所有其他线程将依次等待第一个线程释放currentObject隐式锁。当线程完成后移动到下一个迭代,而剩余的线程仍在前一个迭代中。因此,进入synchronized (currentObject)的第一个线程将有一个开始,并且将比前面的线程领先一步,并且可能会计算所有剩余的迭代。结果:

第一个处理for循环的线程将处理所有对象,

在性能和可读性方面,顺序执行代码会更好。

我假设

  1. 当这些线程遍历列表时,存储在列表中的对象没有被其他地方访问;
  2. 列表不包含对同一对象的多个引用;

我建议不是每个线程遍历整个列表并在每次迭代中同步——这是非常不执行的,实际上破坏了并行性——每个线程将计算列表的不同块(例如。,在线程之间划分循环的的迭代)。例如:

方法1:使用并行流

如果你不需要显式地并行化你的代码,那么考虑使用ParallelStream:

list.parallelStream().forEach(this::setHandled);

private void setHandled(ListObject currentObject) {
if (!currentObject.getHandled) {
currentObject.setHandled(true);
System.out.println("Object is handled by " + this.getID());
}
}

方法2:如果必须使用executor显式并行化代码

我正在运行五个线程,

(如ernest_k所示)

ExecutorService ex = Executors.newFixedThreadPool(5);
for (ListObject l : list)
ex.submit(() -> setHandled(l));
...
private void setHandled(ListObject currentObject) {
if (!currentObject.getHandled) {
currentObject.setHandled(true);
System.out.println("Object is handled by " + this.getID());
}
}

方法3:如果你必须显式地使用Threads

void run() {
for (int i = threadID; i < list.size(); i += total_threads) {
ListObject currentObject = list.get(i);
if (currentObject.getHandled == false) {
currentObject.setHandled(true);
System.out.println("Object is handled by " + this.getID());
}
}
}

在这种方法中,我将for循环的迭代以轮询方式在线程之间分开,假设total_threads是计算run方法的线程数,并且每个线程将具有从0total_threads - 1的唯一threadID。在线程之间分配迭代的其他方法也是可见的,例如在线程之间动态分配迭代:

void run() {
for (int i = task.getAndIncrement(); i < list.size(); i = task.getAndIncrement();) {
ListObject currentObject = list.get(i);
if (currentObject.getHandled == false) {
currentObject.setHandled(true);
System.out.println("Object is handled by " + this.getID());
}
}
}

其中task是一个原子整数(即AtomicInteger task = new AtomicInteger();)。

所有方法的思想都是一样的将列表的不同块分配给线程,以便这些线程可以相互独立地执行这些块。


和2。你仍然可以应用前面提到的在线程之间分割迭代的逻辑,但是你需要添加同步,在我的例子中,到下面的代码块:

private void setHandled(ListObject currentObject) {
if (!currentObject.getHandled) {
currentObject.setHandled(true);
System.out.println("Object is handled by " + this.getID());
}
}

可以将currentObject字段转换为AtomicBoolean,如下所示:

private void setHandled(ListObject currentObject) {
if (currentObject.getHandled.compareAndSet(false, true)) {
System.out.println("Object is handled by " + this.getID());
}
}
private void setHandled(ListObject currentObject) {
synchronized (currentObject) {
if (!currentObject.getHandled) {
currentObject.setHandled(true);
System.out.println("Object is handled by " + this.getID());
}
}
}