Java-8 simplify lambda expression with embedded Streams ,并行流的 线程安全问题

Is there a more simple and performant way of doing this, At the end I would need a list of scheduleContainers (List<ScheduleContainer>)
有没有更简单和更高效的方法来做到这一点,最后我需要一个列表scheduleContainers (List<ScheduleContainer>)
final List<ScheduleResponseContent> scheduleResponseContents = new ArrayList<>();
scheduleResponseWrappers.parallelStream().forEach(srw -> scheduleResponseContents.addAll(srw.getScheduleResponseContents()));
final List<List<ScheduleContainer>> schedulesOfWeek = new ArrayList<>();
scheduleResponseContents.parallelStream().forEach(src -> schedulesOfWeek.addAll(src.getSchedules()));
final List<ScheduleContainer> schedules = new ArrayList<>();
schedulesOfWeek.parallelStream().forEach(s -> schedules.addAll(s));
Because of missing classes, I can just assume this is correct:
final List<ScheduleContainer> schedules = scheduleResponseWrappers.stream()
    .flatMap(srw -> srw.getScheduleResponseContents().stream())
    .flatMap(src -> src.getSchedules().stream())
    .collect(Collectors.toList());

 Java-8 parallelStream(...) -> fill ArrayList

I have tried this code:
 final List<ScheduleContainer> scheduleContainers = new ArrayList<>();
 scheduleResponseContent.getSchedules().parallelStream().forEach(s -> scheduleContainers.addAll(s));

With parallelStream I get either an ArrayIndexOutOfBoundException or a NullpointerException because some entries in scheduleContainers are null.

With ... .stream()... everything works fine. My question now would be if there is a possibiliy to fix this or did I misuse parallelStream?

使用 parallelStream 我得到 ArrayIndexOutOfBoundException 或 NullpointerException,因为scheduleContainers中的某些条目为空。

使用 ... .stream()... 一切正常。我现在的问题是,是否有可能解决这个问题,或者我是否滥用了 parallelStream?

Yes, you are misusing parallelStream. First of all, as you have already said twice in your previous question, you should use stream(), and not parallelStream(), by default. Going parallel has an intrinsic cost, that usually makes things less efficient than a simple sequential stream, unless you has a massive amount of data to process, and the process of each element takes time. You should have a performance problem, and measure if a parallel stream solves it, before using one. There's also a much bigger chance of screwing up with a parallel stream, as your post shows.

Read Should I always use a parallel stream when possible? for more arguments.

Second, this code is not thread-safe at all, since it uses several concurrent threads to add to a thread-unsafe ArrayList. It can be safe if you use collect() to create the final list for you instead of forEach() and add things to the list by yourself.

是的,您在滥用 parallelStream。首先,正如您在上一个问题中已经说过两次,默认情况下您应该使用 stream() 而不是 parallelStream()。并行具有内在成本,这通常会使事情的效率低于简单的顺序流,除非您有大量数据要处理,并且每个元素的处理都需要时间。在使用并行流之前,您应该遇到性能问题,并测量并行流是否解决了它。正如您的帖子所示,并行流搞砸的可能性也更大。

阅读我应该尽可能始终使用并行流吗?更多论据。

其次,此代码根本不是线程安全的,因为它使用多个并发线程来添加到线程不安全的 ArrayList。如果您使用 collect() 而不是 forEach() 为您创建最终列表并自行将内容添加到列表中,则可能是安全的。

The code should be

List<ScheduleContainer> scheduleContainers =
    scheduleResponseContent.getSchedules().
                           .stream()
                           .flatMap(s -> s.stream())
                           .collect(Collectors.toList());

 You could also do the following and it would still be thread safe (use .toList() instead of .collect(Collectors.toList()): List<ScheduleContainer> scheduleContainers = scheduleResponseContent.getSchedules().stream() .flatMap(s -> s.stream()).toList() 

您还可以执行以下操作,它仍然是线程安全的(使用 .toList() 而不是 .collect(Collectors.toList())): -> s.stream()).toList() 

