Java并发 - 使用哪种技术来实现安全性?

时间:2011-07-19 20:19:46

标签: java concurrency thread-safety

我有一个personId列表。有两个API调用来更新它(添加和删除):

public void add(String newPersonName) {
    if (personNameIdMap.get(newPersonName) != null) {
        myPersonId.add(personNameIdMap.get(newPersonName)
    } else {
        // get the id from Twitter and add to the list
    }  
    // make an API call to Twitter
 }

public void delete(String personNAme) {
    if (personNameIdMap.get(newPersonName) != null) {
        myPersonId.remove(personNameIdMap.get(newPersonName)
    } else {
        // wrong person name
    }  
    // make an API call to Twitter
}

我知道可能存在并发问题。我读了大约3个解决方案:

  1. synchronized方法
  2. 使用Collections.synchronizedlist()
  3. CopyOnWriteArrayList
  4. 我不确定哪一个更喜欢防止不一致。

6 个答案:

答案 0 :(得分:4)

  

1)同步方法

     

2)使用Collections.synchronizedlist

     

3)CopyOnWriteArrayList ..

一切都会奏效,这取决于您需要什么样的性能/功能。

方法#1和#2是阻塞方法。如果同步方法,则自己处理并发。如果您将列表包装在Collections.synchronizedList中,它会为您处理。 (恕我直言#2更安全 - 只需确保按照文档说的那样使用它,并且不要让任何访问包含在synchronizedList中的原始列表。)

CopyOnWriteArrayList是在某些应用程序中使用的奇怪之物之一。它是一个非阻塞的准不可变列表,即,如果线程A在线程B更改它时遍历列表,则线程A将遍历旧列表的快照。如果您需要非阻塞性能,并且很少写入列表,但经常从中读取,那么这可能是最好用的。


编辑:至少还有两个选项:

4)使用Vector代替ArrayList; Vector实现List并且已经同步。然而,它通常是皱眉头,因为它被认为是一个老派班级(自Java 1.0以来就在那里!),并且应该等同于#2。

5)仅从一个线程连续访问List。如果这样做,则保证List本身不会出现任何并发问题。一种方法是使用Executors.newSingleThreadExecutor并逐个排队任务以访问列表。这会将资源争用从您的列表移动到ExecutorService;如果任务很短,那可能没问题,但是如果一些任务很长,可能会导致其他人阻塞的时间超过预期。

最后,您需要考虑应用程序级别的并发性:线程安全性应该是一个要求,并找出如何通过最简单的设计获得所需的性能。


在旁注中,您在add()和delete()中调用personNameIdMap.get(newPersonName)两次。如果另一个线程在每个方法中的两个调用之间修改personNameIdMap,则会遇到并发问题。你做得最好

PersonId id = personNameIdMap.get(newPersonName);
if (id != null){
    myPersonId.add(id);
} 
else 
{
    // something else
}

答案 1 :(得分:4)

Collections.synchronizedList是最容易使用的,也许是最好的选择。它只是用synchronized包装基础列表。请注意,您仍需要同步多步操作(例如for循环)。

一些快速的事情

  • 除非确实需要,否则不要同步该方法 - 它只是锁定整个对象,直到方法完成;几乎没有理想的效果
  • CopyOnWriteArrayList是一个非常专业的列表,因为你有一个add方法,所以很可能你不想要它。它本质上是一个正常的ArrayList,但每次添加一些东西时,整个阵列都会被重建,这是一项非常昂贵的任务。它的线程安全,但不是真正理想的结果

答案 2 :(得分:3)

  1. Synchronized是使用线程的旧方法。避免使用主要在java.util.concurrent包中表达的新习语。
  2. 见1.
  3. CopyOnWriteArrayList具有快速读取和慢速写入。如果你对它做了很多改动,它可能会开始拖累你的表现。
  4. 并发不是孤立地选择在单个方法中使用什么机制或类型。您需要从更高层次考虑它以了解其所有影响。

答案 3 :(得分:2)

根据您发布的代码,您可以接受所有3种方式。但是,有一些特定的特征:

#3:这应该与#2具有相同的效果,但可能会运行得更快或更慢,具体取决于系统和工作负载。

#1:这种方式最灵活。只有#1才能使add()和delete()方法更复杂。例如,如果您需要在列表中读取或写入多个项目,则不能使用#2或#3,因为某些其他线程仍然可以看到列表被更新了一半。

答案 4 :(得分:2)

您是否在这些方法中对personNameIdMap进行了更改,还是对同步的任何其他数据结构进行了更改?如果是这样,将方法标记为同步可能是最容易的;否则,您可以考虑使用Collections.synchronizedList获取myPersonId的同步视图,然后通过该同步视图执行所有列表操作。请注意,在这种情况下,您不应直接操纵myPersonId,而应仅通过Collections.synchronizedList调用返回的列表进行所有访问。

无论哪种方式,您都必须确保永远不会出现同时对同一非同步数据结构进行读取和写入或两次写入的情况。记录为线程安全的数据结构或从Collections.synchronizedListCollections.synchronizedMap等返回的数据结构是此规则的例外,因此可以将对这些数据的调用放在任何位置。非同步数据结构仍然可以安全地在声明为同步的方法中使用,但是,因为JVM保证这些方法不会同时运行,因此可能没有并发读/写。

答案 5 :(得分:0)

Java并发(多线程):

并发性是指并行运行多个程序或程序的多个部分的能力。如果可以异步或并行执行耗时的任务,则可以提高程序的吞吐量和交互性。

我们可以用Java进行并发编程。通过java并发,我们可以做并行编程,不变性,线程,执行器框架(线程池),期货,callables和fork-join框架程序。