我正在使用SpringMVC
,并且我有一个类AService
,它充当缓冲区来存储String
的列表,在列表大小达到之后1000 ,将所有查询写入数据库。
@Service
class AService {
List<String> list;
public void addAndInsert(String query) {
list.add(query);
if(list.size() >= 1000) {
writeIntoDatabase(list);
list.clear();
}
}
}
当只有一个线程时,这将正常工作。但是我们知道可以从不同的用户调用查询(当然是 MultiThread ),所以我怎样才能保证这一点正常工作:
当查询达到1000时,我想使用另一个线程来写入数据库,因为这个过程可能很长,我不希望用户等待与之无关的东西有查询。
查询可以不丢失或重复。
有谁能告诉我如何处理这种情况,我应该使用List
类的实现?谢谢!
答案 0 :(得分:5)
我的答案分为两部分:
为了完整起见,我还要强调,如果您的JVM崩溃,您可以放弃查询。您声明查询不会丢失,但是在一分钟内所有内容都被保存在内存中。我认为你对此没问题。
同步添加到列表
虽然系统本身可以是多线程的,但Spring只会创建@Service
类的单例,这意味着所有线程都访问同一个实例。因此,我们可以使用基本的Java功能轻松地同步访问该实例的成员变量。
JDK确实提供了一些开箱即用的基本同步List实现。例如,请查看Collections.synchronizedList()或CopyOnWriteArrayList。
这些实现通常为列表上的单个操作提供同步,例如add()或get()。它们不提供跨多个方法调用的同步。但是,基本的Java同步让我们实现了这个目标:
public void addAndInsert(String query)
{
synchronized(list)
{
list.add(query);
if(list.size() >= 1000)
{
writeIntoDatabase(list);
list.clear();
}
}
}
此代码使用列表实例的object monitor来确保其上的所有操作都已同步。列表中的一个线程操作必须在下一个操作之前完成。
将数据插入数据库
您已经说过要使用另一个Thread将数据插入数据库。我建议您熟悉java.util.concurrent
包中的ExecutorService界面。这提供了出色的实现,可以提供托管的线程池来执行任务。从你所说的,我建议ThreadPoolExecutor是你需要的理想选择。还必须记住将列表中的数据副本传递给另一个线程,以便List.clear()
操作不会干扰插入数据库。
因此,这将使我们的最终代码看起来类似于:
@Service
public class AService
{
private List<String> list;
private ExecutorService executorService;
public void addAndInsert(String query)
{
synchronized(list)
{
list.add(query);
if(list.size() >= 1000)
{
executorService.execute(writeIntoDataBase(new LinkedList<String>(list)));
list.clear();
}
}
}
private Runnable writeIntoDataBase(List<String> list)
{
//TODO - Create your Runnable to write data to the db.
}
}
答案 1 :(得分:1)
如果所有访问都是同步的,ArrayList就可以正常运行,并且在将副本传递给插入线程之前创建一个副本:
@Service
class AService {
private List<String> list = new ArrayList<>(1000);
public synchronized void addAndInsert(String query) {
list.add(query);
if (list.size() >= 1000) {
List<String> copy = new ArrayList<>(list);
writeIntoDatabase(copy);
list.clear();
}
}
}
但是如果查询没有丢失是至关重要的,那么就不应该使用缓冲区,因为很明显,如果服务器在列表包含999个元素时崩溃,那么你将丢失999个查询。
答案 2 :(得分:0)
您可以使用BlockingQueue
之类的并发收集。另一个线程可以从收集和更新数据库中获取查询。
答案 3 :(得分:0)
基于Robs回答,我假设您要确保一次为1000个查询插入数据库。因此,您可以使用BlockingQueue
(例如ArrayBlockingQueue
或LinkedBlockingQueue
)来处理所有同步。您还可以获得类似drainTo
的方法,这些方法从阻塞队列中获取指定数量的元素,并将它们返回到可用于writeIntoDataBase的另一个集合中。如在
BlockingQueue<String> list;
public void addAndInsert(String query) {
list.add(query);
if ( list.size() >= 1000) {
int size = 1000;
final ArrayList<String> toInsert = new ArrayList<String>( 1000);
list.drainTo( toInsert, size);
executorService.execute( new Runnable() {
public void run() {
writeIntoDataBase( toInsert);
}
});
}
}