定期数据库批量插入的Java并发
场景:每秒调用一个线程数千次,以便对同一个表进行插入,并且当前正在逐个执行这些操作。
目标:定期进行批量插入以提高性能。
当线程的saveItem
方法被调用时,尝试使用TimerTask来将保存的对象添加到列表中,然后每隔2秒左右将它们组合成批量插入。
首先想到的是有两个列表,称之为toSave
和toSaveBackup
。 当调用线程的saveItem
方法来保存它时,它将被添加到toSave列表中,但是一旦TimerTask启动并需要将所有内容保存到数据库,它就会将AtomicBoolean标志saveInProgress
设置为true。 saveItem
检查此标志,如果saveInProgress
为true,它将添加到toSaveBackup而不是toSave。 批量保存完成后,toSaveBackup中的所有项目都将移动到toSave列表,可能是列表上的同步块。
这是一种合理的方法吗? 还是有更好的最佳做法? 我的谷歌搜索技能让我失望,所以欢迎任何帮助。
其他信息:
- 所有这些插入都在同一个表中
- 插入是通过接收MQTT消息来驱动的,因此在此之前我无法将它们组合在一起
更新:对CKing以下答案的调整达到了预期的方法:TimerTask每100毫秒运行一次并检查saveQueue
的大小以及自批量保存以来的时间。 如果这些值中的任何一个超过配置的限制(每2秒或每1000条记录保存等),我们就会保存。 LinkedBlockingQueue用于简化同步。
再次感谢大家的帮助!
看起来您的主要目标是等待预定义的时间,然后触发插入。 当插入正在进行时,您不必插入其他插入请求,直到插入完成。 插入完成后,您希望再次为下一个插入请求重复相同的过程。
考虑到上述理解,我会提出以下解决方案。 您无需拥有两个单独的列表即可实现目标。 另请注意,为了解释,我提出了一个老式的解决方案。 我将介绍一些您在解释结束时可以使用的其他API。 开始 :
- 定义一个每N秒运行一次的
Timer
和TimerTask
。 - 定义一个
ArrayList
,用于排队发送到saveItem
方法的插入请求。 -
saveItem
方法可以在此ArrayList
周围定义一个sycnrhonized
块。 您可以在调用saveItem
时将项添加到此synchronized
块中的ArrayList
。 - 在等式的另一方面,
TimerTask
应该在其run
方法中的同一ArrayList
上具有synchronized
块。 它应该将给定时刻ArrayList
中存在的所有记录插入到数据库中。 插入完成后,TimerTask
应clear
ArrayList
并最终退出synchronized
块。
您将不再需要显式监视插入是否正在进行中,或者在插入正在进行时创建ArrayList
的副本。 在这种情况下,您的ArrayList
将成为共享资源。
如果您还希望size
成为继续插入的决定性因素,则可以执行以下操作:
- 在
TimerTask
定义一个名为waitAttempts
的int。 此字段指示如果list
的size
不够大,TimerTask
应该不执行任何操作的连续唤醒次数。 - 每次
TimerTask
唤醒时,它都可以执行if(waitAttempts%3==0 || list.size > 10) { insert data } else { increment waitAttempts and do nothing. Exit the synchronized block and the run method }
if(waitAttempts%3==0 || list.size > 10) { insert data } else { increment waitAttempts and do nothing. Exit the synchronized block and the run method }
。 您可以将3
和10
更改为适合您的吞吐量要求的任何数字。
注意使用内在锁定作为解释方法的手段。 人们总是可以采用这种方法并使用现代结构(如BlockingQueue
来实现它,这将消除在ArrayList
上手动synchronize
的需要。 我还建议使用Executors.newSingleThreadScheduledExecutor()
而不是TimerTask
因为它确保在任何给定时间只有一个线程运行,并且线程不会重叠。 此外, waitAttempts
的逻辑是指示性的,需要调整才能正常工作。