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());//不污染入?yún)?/p>
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(),就是單純的串行遍歷循環(huán),和使用for循環(huán)得到的效果一樣,只是這種方式可以使代碼更精簡;
(2) 使用parallelStream().forEach(),是并行遍歷循環(huán),相當于是使用了多線程處理.這樣可以在一定程度上提高執(zhí)行效率.而程序在運行過程中具體會使用多少個線程進行處理,系統(tǒng)會根據(jù)運行服務器的資源占用情況自動進行分配.
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的數(shù)組實現(xiàn)的,而add()方法上并沒有加同步鎖,可見在多線程并發(fā)情況下存在線程不安全的問題.
這些文章最后的解決方案都是將操作ArrayList轉(zhuǎn)化為一個同步的集合:
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,且該方法上未加同步鎖,說明該方法在多線程并發(fā)訪問時,存在線程安全問題.這就是上面測試代碼中出現(xiàn)重復數(shù)據(jù)的根本原因.
進一步查詢得知,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遇到的坑的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: (SCI论文写作)三种高效的论文用公式编
- 下一篇: 瀑布流插件masonry