Not sure about the cause of the error, but there are better ways to use the Stream API to create a List from multiple input Lists.

不确定错误的原因,但是有更好的方法可以使用 Stream API 从多个输入列表创建一个列表。

final List<ScheduleContainer> scheduleContainers =
    scheduleResponseContent.getSchedules()
                           .parallelStream()
                           .flatMap(s->s.stream()) // assuming getSchedules() returns some 
                                                   // Collection<ScheduleContainer>, based 
                                                   // on your use of addAll(s)
                           .collect(Collectors.toList());

Too many constructor arguments in Spring Beans

Differences of Java 16's Stream.toList() and Stream.collect(Collectors.toList())?

JDK 16 now includes a toList() method directly on Stream instances. In previous Java versions, you always had to use the collect method and provide a Collector instance.

The new method is obviously fewer characters to type. Are both methods interchangeable or are there subtle differences one should be aware of?

JDK 16 现在包含一个toList()直接在Stream实例上的方法。在以前的 Java 版本中,您总是必须使用该collect方法并提供一个Collector实例。

新方法显然需要输入的字符更少。这两种方法是否可以互换,或者是否存在应该注意的细微差别?

var newList = someCollection.stream()
    .map(x -> mapX(x))
    .filter(x -> filterX(x))
    .toList();

// vs.

var oldList = someCollection.stream()
    .map(x -> mapX(x))
    .filter(x -> filterX(x))
    .collect(Collectors.toList());

(This question is similar to Would Stream.toList() perform better than Collectors.toList(), but focused on behavior and not (only) on performance.)

(这个问题类似于Would Stream.toList() perform better than Collectors.toList(),但重点是行为而不是(仅)性能。) 

One difference is that Stream.toList() provides a List implementation that is immutable (type ImmutableCollections.ListN that cannot be added to or sorted) similar to that provided by List.of() and in contrast to the mutable (can be changed and sorted) ArrayList provided by Stream.collect(Collectors.toList()).

一个区别是它提供了Stream.toList()一个List不可变的(ImmutableCollections.ListN无法添加或排序的类型)实现List.of(),类似于.ArrayListStream.collect(Collectors.toList())

Demo:

import java.util.stream.Stream;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> list = Stream.of("Hello").toList();
        System.out.println(list);
        list.add("Hi");
    }
}

Output:

[Hello]
Exception in thread "main" java.lang.UnsupportedOperationException
    at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:142)
    at java.base/java.util.ImmutableCollections$AbstractImmutableCollection.add(ImmutableCollections.java:147)
    at Main.main(Main.java:8)

Please check this article for more details.

Update:

Interestingly, Stream.toList() returns a nulls-containing list successfully.

更新:

有趣的是,成功Stream.toList()返回一个null包含s的列表。

import java.util.stream.Stream;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Object> list = Stream.of(null, null).toList();
        System.out.println(list);
    }
}

Output:

[null, null]

On the other hand, List.of(null, null) throws NullPointerException.

另一方面,List.of(null, null)抛出NullPointerException.

import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Object> list = List.of(null, null);
    }
}

Output:

Exception in thread "main" java.lang.NullPointerException
    at java.base/java.util.Objects.requireNonNull(Objects.java:208)
    at java.base/java.util.ImmutableCollections$List12.<init>(ImmutableCollections.java:453)
    at java.base/java.util.List.of(List.java:827)
    at Main.main(Main.java:5)

Note: I've used openjdk-16-ea+34_osx-x64 to compile and execute the Java SE 16 code.

注意:我使用openjdk-16-ea+34_osx-x64来编译和执行 Java SE 16 代码。

Useful resources:

  1. JDK Bug#JDK-8180352
  2. Calling Java varargs method with single null argument?
  1. JDK 漏洞# JDK-8180352
  2. 使用单个 null 参数调用 Java varargs 方法?
IIRC, Collectors.toList() is not guaranteed to give us a mutable list either. It just happens to do so in the Java versions we have seen so far. 
Collectors.toList()也不能保证给我们一个可变列表。到目前为止,我们看到的 Java 版本恰好是这样做的。
 
