日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

parallel循环java_使用Java8新特性parallelStream遇到的坑

發布時間:2023/12/18 java 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 parallel循环java_使用Java8新特性parallelStream遇到的坑 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

1 問題測試代碼

public static void main(String[] args) {

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

List list = new ArrayList<>();

for (int i = 0; i < 20; i++) {

Calendar startDay = new GregorianCalendar();

Calendar checkDay = new GregorianCalendar();

checkDay.setTime(startDay.getTime());//不污染入參

checkDay.add(checkDay.DATE,i);

list.add(checkDay);

checkDay = null;

startDay = null;

}

list.stream().forEach(day -> System.out.println(sdf.format(day.getTime())));

System.out.println("-----------------------");

list.parallelStream().forEach(day -> System.out.println(sdf.format(day.getTime())));

System.out.println("-----------------------");

}

說明:

(1) 使用stream().forEach(),就是單純的串行遍歷循環,和使用for循環得到的效果一樣,只是這種方式可以使代碼更精簡;

(2) 使用parallelStream().forEach(),是并行遍歷循環,相當于是使用了多線程處理.這樣可以在一定程度上提高執行效率.而程序在運行過程中具體會使用多少個線程進行處理,系統會根據運行服務器的資源占用情況自動進行分配.

2 運行結果

image.png

3 原因排查

這些文章中描述的問題歸根結底都是同一類問題,那就是在使用parallelStream().forEach()時,都操作了線程不安全的對象(ArrayList).

查看ArrayList的源碼如下:

transient Object[] elementData; // non-private to simplify nested class access

/**

* Appends the specified element to the end of this list.

* @param e element to be appended to this list

* @return true (as specified by {@link Collection#add})

*/

public boolean add(E e) {

ensureCapacityInternal(size + 1); // Increments modCount!!

elementData[size++] = e;

return true;

}

通過查看源碼可以看到,ArrayList本身底層是通過一個名為elementData的數組實現的,而add()方法上并沒有加同步鎖,可見在多線程并發情況下存在線程不安全的問題.

這些文章最后的解決方案都是將操作ArrayList轉化為一個同步的集合:

Collections.synchronizedList(new ArrayList<>())

這樣并行流操作同一個ArrayList的對象中add()方法時,就都是同步串行操作的了,就不存在線程安全的問題了,也即是解決了文章中反饋的問題.

那么出問題的原因就找到了,那就是在使用parallelStream().forEach()時,都操作了線程不安全的對象.

4 結合自己的問題

上面找到的出問題的原因,就是在parallelStream().forEach()中使用了線程不安全的對象.

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

...

list.parallelStream().forEach(**day** -> System.out.println(sdf.format(day.getTime())));

如上面代碼所示,從list中遍歷的day和day.getTime()肯定不會有線程安全問題.那么就只剩下SimpleDateFormat實例對象了.下面咱查看SimpleDateFormat對象的format()源碼深挖得到如下信息:

public abstract class DateFormat extends Format {

/**

* The {@link Calendar} instance used for calculating the date-time fields

* and the instant of time. This field is used for both formatting and

* parsing.

*

*

Subclasses should initialize this field to a {@link Calendar}

* appropriate for the {@link Locale} associated with this

* DateFormat.

* @serial

*/

protected Calendar calendar;

...

// Called from Format after creating a FieldDelegate

private StringBuffer format(Date date, StringBuffer toAppendTo,

FieldDelegate delegate) {

// Convert input date to time field list

calendar.setTime(date);

boolean useDateFormatSymbols = useDateFormatSymbols();

for (int i = 0; i < compiledPattern.length; ) {

int tag = compiledPattern[i] >>> 8;

int count = compiledPattern[i++] & 0xff;

if (count == 255) {

count = compiledPattern[i++] << 16;

count |= compiledPattern[i++];

}

switch (tag) {

case TAG_QUOTE_ASCII_CHAR:

toAppendTo.append((char)count);

break;

case TAG_QUOTE_CHARS:

toAppendTo.append(compiledPattern, i, count);

i += count;

break;

default:

subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);

break;

}

}

return toAppendTo;

}

format()方法中操作了一個成員變量calendar,且該方法上未加同步鎖,說明該方法在多線程并發訪問時,存在線程安全問題.這就是上面測試代碼中出現重復數據的根本原因.

進一步查詢得知,Java8以前的老版本中的日期和時間類全部都是線程不安全的,而在Java8新推出的日期類LocalDate和LocalDateTime非常友好的解決了上述問題.

5 針對測試代碼中問題的根本解決之道

棄用Java8之前舊版本中的日期和時間類,改用新版本中的時間類.新修改后的代碼如下:

public static void main1(String[] args) {

DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd");

LocalDate date = LocalDate.now();

List list = new ArrayList<>();

for (int i = 0; i < 20; i++) {

LocalDate date1 = date.plusDays(i);

list.add(date1);

}

list.stream().forEach(day -> System.out.println(day.format(fmt)));

System.out.println("-----------------------");

list.parallelStream().forEach(day -> System.out.println(day.format(fmt)));

}

public static void main2(String[] args) {

DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd");

LocalDateTime date = LocalDateTime.now();

List list = new ArrayList<>();

for (int i = 0; i < 20; i++) {

LocalDateTime date1 = date.plusDays(i);

list.add(date1);

}

list.stream().forEach(day -> System.out.println(day.format(fmt)));

System.out.println("-----------------------");

list.parallelStream().forEach(day -> System.out.println(day.format(fmt)));

}

看一下LocalDate和LocalDateTime的源碼:通過查看源碼,可以看到LocalDate和LocalDateTime類都是不可變和線程安全的.這樣的下面的代碼中的day每一次都是不同的對象

list.parallelStream().forEach(day -> System.out.println(day.format(fmt)));

再來對比最初問題代碼:并行操作時,在使用同一個sdf實例.

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

...

list.parallelStream().forEach(day -> System.out.println(sdf.format(day.getTime())));

LocalDate類源碼:

* @implSpec

* This class is immutable and thread-safe.

* @since 1.8

*/

public **final** class LocalDate

implements Temporal, TemporalAdjuster, ChronoLocalDate, Serializable {

...

LocalDateTime類源碼:

* @implSpec

* This class is immutable and thread-safe.

*

* @since 1.8

*/

public final class LocalDateTime

implements Temporal, TemporalAdjuster, ChronoLocalDateTime, Serializable {

至此,測試代碼中出問題的根本原因找到,根本解決之道找到.OK!

總結

以上是生活随笔為你收集整理的parallel循环java_使用Java8新特性parallelStream遇到的坑的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。