Java 保存集合的一部分,同时由其他线程添加新条目

Java 保存集合的一部分,同时由其他线程添加新条目,java,concurrency,Java,Concurrency,我有一个批处理作业,它异步发送http请求并将它们收集到结果列表中。这很好用 public File addLine(CsvLine line) throws IOException { lines.add(line); return this; } 在尝试优化时,我希望将行存储到持久位置 public File addLine(CsvLine line) throws IOException { lines.add(line); if (lines.size(

我有一个批处理作业,它异步发送http请求并将它们收集到结果列表中。这很好用

public File addLine(CsvLine line) throws IOException {
    lines.add(line);
    return this;
}
在尝试优化时,我希望将行存储到持久位置

public File addLine(CsvLine line) throws IOException {
    lines.add(line);
    if (lines.size() > uploadService.getPartSize()) {
        List<CsvLine> copy;
        copy = new ArrayList<>(lines);
        lines.removeAll(copy);
        uploadService.save(copy);
    }
    return this;
}
public File addLine(CsvLine行)引发IOException{
行。添加(行);
if(lines.size()>uploadService.getPartSize()){
清单副本;
复制=新阵列列表(行);
行。删除所有(副本);
上传服务。保存(复制);
}
归还这个;
}
多个线程仍在添加到lines集合,但在制作副本时,我确保只删除要保存的行(copy collection)。 所以这不起作用,我试着在这个部分添加synchronized关键字

public File addLine(CsvLine line) throws IOException {
    lines.add(line);
    if (lines.size() > uploadService.getPartSize()) {
        List<CsvLine> copy;
        synchronized (this) {
            copy = new ArrayList<>(lines);
            lines.removeAll(copy);
        }
        uploadService.save(copy);
    }
    return this;
}
public File addLine(CsvLine行)引发IOException{
行。添加(行);
if(lines.size()>uploadService.getPartSize()){
清单副本;
已同步(此){
复制=新阵列列表(行);
行。删除所有(副本);
}
上传服务。保存(复制);
}
归还这个;
}
但是没有成功。我假设ArrayList的构造函数不是线程保存,如果我没有弄错的话,这也是唯一需要同步的部分


有人能指出我做错了什么吗?

我创建了一个
双缓冲列表
对象来实现这一点

/**
 * Lock free - thread-safe.
 *
 * Write from many threads - read with fewer threads.
 *
 * Write items of type T.
 *
 * Read items of type List<T>.
 *
 * @author OldCurmudgeon
 * @param <T>
 */
public class DoubleBufferedList<T> {

    /**
     * Atomic reference so I can atomically swap it through.
     *
     * Mark = true means I am adding to it so momentarily unavailable for iteration.
     */
    private final AtomicMarkableReference<List<T>> list = new AtomicMarkableReference<>(newList(), false);

    // Factory method to create a new list - may be best to abstract this.
    protected List<T> newList() {
        return new ArrayList<>();
    }

    /**
     * Get and replace the current list.
     *
     * Used by readers.
     *
     * @return List<T> of a number (possibly 0) of items of type T.
     */
    public List<T> get() {
        // Atomically grab and replace the list with an empty one.
        List<T> empty = newList();
        List<T> it;
        // Replace an unmarked list with an empty one.
        if (!list.compareAndSet(it = list.getReference(), empty, false, false)) {
            // Failed to replace!
            // It is probably marked as being appended to but may have been replaced by another thread.
            // Return empty and come back again soon.
            return Collections.<T>emptyList();
        }
        // Successfull replaced an unmarked list with an empty list!
        return it;
    }

    /**
     * Grab and lock the list in preparation for append.
     *
     * Used by add.
     */
    private List<T> grab() {
        List<T> it;
        // We cannot fail so spin on get and mark.
        while (!list.compareAndSet(it = list.getReference(), it, false, true)) {
            // Spin on mark - waiting for another grabber to release (which it must).
        }
        return it;
    }

    /**
     * Release the grabbed list.
     *
     * Opposite of grab.
     */
    private void release(List<T> it) {
        // Unmark it - should this be a compareAndSet(it, it, true, false)?
        if (!list.attemptMark(it, false)) {
            // Should never fail because once marked it will not be replaced.
            throw new IllegalMonitorStateException("It changed while we were adding to it!");
        }
    }

    /**
     * Add an entry to the list.
     *
     * Used by writers.
     *
     * @param entry - The new entry to add.
     */
    public void add(T entry) {
        List<T> it = grab();
        try {
            // Successfully marked! Add my new entry.
            it.add(entry);
        } finally {
            // Always release after a grab.
            release(it);
        }
    }

    /**
     * Add many entries to the list.
     *
     * @param entries - The new entries to add.
     */
    public void add(List<T> entries) {
        List<T> it = grab();
        try {
            // Successfully marked! Add my new entries.
            it.addAll(entries);
        } finally {
            // Always release after a grab.
            release(it);
        }
    }

    /**
     * Add a number of entries.
     *
     * @param entries - The new entries to add.
     */
    @SafeVarargs
    public final void add(T... entries) {
        // Make a list of them.
        add(Arrays.<T>asList(entries));
    }

}

同步对
行的所有访问(且仅限于此):

public File addLine(CsvLine行)引发IOException{
列表副本=空;
同步(行){
行。添加(行);
if(lines.size()>uploadService.getPartSize()){
复制=新阵列列表(行);
行。清除();
}
}
如果(复制!=null){
上传服务。保存(复制);
}
归还这个;
}

什么是行?现在如何处理同步?我现在处理它的方式是我那里的代码,我可以同步整个方法,但感觉不对。Lines是我存储结果的集合,我希望每x次刷新一行以控制内存消耗。结果包含10mil记录,甚至更多。你说这个方法被几个线程访问,但我在这里没有看到任何同步。为什么同步整个方法对您来说是错误的?我恐怕要回答我自己的问题,行是一个哈希集,而不是concurrenthashset,我的代码可能会抛出并发修改错误并吞没消息,因此代码似乎无法工作。我将对此进行更多的测试。你没有找到简单的解决方案,是吗?@AdamSkywalker-我找到了简单的解决方案,但如果它们不符合要求,我会放弃它们。但这里的要求是什么?我们不知道。使整个方法同步可能是一个解决方案。@jelle-在什么意义上错了?代码行太多?我删除了200多行测试代码以适应这里。
final String BYE = "BYE!";

public void test() throws InterruptedException {
    final DoubleBufferedList<String> l = new DoubleBufferedList<>();
    // Producer.
    Thread p = new Thread(new Runnable() {
        @Override
        public void run() {
            for (int i = 0; i < 100000; i++) {
                // Pump the queue.
                l.add("" + i);
            }
            l.add(BYE);
        }
    });
    // Consumer.
    Thread c = new Thread(new Runnable() {
        @Override
        public void run() {
            boolean done = false;
            do {
                List<String> got = l.get();
                System.out.println("Got: " + got);
                for (String s : got) {
                    if (s.equals(BYE)) {
                        done = true;
                    }
                }
            } while (!done);
        }
    });
    // Fire it up.
    p.start();
    c.start();
    // Wait for them to finish.
    p.join();
    c.join();
}
public File addLine(CsvLine line) throws IOException {
    List<CsvLine> copy = null;
    synchronized (lines) {
        lines.add(line);
        if (lines.size() > uploadService.getPartSize()) {
            copy = new ArrayList<>(lines);
            lines.clear();
        }
    }
    if (copy != null) {
        uploadService.save(copy);
    }
    return this;
}