0

我正在使用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();
    }

    }

}

当只有一个线程时,这将正常工作。但是我们知道查询可以从不同的用户调用(当然是多线程。),所以我怎么能保证它正常工作:

  1. 当查询达到 1000 时,我想使用另一个线程来写入数据库,因为这个过程可能很长,我不希望用户等待与查询无关的东西。

  2. 查询不能丢失或重复。

谁能告诉我如何处理这种情况,List我应该使用哪个类的实现?谢谢!

4

4 回答 4

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();
           }
      }
}

此代码使用 List 实例的对象监视器来确保对其进行的所有操作都是同步的。一个线程对列表的操作必须在下一个线程之前完成。

计划将数据插入数据库

您说过您想使用另一个线程将数据插入数据库。我建议你熟悉一下包中的ExecutorService接口java.util.concurrent。这提供了出色的实现,提供托管的线程池来执行任务。根据您所说,我建议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.

      }
}
于 2013-11-11T12:13:01.803 回答
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 个查询。

于 2013-11-11T12:09:09.783 回答
0

您可以像BlockingQueue. 另一个线程可以从收集和更新数据库中获取查询。

于 2013-11-11T12:11:51.853 回答
0

基于 Rob 的回答,我假设您要确保一次插入到数据库中的 1000 个查询。所以你可以使用BlockingQueues (like ArrayBlockingQueueor 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);
            }
        });
    }
}
于 2013-11-11T12:55:02.313 回答