搜索具有多个线程的数组时出现Java锁定/并发问题

时间:2012-12-06 05:44:58

标签: java multithreading concurrency locking

我是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?或者是别的什么?我不确定事情的时间安排..

4 个答案:

答案 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来处理线程创建的版本。一般的想法是:

  1. 向您的ExecutorService
  2. 提交一系列并发作业
  3. Futuresubmit(...)返回的List添加到get()
  4. 遍历每个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); } } 的列表并汇总结果
  5. 此版本的另一个好处是工作线程之间没有锁争用(或一般锁定),因为每个只返回其数组切片的最大值。

    ExecutorService

    加分:如果您反复调用此方法,那么将{{1}}创建从方法中拉出并让它在调用之间重复使用可能是有意义的。

答案 3 :(得分:0)

嗯,这肯定是一个问题 - 但如果没有更多的代码,很难理解它是否是唯一的问题。

thread[0](以及此sharedMax的读取)与其他线程中sharedMax的修改之间基本上有race condition

想想如果调度程序决定不让任何线程暂时运行会发生什么 - 所以当你完成创建线程时,你将返回答案而不修改它甚至一次! (当然还有其他可能的情况......)

你可以在回复答案之前通过join()所有线程克服它。