An Introduction to Synchronized Java Collections – 同步的Java集合的介绍

最后修改: 2018年 10月 18日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

The collections framework is a key component of Java. It provides an extensive number of interfaces and implementations, which allows us to create and manipulate different types of collections in a straightforward manner.

集合框架是Java的一个关键组件。它提供了大量的接口和实现,使我们能够以直接的方式创建和操作不同类型的集合。

Although using plain unsynchronized collections is simple overall, it can also become a daunting and error-prone process when working in multi-threaded environments (a.k.a. concurrent programming).

尽管使用普通的非同步集合总体上很简单,但当在多线程环境中工作时(又称并发编程),它也会成为一个令人生畏和容易出错的过程。

Hence, the Java platform provides strong support for this scenario through different synchronization wrappers implemented within the Collections class.

因此,Java平台通过不同的同步包装器Collections类中实现,为这种情况提供了强有力的支持。

These wrappers make it easy to create synchronized views of the supplied collections by means of several static factory methods.

这些包装器使得通过几个静态工厂方法来创建所提供的集合的同步视图变得容易。

In this tutorial, we’ll take a deep dive into these static synchronization wrappers. Also, we’ll highlight the difference between synchronized collections and concurrent collections.

在本教程中,我们将深入探讨这些静态同步包装器。此外,我们还将强调同步集合和并发集合之间的区别.

2. The synchronizedCollection() Method

2、synchronizedCollection()方法

The first synchronization wrapper that we’ll cover in this round-up is the synchronizedCollection() method. As the name suggests, it returns a thread-safe collection backed up by the specified Collection.

我们将在本综述中介绍的第一个同步包装器是synchronizedCollection()方法。顾名思义,它返回一个由指定的Collection支持的线程安全集合。

Now, to understand more clearly how to use this method, let’s create a basic unit test:

现在,为了更清楚地了解如何使用这个方法,让我们创建一个基本的单元测试。

Collection<Integer> syncCollection = Collections.synchronizedCollection(new ArrayList<>());
    Runnable listOperations = () -> {
        syncCollection.addAll(Arrays.asList(1, 2, 3, 4, 5, 6));
    };
    
    Thread thread1 = new Thread(listOperations);
    Thread thread2 = new Thread(listOperations);
    thread1.start();
    thread2.start();
    thread1.join();
    thread2.join();
    
    assertThat(syncCollection.size()).isEqualTo(12);
}

As shown above, creating a synchronized view of the supplied collection with this method is very simple.

如上所示,用这个方法创建一个所提供的集合的同步视图是非常简单的。

To demonstrate that the method actually returns a thread-safe collection, we first create a couple of threads.

为了证明该方法实际上返回了一个线程安全的集合,我们首先创建几个线程。

After that, we then inject a Runnable instance into their constructors, in the form of a lambda expression. Let’s keep in mind that Runnable is a functional interface, so we can replace it with a lambda expression.

之后,我们再将一个Runnable实例以lambda表达式的形式注入它们的构造器中。我们要记住,Runnable是一个函数式接口,所以我们可以用lambda表达式来代替它。

Lastly, we just check that each thread effectively adds six elements to the synchronized collection, so its final size is twelve.

最后,我们只是检查一下,每个线程有效地将六个元素添加到同步集合中,所以它的最终大小是12个。

3. The synchronizedList() Method

3、synchronizedList()方法

Likewise, similar to the synchronizedCollection() method, we can use the synchronizedList() wrapper to create a synchronized List.

同样地,与synchronizedCollection()方法类似,我们可以使用synchronizedList()包装器来创建一个同步的List

As we might expect, the method returns a thread-safe view of the specified List:

正如我们所期望的,该方法返回指定List的线程安全视图:

List<Integer> syncList = Collections.synchronizedList(new ArrayList<>());

Unsurprisingly, the use of the synchronizedList() method looks nearly identical to its higher-level counterpart, synchronizedCollection().

不出所料,synchronizedList()方法的使用看起来与它的高级对应方法synchronizedCollection()几乎完全相同。

Therefore, as we just did in the previous unit test, once that we’ve created a synchronized List, we can spawn several threads. After doing that, we’ll use them to access/manipulate the target List in a thread-safe fashion.

因此,正如我们在前面的单元测试中所做的那样,一旦我们创建了一个同步的List,我们就可以生成几个线程。之后,我们将使用它们以线程安全的方式访问/操纵目标List

In addition, if we want to iterate over a synchronized collection and prevent unexpected results, we should explicitly provide our own thread-safe implementation of the loop. Hence, we could achieve that using a synchronized block:

此外,如果我们想在一个同步的集合上进行迭代,并防止出现意外的结果,我们应该明确地提供我们自己的线程安全的循环实现。因此,我们可以使用一个同步块来实现。

List<String> syncCollection = Collections.synchronizedList(Arrays.asList("a", "b", "c"));
List<String> uppercasedCollection = new ArrayList<>();
    
Runnable listOperations = () -> {
    synchronized (syncCollection) {
        syncCollection.forEach((e) -> {
            uppercasedCollection.add(e.toUpperCase());
        });
    }
};

In all cases where we need to iterate over a synchronized collection, we should implement this idiom. This is because the iteration on a synchronized collection is performed through multiple calls into the collection. Therefore they need to be performed as a single atomic operation.