@OleV.V. - Correct. The article linked in my answer mentions it as: Although there are no guarantees regarding the “type, mutability, serializability, or thread-safety” on the List provided by Collectors.toList(), it is expected that some may have realized it’s currently an ArrayList and have used it in ways that depend on the characteristics of an ArrayList. 
正确的。我的回答中链接的文章提到它是:尽管没有关于collectors.tolist()提供的列表上的“类型,可变性,序列化或线程安全性”的保证关于阵列列表的特征。 预计有些人可能已经意识到它是当前的阵列列表,并且以取决于阵列列表的特征的方式使用它。
@Caramiriel The List interface was on purpose designed with a number of optional methods, that is, methods that implementing classes need not implement and that many implementations do not implement. You’re not the first to wonder. 
List接口是有意使用许多可选方法设计的,也就是说,实现类的方法不需要实现,许多实现也不需要实现。你不是第一个想知道的人
It is not really a correct statement to say that collect(toList()) returns a mutable list; the specification is very clear that it makes no guarantees as to the mutability of the returned list. The current implementation happens to return an ArrayList right now, but the spec was written explicitly to allow that to change. If you want a mutable list, use toCollection(ArrayList::new). 
collect(toList())说返回一个可变列表并不是一个正确的说法;规范非常清楚,它不保证返回列表的可变性。当前的实现恰好返回一个ArrayList right now,但规范是明确编写的以允许更改。如果您想要一个可变列表,请使用toCollection(ArrayList::new). 
It is unfortunate that List was not divided into ReadableList with get() etc. and WritableList with set()add() etc., but it's too late to fix now. (note: I originally came here to suggest toCollection(ArrayList::new), which is what I always use when I need a mutable list, but The Man Himself beat me to it. 😂) 
 可惜当时List没有分成ReadableListwith etc.get()WritableListwith等,但现在修复已经来不及了。(注意:我最初是来这里建议的,这是我在需要可变列表时经常使用的方法,但是 The Man Himself 抢先了我。😂)set()add()toCollection(ArrayList::new) 

Here is a small table that summarizes the differences between Stream.collect(Collectors.toList())Stream.collect(Collectors.toUnmodifiableList()) and Stream.toList():

Stream.collect(Collectors.toList())这是一个小表格,总结了,Stream.collect(Collectors.toUnmodifiableList())和之间的区别Stream.toList()

Method Guarantees unmodifiability Allows nulls
collect(toList()) No Yes
collect(toUnmodifiableList()) Yes No
toList() Yes Yes

Another small difference:

// Compiles
List<CharSequence> list = Stream.of("hello", "world").collect(toList());

// Error
List<CharSequence> list = Stream.of("hello", "world").toList();

 The new method Stream.toList() produces neither an unmodifiable list nor a shortcut to collect(toUnmodifiableList()), because toUnmodifiableList() doesn’t accept nulls.The implementation of Stream.toList() is not constrained by the Collector interface; therefore, Stream.toList() allocates less memory. That makes it optimal to use when the stream size is known in advance. blogs.oracle.com/javamagazine/hidden-gems-jdk16-jdk17-jep Can you Validate the above statements 

 新方法 Stream.toList() 既不生成不可修改的列表,也不生成 collect(toUnmodifiableList()) 的快捷方式,因为 toUnmodifiableList() 不接受空值。 Stream.toList() 的实现不受 Collector 接口的约束;因此,Stream.toList() 分配的内存较少。这使得它最适合在事先知道流大小时使用。 
I would add that the first one was introduced in Java 8, the 2nd in Java 10, and the 3rd in Java 16. 
 我要补充的是,第一个是在 Java 8 中引入的,第二个是在 Java 10 中引入的,第三个是在 Java 16 中引入的。

.collect(toList()) and toList() behave different regarding sub type compatibility of the elements in the created lists.

Have a look at the following alternatives:

  • List<Number> old = Stream.of(0).collect(Collectors.toList()); works fine, although we collect a stream of Integer into a list of Number.
  • List<Number> new = Stream.of(0).toList(); is the equivalent Java 16+ version, but it doesn't compile (cannot convert from List<Integer> to List<Number>; at least in ecj, the Eclipse Java compiler).

There are at least 2 workarounds to fix the compile error:

  • Explicitly cast to the wanted type List<Number> fix1 = Stream.of(0).map(Number.class::cast).toList();
  • allow sub types in the result collection: List<? extends Number> fix2 = Stream.of(0).toList();

To my understanding the root cause is as follows: The generic type T of the Java 16 toList() is the same as the generic type T of the Stream itself. The generic type T of Collectors.toList() however is propagated from the left hand side of the assignment. If those 2 types are different, you may see errors when replacing all the old calls.

.collect(toList())并且在创建的列表中元素的子类型兼容性方面toList()表现不同。

看看以下替代方案:

  • List<Number> old = Stream.of(0).collect(Collectors.toList());工作正常,尽管我们将Integer流收集到Number列表中。
  • List<Number> new = Stream.of(0).toList();是等效的 Java 16+ 版本,但它不编译(cannot convert from List<Integer> to List<Number>;至少在 ecj,Eclipse Java 编译器中)。

至少有 2 种解决方法可以修复编译错误:

  • 显式转换为想要的类型 List<Number> fix1 = Stream.of(0).map(Number.class::cast).toList();
  • 在结果集合中允许子类型: List<? extends Number> fix2 = Stream.of(0).toList();

据我了解,根本原因如下:Java 16 的泛型 TtoList()与 Stream 本身的泛型 T 相同。然而,Collectors.toList() 的通用类型 T 从赋值的左侧传播。如果这两种类型不同,您可能会在替换所有旧调用时看到错误。

Collecting Stream Elements into a List in Java | Baeldung

一、概述

在本教程中,我们将了解从Stream获取List 的不同方法。我们还将讨论它们之间的差异,以及何时使用每种方法。

2. 将流元素收集到列表中

Stream中获取List是Stream管道最常用的终端操作。在 Java 16 之前,我们习惯于调用Stream.collect()方法并将其作为参数传递给Collector以将元素收集到其中。Collector本身是通过调用Collectors.toList()方法创建的。

但是,已经有人要求更改直接从Stream实例获取List的方法。随着 Java 16 的发布,我们现在可以调用toList(),一种直接在 Stream 上的新方法 来获取List像StreamEx这样的库提供了一种直接从Stream获取List 的便捷方式。

我们可以使用以下方法将Stream元素累积到List中:

  • Stream.collect(Collectors.toList()):从 Java 8 开始
  • Stream.collect( Collectors.toUnmodifiableList() ):从 Java 10 开始
  • Stream.toList():从 Java 16 开始

我们将按照发布的时间顺序使用这些方法。

 

3. 分析列表

我们将首先使用上一节中描述的方法创建列表。然后我们将分析它们的属性。

 我们将在所有示例中使用以下国家/地区代码流:

Stream.of(Locale.getISOCountries());
复制

3.1. 创建列表

现在我们将使用不同的方法从给定的国家代码流创建一个列表

首先,我们将使用Collectors:toList()创建一个列表

List<String> result = Stream.of(Locale.getISOCountries()).collect(Collectors.toList());复制

 然后我们将使用Collectors.toUnmodifiableList()收集它:

 
List<String> result = Stream.of(Locale.getISOCountries()).collect(Collectors.toUnmodifiableList());复制

这里,在这些方法中,我们通过 Collector接口将Stream累积成一个List 这会导致额外的分配和复制,因为我们不直接使用Stream。

 接下来,我们将使用Stream.toList()重复收集:

List<String> result = Stream.of(Locale.getISOCountries()).toList();复制

在这里,我们直接从Stream 中获取List 从而避免了额外的分配和复制。

因此,与其他两个调用相比, 直接在 Stream上使用toList()更加简洁、整洁、方便和最佳。

3.2. 检查累积列表

让我们首先检查我们创建的List的类型。

 

Collectors.toList()将Stream元素 收集到ArrayList中:

java.util.ArrayList复制

Collectors.toUnmodifiableList()将Stream元素 收集到一个不可修改的列表中:

java.util.ImmutableCollections.ListN复制

Stream.toList() 将元素收集到不可修改的列表中:

java.util.ImmutableCollections.ListN复制

尽管Collectors.toList()的当前实现创建了一个可变的List,但该方法的规范本身并不保证 List 的类型、可变性、可序列化性或线程安全性

另一方面,Collectors.toUnmodifiableList() 和Stream.toList()都 生成不可修改的列表。

这意味着我们可以对Collectors.toList() 的元素进行添加和排序等操作,但不能对Collectors.toUnmodifiableList()Stream.toList()的元素进行操作。 

3.3. 允许列表中的空元素

尽管 Stream.toList()生成一个不可修改的List,但它仍然与 Collectors.toUnmodifiableList() 不同这是因为Stream.toList()允许元素,而Collectors.toUnmodifiableList()不允许元素。但是,Collectors.toList()允许 元素。

当 收集包含空元素的Stream时, Collectors.toList()不会抛出异常:

 
Assertions.assertDoesNotThrow(() -> {
    Stream.of(null,null).collect(Collectors.toList());
});复制

当我们收集 包含 空元素的Stream时, Collectors.toUnmodifiableList()会抛出 NulPointerException

Assertions.assertThrows(NullPointerException.class, () -> {
    Stream.of(null,null).collect(Collectors.toUnmodifiableList());
});复制

当我们尝试收集包含 空元素的Stream时, Stream.toList()不会抛出NulPointerException

Assertions.assertDoesNotThrow(() -> {
    Stream.of(null,null).toList();
});复制

因此,在将我们的代码从 Java 8 迁移到 Java 10 或 Java 16 时,这是需要注意的。我们不能盲目地使用Stream.toList()来代替Collectors.toList()Collectors.toUnmodifiableList()。

3.4. 分析总结

下表总结了我们分析的列表的异同:

流列表摘要

4. 何时使用不同的toList()方法

添加Stream.toList()的主要目的是减少Collector API的冗长。

如前所示,使用Collectors方法获取List非常冗长另一方面,使用Stream.toList()方法可以使代码简洁明了。

然而,如前几节所示,Stream.toList()不能用作Collectors.toList()Collectors.toUnmodifiableList()的快捷方式。

其次,  Stream.toList()使用较少的内存,因为它的实现独立于 Collector 接口。它将Stream元素直接累积到List中。因此,如果我们事先知道流的大小,那么使用Stream.toList() 将是最佳选择。

 

我们也知道Stream API 只提供了toList()方法的实现 。它不包含用于获取地图或集合的类似方法。因此,如果我们想要一种统一的方法来获取任何转换器,如 list、map 或 set,我们将继续使用Collector API。这也将保持一致性并避免混淆。

最后,如果我们使用低于 Java 16 的版本,我们必须继续使用Collectors方法。

下表总结了给定方法的最佳使用:

比较

5.结论

在本文中,我们分析了三种最流行的从Stream获取List的方法。然后我们研究了主要的区别和相似之处。我们还讨论了如何以及何时使用这些方法。

与往常一样,本文中使用的示例的源代码可在 GitHub 上获得。

如何实现ArrayList的线程安全_arraylist如何保证线程安全-CSDN博客

     ArrayList用的太多了,几乎所有人都知道它是线程不安全的,但实际使用中,我们的多线程实现,普遍都是基于一些同步方法或者锁,很多场景其实并不需要关注ArrayList本身的线程安全。网上可以找到三种主流的实现ArrayList线程安全的手段,他们分别是什么样的思路,还是值得简单了解和记录的。

Vector

        Vector 是矢量队列,它是JDK1.0版本添加的类,历史比ArrayList(since 1.2)更为悠久。其具体历史已不太可考,个人只能简单猜测是在java初期从其他语言直接借鉴的名字及相关概念。其底层和ArrayList一样是数组,除线程安全外,大多数实现方法和ArrayList逻辑基本一致。值得一提的是,从jdk1.2以后,Java提供了系统的集合框架,就将Vector改为实现List接口,从而导致Vector里有一些重复的方法,例如:addElement(Object obj),实际上这个方法和add(Object obj)没什么区别。

        Vector 的实现,就是在方法上都加上synchronized(即使get也不例外)。康康部分源码。

public synchronized E get(int index) {
    if (index >= elementCount)
        throw new ArrayIndexOutOfBoundsException(index);
    return elementData(index);
}
 
public synchronized boolean add(E e) {
    modCount++;
    ensureCapacityHelper(elementCount + 1);
    elementData[elementCount++] = e;
    return true;
}
关于Vector为什么被弃用

所有方法都有同步开销,非多线程下,效率不如ArrayList;
一些老代码,导致有重复的方法,以及风格和新的集合类格格不入;
线程安全的实现,可以通过新的Collections.synchronizedList之类的调用来替换。
Collections.synchronizedList
        基于集合类的实现,可以简单的使用函数来操作,例如,List<Integer> list2 = Collections.synchronizedList(new ArrayList<Integer>())。先看看函数的定义。

public static <T> List<T> synchronizedList(List<T> list) {
    return (list instanceof RandomAccess ?
        new Collections.SynchronizedRandomAccessList<>(list) :
        new Collections.SynchronizedList<>(list));
}
        显然,里面还藏了一些list类,我们以SynchronizedRandomAccessList接着看看这个类是如何操作的。

//Collections中的静态类1号,主要实现在SynchronizedList中
static class SynchronizedRandomAccessList<E>
    extends Collections.SynchronizedList<E>
    implements RandomAccess {
        ... //绝大部分方法都没有单独的实现
}
//Collections中的静态类2号
static class SynchronizedList<E>
        extends Collections.SynchronizedCollection<E>
        implements List<E> {
    private static final long serialVersionUID = -7754090372962971524L;
 
    final List<E> list;
 
        ...
 
    //可以看到,大部分方法的处理方式,是多了一个synchronized (mutex),
    //mutex是SynchronizedCollection中的一个Object变量
    public boolean equals(Object o) {
        if (this == o)
            return true;
        synchronized (mutex) {
            return list.equals(o);
        }
    }
 
    public int hashCode() {
        synchronized (mutex) {
            return list.hashCode();
        }
    }
 
    public E get(int index) {
        synchronized (mutex) {
            return list.get(index);
        }
    }
 
    public E set(int index, E element) {
        synchronized (mutex) {
            return list.set(index, element);
        }
    }
 
    public void add(int index, E element) {
        synchronized (mutex) {
            list.add(index, element);
        }
    }
 
    public E remove(int index) {
        synchronized (mutex) {
            return list.remove(index);
        }
    }
 
    public int indexOf(Object o) {
        synchronized (mutex) {
            return list.indexOf(o);
        }
    }
 
    public int lastIndexOf(Object o) {
        synchronized (mutex) {
            return list.lastIndexOf(o);
        }
    }
 
    public boolean addAll(int index, Collection<? extends E> c) {
        synchronized (mutex) {
            return list.addAll(index, c);
        }
    }
 
...
 
    //官方注释明确提出,对于使用 Iterator遍历列表时,Collections.synchronizedList可能发生错误
    //还需要手动去确保线程安全
    public ListIterator<E> listIterator() {
        return list.listIterator(); // Must be manually synched by user
    }
 
    public ListIterator<E> listIterator(int index) {
        return list.listIterator(index); // Must be manually synched by user
    }
}
CopyOnWriteArrayList
        CopyOnWriteArrayList是1.5后引入,属于JUC的一部分。他基本的原理还是和ArrayList一样,涉及线程安全的部分,是通过写时复制的方式来实现(从名字中就可以看出)。它内部有个volatile数组来保持数据。在“添加/修改/删除”数据时,会先获取互斥锁,再新建一个数组,并将更新后的数据拷贝到新建的数组中,最后再将该数组赋值给volatile数组,然后再释放互斥锁。以get和set为例看看代码。

public class CopyOnWriteArrayList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    private static final long serialVersionUID = 8673264195747942595L;
 
    //互斥锁
    final transient ReentrantLock lock = new ReentrantLock();
 
    //存储数据的volatile数组,入口仅限于类中函数getArray/setArray。
    private transient volatile Object[] array;
 
    public E get(int index) {
        return get(getArray(), index);
    }
 
    public E set(int index, E element) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            E oldValue = get(elements, index);
 
            if (oldValue != element) {
                int len = elements.length;
                //直接开始复制一格数组,修改某个值,将型数组赋值给CopyOnWriteArrayList中的array
                Object[] newElements = Arrays.copyOf(elements, len);
                newElements[index] = element;
                setArray(newElements);
            } else {
                // Not quite a no-op; ensures volatile write semantics
                // 官方介绍为,并非无效的操作,是为了保证volatile的写语义。
                // 这里有点搞心态,翻译一下参考文档6,按照其中一个回答,这里的代码jdk 8中存在,jdk 11中已经移除。但我在jdk 14中又再次发现。
                // 参考热门回答,主要是为了确保一些逻辑中CopyOnWriteArrayList外的非 volatile变量,由于指令重排导致的执行顺序问题。
                setArray(elements);
            }
            return oldValue;
        } finally {
            lock.unlock();
        }
    }
}
因为通常需要复制整个基础数组,所以可变操作(add()、set() 和 remove() 等等)的开销很大。

性能对比
        我尝试的代码主要来自参考文档4,就不再贴出来了。

CopyOnWriteArrayList add method cost time is 17156
Collections.synchronizedList add method cost time is 23
Vector add method cost time is 5
CopyOnWriteArrayList get method cost time is 2
Collections.synchronizedList get method cost time is 4
Vector get method cost time is 5
---- 10万级数据
 
CopyOnWriteArrayList add method cost time is 133
Collections.synchronizedList add method cost time is 1
Vector add method cost time is 0
CopyOnWriteArrayList get method cost time is 0
Collections.synchronizedList get method cost time is 0
Vector get method cost time is 1
---- 万级数据
        以上代码又多跑了几次,大同小异。

        结论:

写时CopyOnWriteArrayList性能较差,且随着数据量的增大,几何级下跌。读操作各方式基本没有区别。
CopyOnWriteArrayList,适用于以读为主,读操作远远大于写操作的场景中使用,比如缓存。
Collections.synchronizedList则可以用在CopyOnWriteArrayList不适用,但是有需要同步的地方使用, 比如读写操作都比较均匀的地方。
不得不含泪承认,从简单的几次跑数中,Vector的读写都很优秀。但既然已经不建议使用,就忘了它吧。
参考文档
1. Java集合(五)应该弃用的Vector和Stack,Java集合(五)应该弃用的Vector和Stack_weixin_33695082的博客-CSDN博客

2. vector过时的代替建议,vector过时的代替建议_忧伤的可乐鸡-CSDN博客_vector的替代

3. 简单理解Collections.synchronizedList,简单理解Collections.synchronizedList_walker-CSDN博客_collections.synchronizedlist

4. CopyOnWriteArrayList与Collections.synchronizedList的性能对比,https://blog.csdn.net/yangzl2008/article/details/39456817

5. Java多线程系列--“JUC集合”02之 CopyOnWriteArrayList,Java多线程系列--“JUC集合”02之 CopyOnWriteArrayList - 如果天空不死 - 博客园

6. Why setArray() method call required in CopyOnWriteArrayList,java - Why setArray() method call required in CopyOnWriteArrayList - Stack Overflow

 

posted @ 2023-04-25 11:04  CharyGao  阅读(95)  评论(0编辑  收藏  举报