用java并行处理数组
我试图通过线程应用程序以获得更快的输出。只是做一个小的POC排序。 假设我有一个问题陈述来查找数组中所有奇数出现的数字。 下面是我对顺序和并行的尝试用java并行处理数组,java,multithreading,Java,Multithreading,我试图通过线程应用程序以获得更快的输出。只是做一个小的POC排序。 假设我有一个问题陈述来查找数组中所有奇数出现的数字。 下面是我对顺序和并行的尝试 import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Random
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
public class Test1 {
final static Map<Integer, Integer> mymap = new HashMap<>();
static Map<Integer, AtomicInteger> mymap1 = new ConcurrentHashMap<>();
public static void generateData(final int[] arr) {
final Random aRandom = new Random();
for (int i = 0; i < arr.length; i++) {
arr[i] = aRandom.nextInt(10);
}
}
public static void calculateAllOddOccurrence(final int[] arr) {
for (int i = 0; i < arr.length; i++) {
if (mymap.containsKey(arr[i])) {
mymap.put(arr[i], mymap.get(arr[i]) + 1);
} else {
mymap.put(arr[i], 1);
}
}
for (final Map.Entry<Integer, Integer> entry : mymap.entrySet()) {
if (entry.getValue() % 2 != 0) {
System.out.println(entry.getKey() + "=" + entry.getValue());
}
}
}
public static void calculateAllOddOccurrenceThread(final int[] arr) {
final ExecutorService executor = Executors.newFixedThreadPool(10);
final List<Future<?>> results = new ArrayList<>();
;
final int range = arr.length / 10;
for (int count = 0; count < 10; ++count) {
final int startAt = count * range;
final int endAt = startAt + range;
executor.submit(() -> {
for (int i = startAt; i < endAt; i++) {
if (mymap1.containsKey(arr[i])) {
final AtomicInteger accumulator = mymap1.get(arr[i]);
accumulator.incrementAndGet();
mymap1.put(arr[i], accumulator);
} else {
mymap1.put(arr[i], new AtomicInteger(1));
}
}
});
}
awaitTerminationAfterShutdown(executor);
for (final Entry<Integer, AtomicInteger> entry : mymap1.entrySet()) {
if (entry.getValue().get() % 2 != 0) {
System.out.println(entry.getKey() + "=" + entry.getValue());
}
}
}
public static void calculateAllOddOccurrenceStream(final int[] arr) {
final ConcurrentMap<Integer, List<Integer>> map2 = Arrays.stream(arr).parallel().boxed().collect(Collectors.groupingByConcurrent(i -> i));
map2.entrySet().stream().parallel().filter(e -> e.getValue().size() % 2 != 0).forEach(entry -> System.out.println(entry.getKey() + "=" + entry.getValue().size()));
}
public static void awaitTerminationAfterShutdown(final ExecutorService threadPool) {
threadPool.shutdown();
try {
if (!threadPool.awaitTermination(60, TimeUnit.SECONDS)) {
threadPool.shutdownNow();
}
} catch (final InterruptedException ex) {
threadPool.shutdownNow();
Thread.currentThread().interrupt();
}
}
public static void main(final String... doYourBest) {
final int[] arr = new int[200000000];
generateData(arr);
long starttime = System.currentTimeMillis();
calculateAllOddOccurrence(arr);
System.out.println("Total time=" + (System.currentTimeMillis() - starttime));
starttime = System.currentTimeMillis();
calculateAllOddOccurrenceStream(arr);
System.out.println("Total time Thread=" + (System.currentTimeMillis() - starttime));
}
}
并行执行CalculateAllodoccurrenceStream需要更多时间。并行处理数组然后合并结果的最佳方法是什么
我的目标不是找到最快的算法,而是使用任何算法并尝试在不同的线程中运行,以便它们同时处理阵列的不同部分 您将看到Java 8中引入的STREAMS API: 例如:
// sequential processes
myArray.stream().filter( ... ).map( ... ).collect(Collectors.toList()):
// parallel processes
myArray.parallelStream().filter( ... ).map( ... ).collect(Collectors.toList());
您将看到Java 8中引入的STREAMS API: 例如:
// sequential processes
myArray.stream().filter( ... ).map( ... ).collect(Collectors.toList()):
// parallel processes
myArray.parallelStream().filter( ... ).map( ... ).collect(Collectors.toList());
这些线程似乎同时在阵列的相同部分上工作,因此答案不正确 而是使用适当的开始索引和结束索引将数组分成若干部分。分配单独的线程来处理这些部分,并统计每个部分中每个数字的出现次数 最后,您将有多个贴图,其中的计数是从这些单独的部分计算出来的。合并这些地图以获得最终答案
或者您可以使用一个concurrentHashMap来存储来自所有这些线程的计数,但是我想可能会有一个bug潜入其中,因为仍然会存在并发写入冲突。在高度多线程的环境中,cocnurrentHashMap上的写入可能不是100%安全的。对于有保证的写入行为,正确的方法是使用ConcurrentHashMap.putIfAbsentK键的原子性,V值方法,并注意返回值,该值指示put操作是否成功。简单的put可能不正确。看 您可以使用Java8StreamsAPI编写代码,也可以使用Java5构造编写简单的线程代码 添加了Java8流代码,请注意计时差异。ArrayList而不是数组会产生不同:
package com.test;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.function.Function;
import java.util.stream.Collectors;
public class Test {
public static void generateData(final int[] arr) {
final Random aRandom = new Random();
for (int i = 0; i < arr.length; i++) {
arr[i] = aRandom.nextInt(10);
}
}
public static void calculateAllOddOccurrence(final int[] arr) {
final Map<Integer, Integer> mymap = new HashMap<>();
for (int i = 0; i < arr.length; i++) {
if (mymap.containsKey(arr[i])) {
mymap.put(arr[i], mymap.get(arr[i]) + 1);
} else {
mymap.put(arr[i], 1);
}
}
for (final Map.Entry<Integer, Integer> entry : mymap.entrySet()) {
if (entry.getValue() % 2 != 0) {
System.out.println(entry.getKey() + "=" + entry.getValue());
}
}
}
public static void calculateAllOddOccurrenceStream( int[] arr) {
Arrays.stream(arr).boxed().collect(Collectors.groupingBy(Function.identity(), Collectors.counting())).entrySet().parallelStream().filter(e -> e.getValue() % 2 != 0).forEach(entry -> System.out.println(entry.getKey()+"="+ entry.getValue()));
}
public static void calculateAllOddOccurrenceStream(List<Integer> list) {
list.parallelStream().collect(Collectors.groupingBy(Function.identity(), Collectors.counting())).entrySet().parallelStream().filter(e -> e.getValue() % 2 != 0).forEach(entry -> System.out.println(entry.getKey()+"="+ entry.getValue()));
}
public static void main(final String... doYourBest) {
final int[] arr = new int[200000000];
generateData(arr);
long starttime = System.currentTimeMillis();
calculateAllOddOccurrence(arr);
System.out.println("Total time with simple map=" + (System.currentTimeMillis() - starttime));
List<Integer> list = Arrays.stream(arr).boxed().collect(Collectors.toList());
starttime = System.currentTimeMillis();
calculateAllOddOccurrenceStream(list);
System.out.println("Total time stream - with a readymade list, which might be the case for most apps as arraylist is more easier to work with =" + (System.currentTimeMillis() - starttime));
starttime = System.currentTimeMillis();
calculateAllOddOccurrenceStream(arr);
System.out.println("Total time Stream with array=" + (System.currentTimeMillis() - starttime));
}}
这些线程似乎同时在阵列的相同部分上工作,因此答案不正确 而是使用适当的开始索引和结束索引将数组分成若干部分。分配单独的线程来处理这些部分,并统计每个部分中每个数字的出现次数 最后,您将有多个贴图,其中的计数是从这些单独的部分计算出来的。合并这些地图以获得最终答案
或者您可以使用一个concurrentHashMap来存储来自所有这些线程的计数,但是我想可能会有一个bug潜入其中,因为仍然会存在并发写入冲突。在高度多线程的环境中,cocnurrentHashMap上的写入可能不是100%安全的。对于有保证的写入行为,正确的方法是使用ConcurrentHashMap.putIfAbsentK键的原子性,V值方法,并注意返回值,该值指示put操作是否成功。简单的put可能不正确。看 您可以使用Java8StreamsAPI编写代码,也可以使用Java5构造编写简单的线程代码 添加了Java8流代码,请注意计时差异。ArrayList而不是数组会产生不同:
package com.test;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.function.Function;
import java.util.stream.Collectors;
public class Test {
public static void generateData(final int[] arr) {
final Random aRandom = new Random();
for (int i = 0; i < arr.length; i++) {
arr[i] = aRandom.nextInt(10);
}
}
public static void calculateAllOddOccurrence(final int[] arr) {
final Map<Integer, Integer> mymap = new HashMap<>();
for (int i = 0; i < arr.length; i++) {
if (mymap.containsKey(arr[i])) {
mymap.put(arr[i], mymap.get(arr[i]) + 1);
} else {
mymap.put(arr[i], 1);
}
}
for (final Map.Entry<Integer, Integer> entry : mymap.entrySet()) {
if (entry.getValue() % 2 != 0) {
System.out.println(entry.getKey() + "=" + entry.getValue());
}
}
}
public static void calculateAllOddOccurrenceStream( int[] arr) {
Arrays.stream(arr).boxed().collect(Collectors.groupingBy(Function.identity(), Collectors.counting())).entrySet().parallelStream().filter(e -> e.getValue() % 2 != 0).forEach(entry -> System.out.println(entry.getKey()+"="+ entry.getValue()));
}
public static void calculateAllOddOccurrenceStream(List<Integer> list) {
list.parallelStream().collect(Collectors.groupingBy(Function.identity(), Collectors.counting())).entrySet().parallelStream().filter(e -> e.getValue() % 2 != 0).forEach(entry -> System.out.println(entry.getKey()+"="+ entry.getValue()));
}
public static void main(final String... doYourBest) {
final int[] arr = new int[200000000];
generateData(arr);
long starttime = System.currentTimeMillis();
calculateAllOddOccurrence(arr);
System.out.println("Total time with simple map=" + (System.currentTimeMillis() - starttime));
List<Integer> list = Arrays.stream(arr).boxed().collect(Collectors.toList());
starttime = System.currentTimeMillis();
calculateAllOddOccurrenceStream(list);
System.out.println("Total time stream - with a readymade list, which might be the case for most apps as arraylist is more easier to work with =" + (System.currentTimeMillis() - starttime));
starttime = System.currentTimeMillis();
calculateAllOddOccurrenceStream(arr);
System.out.println("Total time Stream with array=" + (System.currentTimeMillis() - starttime));
}}
查看您的代码,您的这一行出现了错误:
mymap1.put(arr[i], mymap1.get(arr[i]) + 1);
您正在并行覆盖这些值,例如:
Thread 1 'get' = 0
Thread 2 'get' = 0
Thread 1 'put 1'
Thread 2 'put 1'
将地图更改为:
static Map<Integer, AtomicInteger> mymap1 = new ConcurrentHashMap<>();
static {
//initialize to avoid null values and non-synchronized puts from different Threads
for(int i=0;i<10;i++) {
mymap1.put(i, new AtomicInteger());
}
}
....
//in your loop
for (int i = 0; i < arr.length; i++) {
AtomicInteger accumulator = mymap1.get(arr[i]);
accumulator.incrementAndGet();
}
编辑:上述方法的问题当然是mymap1的初始化。为了避免落入相同的陷阱,在循环中创建AtomicInteger并再次相互覆盖,需要使用值对其进行预填充
因为我觉得自己很慷慨,下面是使用Streams API的一些方法:
int totalEvenCount = Arrays.stream(arr).parallel().filter(i->i%2==0).reduce(0, Integer::sum);
int totalOddCount = Arrays.stream(arr).parallel().filter(i->i%2!=0).reduce(0, Integer::sum);
//or this to count by individual numbers:
ConcurrentMap<Integer,List<Integer>> map1 = Arrays.stream(arr).parallel().boxed().collect(Collectors.groupingByConcurrent(i->i));
map1.entrySet().stream().filter(e -> e.getKey()%2!=0).forEach(entry -> System.out.println(entry.getKey() + "=" + entry.getValue().size()));
作为读者的练习,也许您可以了解各种收集器是如何工作的,以便编写自己的countingByi->i%2=0以输出仅包含计数而不是值列表的映射。查看代码,这一行出现错误:
mymap1.put(arr[i], mymap1.get(arr[i]) + 1);
您正在并行覆盖这些值,例如:
Thread 1 'get' = 0
Thread 2 'get' = 0
Thread 1 'put 1'
Thread 2 'put 1'
将地图更改为:
static Map<Integer, AtomicInteger> mymap1 = new ConcurrentHashMap<>();
static {
//initialize to avoid null values and non-synchronized puts from different Threads
for(int i=0;i<10;i++) {
mymap1.put(i, new AtomicInteger());
}
}
....
//in your loop
for (int i = 0; i < arr.length; i++) {
AtomicInteger accumulator = mymap1.get(arr[i]);
accumulator.incrementAndGet();
}
编辑:上述方法的问题当然是mymap1的初始化。为了避免落入相同的陷阱,在循环中创建AtomicInteger并再次相互覆盖,需要使用值对其进行预填充
因为我觉得自己很慷慨,下面是使用Streams API的一些方法:
int totalEvenCount = Arrays.stream(arr).parallel().filter(i->i%2==0).reduce(0, Integer::sum);
int totalOddCount = Arrays.stream(arr).parallel().filter(i->i%2!=0).reduce(0, Integer::sum);
//or this to count by individual numbers:
ConcurrentMap<Integer,List<Integer>> map1 = Arrays.stream(arr).parallel().boxed().collect(Collectors.groupingByConcurrent(i->i));
map1.entrySet().stream().filter(e -> e.getKey()%2!=0).forEach(entry -> System.out.println(entry.getKey() + "=" + entry.getValue().size()));
作为读者的练习,也许您可以了解各种收集器是如何工作的,以便编写自己的countingByi->i%2=0以输出仅包含计数而不是值列表的映射。您考虑过Java 8+中的流API吗?是的,当然,无法通过它解决此问题。你能帮忙吗?如果你向我展示你现有的基于Streams API的代码,我会帮你的。但是,作为错误所在的提示,您正在并行地覆盖循环中map1中的值。使用ConcurrentHashMap d
oesn不能改变last put获胜的事实。您考虑过Java 8+中的流API吗?是的,当然,无法通过它解决这个问题。你能帮忙吗?如果你向我展示你现有的基于Streams API的代码,我会帮你的。但是,作为错误所在的提示,您正在并行地覆盖循环中map1中的值。使用ConcurrentHashMap不会改变last put获胜的事实。注意:为链接提供上下文-鼓励链接到外部资源,但请在链接周围添加上下文,以便您的其他用户了解它是什么以及为什么存在。始终引用重要链接的最相关部分,以防目标站点无法访问或永久脱机。注意:为链接提供上下文-鼓励链接到外部资源,但请在链接周围添加上下文,以便您的其他用户了解它是什么以及为什么存在。始终引用重要链接中最相关的部分,以防目标站点无法访问或永久脱机。但我想可能会有bug潜入其中,因为仍然会有并发写入冲突。-什么ConcurrentHashMap的要点是它是线程安全的,可以由多个线程同时写入。在高度多线程的环境中,在cocnurrentHashMap上写入可能不是100%安全的。对于有保证的写入行为,正确的方法是使用ConcurrentHashMap.putIfAbsentK键的原子性,V值方法,并注意返回值,该值指示put操作是否成功。简单的put可能不正确。看到“bug”了吧,get-then-put序列从来都不是原子的。它总是需要外部同步。好的,很好。很高兴更新答案以便更清楚。我已经更新了我的答案,如果有帮助,请升级投票并接受。但我想可能会出现错误,因为仍然会有并发写入冲突。-什么ConcurrentHashMap的要点是它是线程安全的,可以由多个线程同时写入。在高度多线程的环境中,在cocnurrentHashMap上写入可能不是100%安全的。对于有保证的写入行为,正确的方法是使用ConcurrentHashMap.putIfAbsentK键的原子性,V值方法,并注意返回值,该值指示put操作是否成功。简单的put可能不正确。看到“bug”了吧,get-then-put序列从来都不是原子的。它总是需要外部同步。好的,很好。很高兴更新答案以便更清楚。我已经更新了我的答案,如果有帮助,请投票并接受。谢谢你的错误。。已经编辑了程序,现在顺序和并行输出都是相同的。但是,并行执行所需的时间是原来的3倍多:已经添加了一些Streams API代码供您尝试并从中获得灵感。感谢您的回答。我现在明白你的方法了。它的清洁和功能。然而,它比命令式的方式慢。你知道y吗?@Dhananjay作为猜测,很可能是因为与Lamndas相关的开销。lamnda本身就是对象。编译器在编译时可以很容易地优化函数循环,但Lamnda对象创建/调用周期却不那么容易。这也取决于您如何进行计算——请注意,在我的代码中使用了boxed,它将所有整数转换为整数——这是一个很大的开销!您可以将数组创建为Integer[],并在测试中查看它的性能,因为将int与Integer进行比较是不公平的。感谢您的bug。。已经编辑了程序,现在顺序和并行输出都是相同的。但是,并行执行所需的时间是原来的3倍多:已经添加了一些Streams API代码供您尝试并从中获得灵感。感谢您的回答。我现在明白你的方法了。它的清洁和功能。然而,它比命令式的方式慢。你知道y吗?@Dhananjay作为猜测,很可能是因为与Lamndas相关的开销。lamnda本身就是对象。编译器在编译时可以很容易地优化函数循环,但Lamnda对象创建/调用周期却不那么容易。这也取决于您如何进行计算——请注意,在我的代码中使用了boxed,它将所有整数转换为整数——这是一个很大的开销!您可以将数组创建为Integer[],并查看它在测试中的性能,因为将int与Integer进行比较是不公平的。