繁体   English   中英

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

[英]Java Multithreading: threads shall access list

我正在使用五个线程运行,并且我有一个对象列表(我独立于线程进行初始化)。 列表中的对象使用 boolean 作为标志,所以我知道它们是否已经由另一个线程处理。 此外,我的线程的“ID”有一个 Integer(所以你知道哪个线程当前正在工作)。

问题:参与 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) {
            ....
        }
    }

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

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

实际上,您最好在性能方面和可读性方面按顺序执行代码。

假设

我假设

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

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

方法一:使用并行 Stream

如果您不必显式并行化代码,请考虑使用 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:如果您必须使用执行器显式并行化代码

我正在运行五个线程,

(最初由 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:如果您必须显式使用线程

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方法的线程数,并且每个线程都有一个唯一的threadID ,范围从0total_threads - 1 在线程之间分配迭代的其他方法也将如此可见,例如在线程之间动态分配迭代:

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将是原子 integer(即AtomicInteger task = new AtomicInteger(); )。

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


如果假设 1. 和 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());
            }
       }
 }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM