如何在Java中使用List Class当需要多线程时?

时间:2013-11-11 11:45:16

标签: java multithreading list spring-mvc

我正在使用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 ),所以我怎样才能保证这一点正常工作:

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

  2. 查询可以丢失或重复。

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

4 个答案:

答案 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(例如ArrayBlockingQueueLinkedBlockingQueue)来处理所有同步。您还可以获得类似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);
            }
        });
    }
}