同步和并发集合是线程安全的,但它们的内容不是



从这个来源可以读取:

值得一提的是,同步集合和并发集合只使集合本身线程安全,而不使集合的内容线程安全。

我认为如果Collection是线程安全的,那么它的内容将隐式地是线程安全的。

我的意思是如果两个线程不能访问我的Collection对象,那么我的Collection对象所持有的对象将隐式地成为线程安全的。

我没抓住要点,有人能举个例子给我解释一下吗?

br

存储在线程安全集合中的对象可以泄漏到外部,并以非线程安全的方式使用。

详细回答:

我认为如果集合是线程安全的,那么它的内容将隐式是线程安全的,我的意思是如果两个线程不能访问我的集合对象,我的集合对象所在的对象持有将隐式地变为线程安全。

我知道我肯定没有抓住重点,谁能给我解释一下吗一个例子。

考虑下面的代码,它使用两个线程向同一个非线程安全列表添加0 to 10中的元素。最后,main线程对该列表中的所有元素求和。最终的结果应该与0 + 0 + 1 + 1 + ... 9 + 9 = 90.相同。然而,如果您多次执行该代码,您会得到不同的值,有时甚至会出现以下NPE:

Exception in thread "main" java.lang.NullPointerException
at java.base/java.util.stream.ReduceOps$1ReducingSink.accept(ReduceOps.java:80)
at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1655)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.reduce(ReferencePipeline.java:553)
at Z.CollectionThreadSafe.main(CollectionThreadSafe.java:26)

所有这些都是在调用add方法期间竞争条件的结果。

private static void addToList(List<Integer> list) {
for (int i = 0; i < 10; i++)
list.add(i);
}
public static void main(String[] arg) throws InterruptedException {
final int TOTAL_THREADS = 2;
List<Integer> list = new ArrayList<>();
ExecutorService pool = Executors.newFixedThreadPool(TOTAL_THREADS);
for (int i = 0; i < TOTAL_THREADS; i++) {
pool.submit(() -> addToList(list));
}
pool.shutdown();
pool.awaitTermination(10, TimeUnit.SECONDS);
System.out.println(list.stream().reduce(0, Integer::sum));
}

让我们通过调用Collections.synchronizedList来使用线程安全的List来修复竞争条件。所以让我们把前面的代码改成:

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

你可以随心所欲地运行它;最终结果总是相同的,即。90。这些我们已经知道了。让我们来展示:

值得一提的是,同步集合和并发集合只使集合本身线程安全,而不使集合的内容线程安全。

你只需要适应前面的代码:

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

:

final List<List<Integer>> LIST_THREAD_SAFE = Collections.synchronizedList(new ArrayList<>());
LIST_THREAD_SAFE.add(new ArrayList<>());
List<Integer> list = LIST_THREAD_SAFE.get(0);
...

瞧!您的情况与我们展示的第一个例子()完全相同。竞争条件)。尽管列表LIST_THREAD_SAFE是线程安全的,但它的内容却不是。因此,

synchronized and concurrent collections only收集本身线程安全的而不是内容。

如果集合是线程安全的,两个线程仍然可以同时访问集合。如果不影响集合的一致性,他们也可以同时进行修改。它只保证所有的操作都尽可能与最少的锁数量保持一致。这与集合中的对象没有关系:一个线程可以从集合中获得对象的引用并保留一定的时间,因此其他线程可以从集合中获得相同的引用(在同一时间或之后)并在同一时间访问该对象。

元素的访问是线程安全的,但它们的使用不是线程安全的。两个线程访问同一个元素(一个接一个),然后都调用该元素的方法。集合甚至不知道。