我是Java的新手,并尝试编写一个方法,在长数据的2D数组中找到最大值。
该方法在单独的线程中搜索每一行,并且线程保持共享的当前最大值。每当线程找到大于其本地最大值的值时,它会将此值与共享本地最大值进行比较,并根据需要更新其当前本地最大值和可能的共享最大值。我需要确保实现适当的同步,以便无论如何计算交错,结果都是正确的。
我的代码冗长而混乱,但对于初学者来说,我有这个功能:
static long sharedMaxOf2DArray(long[][] arr, int r){
MyRunnableShared[] myRunnables = new MyRunnableShared[r];
for(int row = 0; row < r; row++){
MyRunnableShared rr = new MyRunnableShared(arr, row, r);
Thread t = new Thread(rr);
t.start();
myRunnables[row] = rr;
}
return myRunnables[0].sharedMax; //should be the same as any other one (?)
}
对于改编的runnable,我有这个:
public static class MyRunnableShared implements Runnable{
long[][] theArray;
private int row;
private long rowMax;
public long localMax;
public long sharedMax;
private static Lock sharedMaxLock = new ReentrantLock();
MyRunnableShared(long[][] a, int r, int rm){
theArray = a;
row = r;
rowMax = rm;
}
public void run(){
localMax = 0;
for(int i = 0; i < rowMax; i++){
if(theArray[row][i] > localMax){
localMax = theArray[row][i];
sharedMaxLock.lock();
try{
if(localMax > sharedMax)
sharedMax = localMax;
}
finally{
sharedMaxLock.unlock();
}
}
}
}
}
我认为使用锁是一种安全的方法,可以防止多个线程一次弄乱sharedMax
,但是在同一输入上测试/比较非并发最大查找函数,我发现结果不正确。我认为问题可能来自我刚才说的事实
...
t.start();
myRunnables[row] = rr;
...
sharedMaxOf2DArray
函数中的。在我将它放入myRunnables数组之前,可能需要完成一个给定的线程;否则,我会“捕获”错误的sharedMax?或者是别的什么?我不确定事情的时间安排..
答案 0 :(得分:1)
来自JavaDocs:
public interface Callable
返回结果的任务 抛出一个例外。实现者定义一个没有的方法 称为呼叫的参数。
Callable接口类似于Runnable,两者都是 设计用于其实例可能由其执行的类 另一个线程。但是,Runnable不会返回结果 不能抛出一个检查过的异常。
好吧,您可以使用Callable从一个1darray计算结果,并等待ExecutorService结束。您现在可以比较Callable的每个结果以获取最大值。代码可能如下所示:
Random random = new Random(System.nanoTime());
long[][] myArray = new long[5][5];
for (int i = 0; i < 5; i++) {
myArray[i] = new long[5];
for (int j = 0; j < 5; j++) {
myArray[i][j] = random.nextLong();
}
}
ExecutorService executor = Executors.newFixedThreadPool(myArray.length);
List<Future<Long>> myResults = new ArrayList<>();
// create a callable for each 1d array in the 2d array
for (int i = 0; i < myArray.length; i++) {
Callable<Long> callable = new SearchCallable(myArray[i]);
Future<Long> callResult = executor.submit(callable);
myResults.add(callResult);
}
// This will make the executor accept no new threads
// and finish all existing threads in the queue
executor.shutdown();
// Wait until all threads are finish
while (!executor.isTerminated()) {
}
// now compare the results and fetch the biggest one
long max = 0;
for (Future<Long> future : myResults) {
try {
max = Math.max(max, future.get());
} catch (InterruptedException | ExecutionException e) {
// something bad happend...!
e.printStackTrace();
}
}
System.out.println("The result is " + max);
你的Callable:
public class SearchCallable implements Callable<Long> {
private final long[] mArray;
public SearchCallable(final long[] pArray) {
mArray = pArray;
}
@Override
public Long call() throws Exception {
long max = 0;
for (int i = 0; i < mArray.length; i++) {
max = Math.max(max, mArray[i]);
}
System.out.println("I've got the maximum " + max + ", and you guys?");
return max;
}
}
答案 1 :(得分:1)
我不确定这是否是拼写错误,但您的Runnable
实现将sharedMax
声明为实例变量:
public long sharedMax;
而不是共享的:
public static long sharedMax;
在前一种情况下,每个Runnable都有自己的副本,不会“看到”其他人的值。将其改为后者应该有所帮助。或者,将其更改为:
public long[] sharedMax; // array of size 1 shared across all threads
现在您可以在循环外创建一个大小为1的数组,并将其传递给每个Runnable以用作共享存储。
作为旁白:请注意,由于每个线程通过对其循环的每次迭代持一个锁来检查公共sharedMax值,因此会有巨大的锁争用。这可能会导致性能不佳。你必须衡量,但我猜测让每个线程找到最大行然后运行最后一遍以找到“max of maxes”可能实际上可比较或更快。
答案 2 :(得分:1)
您的代码存在严重的锁争用和线程安全问题。更糟糕的是,它实际上并没有等待任何线程在return myRunnables[0].sharedMax
之前完成,这是一个非常糟糕的竞争条件。此外,通过ReentrantLock
或甚至synchronized
块显式锁定通常是错误的处理方式,除非您实现低级别(例如您自己的/新的并发数据结构)
这是一个使用Future
并发原语和ExecutorService
来处理线程创建的版本。一般的想法是:
ExecutorService
Future
从submit(...)
返回的List
添加到get()
Future
上调用import java.util.concurrent.*;
import java.util.*;
public class PMax {
public static long pmax(final long[][] arr, int numThreads) {
ExecutorService pool = Executors.newFixedThreadPool(numThreads);
try {
List<Future<Long>> list = new ArrayList<Future<Long>>();
for(int i=0;i<arr.length;i++) {
// put sub-array in a final so the inner class can see it:
final long[] subArr = arr[i];
list.add(pool.submit(new Callable<Long>() {
public Long call() {
long max = Long.MIN_VALUE;
for(int j=0;j<subArr.length;j++) {
if( subArr[j] > max ) {
max = subArr[j];
}
}
return max;
}
}));
}
// find the max of each slice's max:
long max = Long.MIN_VALUE;
for(Future<Long> future : list) {
long threadMax = future.get();
System.out.println("threadMax: " + threadMax);
if( threadMax > max ) {
max = threadMax;
}
}
return max;
} catch( RuntimeException e ) {
throw e;
} catch( Exception e ) {
throw new RuntimeException(e);
} finally {
pool.shutdown();
}
}
public static void main(String args[]) {
int x = 1000;
int y = 1000;
long max = Long.MIN_VALUE;
long[][] foo = new long[x][y];
for(int i=0;i<x;i++) {
for(int j=0;j<y;j++) {
long r = (long)(Math.random() * 100000000);
if( r > max ) {
// save this to compare against pmax:
max = r;
}
foo[i][j] = r;
}
}
int numThreads = 32;
long pmax = pmax(foo, numThreads);
System.out.println("max: " + max);
System.out.println("pmax: " + pmax);
}
}
的列表并汇总结果此版本的另一个好处是工作线程之间没有锁争用(或一般锁定),因为每个只返回其数组切片的最大值。
ExecutorService
加分:如果您反复调用此方法,那么将{{1}}创建从方法中拉出并让它在调用之间重复使用可能是有意义的。
答案 3 :(得分:0)
嗯,这肯定是一个问题 - 但如果没有更多的代码,很难理解它是否是唯一的问题。
thread[0]
(以及此sharedMax
的读取)与其他线程中sharedMax
的修改之间基本上有race condition。
想想如果调度程序决定不让任何线程暂时运行会发生什么 - 所以当你完成创建线程时,你将返回答案而不修改它甚至一次! (当然还有其他可能的情况......)
你可以在回复答案之前通过join()
所有线程克服它。