避免使用多个线程的ArrayList并发

时间:2017-10-05 19:09:12

标签: java multithreading arraylist

我有一个ArrayList对象:

    List<Sample> dataList = new ArrayList<Sample>();

这有一个Sample对象列表。 Sample包含一个长时间戳和一个双值原语。

我有一个程序可以通过多个线程对这些进行操作。我有一个线程将1 /小时修剪数据。修剪大约需要2分钟才能完成(低端嵌入式系统和大量数据)。它调用以下函数来执行此操作:

 public synchronized void prune(long timestamp)
    {
        Iterator<Sample> it = dataList.listIterator();

        while (it.next().getTimestamp() < timestamp)
        {
            it.remove();
        }       

    }
}

我还通过另一个线程以1 /秒的速度将动态数据更新到此阵列。根据添加的数据,它可以调用以下两个函数之一:

  public synchronized void addPointData(ArrayList<Sample> a)
    {

            a.addAll(dataList);
            dataList = a;

    }

    public synchronized void addPointData(Sample a)
    {

            dataList.add(a);
            if (dataList.size() > 0 && pruneLock == 0 && dataList.get(0).getTimestamp() < (System.currentTimeMillis() - 90000000L) * 1000000)
            {
                dataList.remove(0);
                startTimestamp = dataList.get(0).getTimestamp();
            }       
    }

到目前为止,运行它,我没有任何并发​​异常,我不相信我有任何丢失的数据。如果修剪器使添加功能等待它,我担心丢失的数据。谁能解释为什么我没有例外?我应该以不同的方式做这件事吗?

2 个答案:

答案 0 :(得分:1)

因为没有其他人回答过......好评,以及关于如何更有效地存储数据的好点。但是,除此之外,如果您包含的操作是仅修改列表的 操作,则您可以使用正确的代码来防止并发修改异常或数据丢失。所有修改的操作都是同步的。您将遇到阻止情况,但您无法进行并发修改。

使添加功能等待剪枝完成正是应该发生的事情。只要没有其他原因,添加功能就无法等待,这很好。

正如评论者指出的那样,有更快的方法可以减少整体等待时间。鉴于您总是按时间删除,如果您知道按时间顺序添加内容,则可以显着优化该过程。如果您按时间排序并要求,则肯定有更好的选择(或者如果您可以选择对插入进行排序)。 Java 8流的一些用途可以并行化并提供一些不同的处理选项。

但简短的回答是,您已经锁定了防止问题所需的地方。

答案 1 :(得分:1)

您已将synchronize放在触及ArrayList的所有内容上,这样可确保您不会遇到并发问题。

另一方面,你的表现会很糟糕。每当修剪发生时,需要数据列表的所有内容都将停止。

您有两个重要的性能问题。首先是remove ArrayList的效率非常低。每次从中间移除某些东西时,它必须将其上方的所有东西随机移动以填补空白。这甚至可以容忍的唯一原因是它使用System.arrayCopy这是一个低级超优化呼叫并且很快。但是如果你做了很多删除,那么每次删除都会让你失去作用。

有一点不清楚,您的样品是否已分拣。如果它们是有序的并且您可以确定需要修剪的起点和终点,则应使用removeRange一次性删除块。

如果您的列表已排序,并且您要从前面删除,则最好使用ArrayDeque,因为这样可以有效地支持从正面和背面移除。

假设情况并非如此,并且时间戳在数组中随机分布,那么填充空白可能会更快,因为您可以使用以下内容:

j = 0;
for (int i = 0; i < dataList.size(); i++) {
    Sample s = dataList.get(i);
    if (s.getTimestamp() >= timestamp) {
        dataList.set(j++, s);
    }
}
removeRange(j, dataList.size());

我还没有测试过,但也许你明白了。

或者也许有一些Java 8的聪明才能更优雅地做同样的事情。

但是,当修剪发生时,这仍然会锁定整个数据结构,因此您可以考虑在较小的块中进行修剪。这样可以在较短的时间内同步您的数据,从而缩短延迟时间。