我正在为应用程序开发一个自定义appender,它会将日志消息写入应用程序级变量列表。因此,在应用程序中运行的所有线程都将访问此列表,以便同时向此列表添加消息(只写操作)。 还有一个单独的工作,也可以同时访问此列表,并每隔几毫秒批量删除25个项目。因此,列表上的添加和删除操作将同时执行。 现在,我的问题是,如果我可以使用java.util.ArrayList?我在某处看到,同时向ArrayList添加项目可能会出现问题。此外,当我们尝试从列表中删除25个项目而其他线程正在添加到列表中时,肯定存在问题。
如果我创建自己的List MYList实现,它只是创建一个新的remove25()方法并对此方法使用synchronize限定符,它会解决我的问题吗?或者我应该去更安全的选项,比如CopyOnWriteArrayList,这可能是非常糟糕的性能(??),因为日志操作需要同时由许多应用程序线程完成。请指教。
一次更新:由于我不需要随机访问,只需要顺序访问许多添加和删除操作,链接列表类型的实现会更好。
答案 0 :(得分:5)
我会使用像ArrayBlockingQueue这样的线程安全队列,它有一个drainTo方法,允许你一次提取多个值。
BlockingQueue<String> queue = new ArrayBlockingQueue<>(100000);
// to add
queue.add(log);
// to grab up to 25 elements
queue.drainTo(copy, 25);
答案 1 :(得分:3)
ArrayList的实现未同步。如果多个 线程同时访问一个ArrayList实例,并且至少有一个 线程在结构上修改列表,必须同步 外部。 (结构修改是添加或的任何操作 删除一个或多个元素,或显式调整后备数组的大小; 仅设置元素的值不是结构 修改。)
这通常通过同步一些来完成 自然封装列表的对象。如果不存在这样的对象, 列表应该是&#34;包装&#34;使用Collections.synchronizedList 方法。这最好在创建时完成,以防止意外 对列表的非同步访问:
List list = Collections.synchronizedList(new ArrayList(...));
此类的迭代器和listIterator方法返回的迭代器 快速失败:如果列表在之后的任何时间进行结构修改 除了通过迭代器自己以外的任何方式创建迭代器 删除或添加方法,迭代器将抛出一个 ConcurrentModificationException的。因此,面对并发 修改,迭代器快速而干净地失败,而不是 在不确定的时间冒着任意的,非确定性的行为 在将来。
请注意,无法保证迭代器的快速失败行为 一般来说,不可能做出任何硬性保证 存在不同步的并发修改。快速失败 迭代器抛出ConcurrentModificationException就是尽力而为 基础。因此,编写一个依赖的程序是错误的 关于它的正确性的这个例外:快速失败的行为 迭代器应该只用于检测错误。
(来自Java文档)
CopyOnWriteArrayList是ArrayList的线程安全变体,但是它创建了底层数组的新副本,从而覆盖了所有内容,而这似乎并不是您想要的。
您想要使用的是BlockingQueue
,它是线程安全的(并且不会处理空值);我建议您阅读更多about it here和Concurrent Collections
答案 2 :(得分:1)
您需要保护您的内存结构不受并发修改的影响。对于您的用例,CopyOnWriteArrayList
可能过于昂贵。
Alternativley您可能需要考虑以下事项:
app queue
的线程安全阻止add
takes
(或可能是drains
)来自app queue
的邮件,并将其添加到logger queue
logger queue
交互看起来像thisL
app --add--> app queue <--take-- logger queue <--take-- logger
或者你可以使用环形缓冲区作为log4j(https://logging.apache.org/log4j/2.x/manual/async.html)