在所有需要对同步集合进行迭代的情况下,我们都应该实现这个成语。这是因为对一个同步集合的迭代是通过对集合的多次调用来进行的。因此,它们需要作为一个单一的原子操作来执行。

The use of the synchronized block ensures the atomicity of the operation.

使用synchronized块可以确保操作的原子性

4. The synchronizedMap() Method

4、synchronizedMap()方法

The Collections class implements another neat synchronization wrapper, called synchronizedMap(). We could use it for easily creating a synchronized Map.

Collections类实现了另一个整洁的同步包装器,称为synchronizedMap()。我们可以用它来轻松创建一个同步的Map

The method returns a thread-safe view of the supplied Map implementation:

该方法返回所提供的Map实现的线程安全视图

Map<Integer, String> syncMap = Collections.synchronizedMap(new HashMap<>());

5. The synchronizedSortedMap() Method

5.synchronizedSortedMap()方法

There’s also a counterpart implementation of the synchronizedMap() method. It is called synchronizedSortedMap(), which we can use for creating a synchronized SortedMap instance:

还有一个对应的synchronizedMap()方法的实现。它被称为synchronizedSortedMap(),我们可以用它来创建一个同步的SortedMap实例。

Map<Integer, String> syncSortedMap = Collections.synchronizedSortedMap(new TreeMap<>());

6. The synchronizedSet() Method

6、synchronizedSet()方法

Next, moving on in this review, we have the synchronizedSet() method. As its name implies, it allows us to create synchronized Sets with minimal fuss.

接下来,在本回顾中,我们有synchronizedSet()方法。顾名思义,它允许我们以最小的代价创建同步的Sets

The wrapper returns a thread-safe collection backed by the specified Set:

包装器返回一个由指定的Set支持的线程安全的集合。

Set<Integer> syncSet = Collections.synchronizedSet(new HashSet<>());

7. The synchronizedSortedSet() Method

7、synchronizedSortedSet()方法

Finally, the last synchronization wrapper that we’ll showcase here is synchronizedSortedSet().

最后,我们将在这里展示的最后一个同步包装器是 synchronizedSortedSet()

Similar to other wrapper implementations that we’ve reviewed so far, the method returns a thread-safe version of the given SortedSet:

与我们迄今为止审查的其他包装器实现类似,该方法返回给定SortedSet的线程安全版本。

SortedSet<Integer> syncSortedSet = Collections.synchronizedSortedSet(new TreeSet<>());

8. Synchronized vs Concurrent Collections

8.同步与并发集合

Up to this point, we took a closer look at the collections framework’s synchronization wrappers.

到此为止,我们对集合框架的同步包装器进行了仔细研究。

Now, let’s focus on the differences between synchronized collections and concurrent collections, such as ConcurrentHashMap and BlockingQueue implementations.

现在,让我们关注同步集合和并发集合之间的区别,例如ConcurrentHashMapBlockingQueue的实现。

8.1. Synchronized Collections

8.1.同步的集合

Synchronized collections achieve thread-safety through intrinsic locking, and the entire collections are locked. Intrinsic locking is implemented via synchronized blocks within the wrapped collection’s methods.

同步集合通过内在锁c锁实现线程安全,并且整个集合都被锁定。内在锁定是通过被包裹的集合的方法中的同步块来实现的。

As we might expect, synchronized collections assure data consistency/integrity in multi-threaded environments. However, they might come with a penalty in performance, as only one single thread can access the collection at a time (a.k.a. synchronized access).

正如我们所期望的,同步集合保证了多线程环境下的数据一致性/完整性。然而,它们可能会带来性能上的损失,因为每次只有一个单线程可以访问集合(又称同步访问)。

For a detailed guide on how to use synchronized methods and blocks, please check our article on the topic.

关于如何使用同步方法和块的详细指南,请查看我们关于该主题的文章

8.2. Concurrent Collections

8.2.并行集合

Concurrent collections (e.g. ConcurrentHashMap), achieve thread-safety by dividing their data into segments. In a ConcurrentHashMap, for example, different threads can acquire locks on each segment, so multiple threads can access the Map at the same time (a.k.a. concurrent access).

并发集合(例如ConcurrentHashMap),通过将其数据划分为段来实现线程安全。例如,在ConcurrentHashMap中,不同的线程可以获得每个段的锁,因此多个线程可以同时访问Map(又称并发访问)。

Concurrent collections are much more performant than synchronized collections, due to the inherent advantages of concurrent thread access.

由于并发线程访问的固有优势,并发集合的性能要比同步集合好得多。

So, the choice of what type of thread-safe collection to use depends on the requirements of each use case, and it should be evaluated accordingly.

所以,选择使用哪种类型的线程安全集合,取决于每个用例的要求,应该对其进行相应的评估。

9. Conclusion

9.结论

In this article, we took an in-depth look at the set of synchronization wrappers implemented within the Collections class.

在这篇文章中,我们深入了解了在Collections类中实现的一组同步包装器

Additionally, we highlighted the differences between synchronized and concurrent collections, and also looked at the approaches they implement for achieving thread-safety.

此外,我们还强调了同步集合和并发集合之间的区别,并且还研究了它们实现线程安全的方法。

As usual, all the code samples shown in this article are available over on GitHub.

像往常一样,本文中显示的所有代码样本都可以在GitHub上获得。