多线程 - 为什么一个线程正在完成所有工作?

whi*_*ler 3 java multithreading synchronized matrix-multiplication

我使用两个线程将两个矩阵相乘(但是,程序也是为了扩展而编写的,因此我可能会使用三个,四个等等线程).每个线程计算/完成最终矩阵的一行(或列)的工作.如果一个线程在一行上工作,另一个线程不应该在该行上工作.它/它们应该移动到下一个可用行.

首先,我不确定我实施问题的方式是否正确.如果你能看到更好的方法,请告诉我.

其次,我每次测试它的方式(使用不同大小的矩阵 - 甚至是巨大的矩阵),只有一个线程完成工作.也就是说,每次,同一个线程都可以访问run()方法的synchronized块.其他线程正在进入run()方法,但为什么只有一个线程总是获得锁定并完成所有工作?

这是我的run方法:

 public void run() {
    System.out.println(Thread.currentThread().getName());
    while (i < number of columns in final matrix) {
        synchronized (this) {
            if (i < number of columns in final matrix) {
                for (int j = 0; j < Main.B[0].length; j++) { 
                    for (int k = 0; k < Main.A[0].length; k++) { 
                        Main.C[i][j] += Main.A[i][k] * Main.B[k][j];
                    }
                }
                i++;
            }
        }
    }
} 
Run Code Online (Sandbox Code Playgroud)

这是我的驱动程序类中的代码,它创建线程并启动程序:

MyRunnable r = new MyRunnable();
Thread thread1 = new Thread(r);
Thread thread2 = new Thread(r);
thread1.start();
thread2.start();

try {
    thread1.join();
    thread2.join();
    } catch (InterruptedException ie) {
        System.out.println("\nThe following error occurred: " + ie);
        }
    }
Run Code Online (Sandbox Code Playgroud)

我想我的问题是双重的 - 我的方法是否适合手头的问题?如果是这样,(如果没有),为什么一个线程总是抓住锁并完成所有工作?我已经在20x20矩阵上检查了最多6个线程的程序,并且总是只有一个线程正在进行工作.

rod*_*ion 5

正如一些评论所暗示的那样,问题在于锁定(即synchronized(this)部分).this在您的情况下,执行同步的单个实例MyRunnable,因此当一个线程在synchronized块内执行工作时,所有其他线程将等待直到工作完成.如此有效,一次只有一个线程正在进行实际工作.

这是解决问题的方法.由于您需要并行处理不同行的线程,因此不能通过锁同步此工作(因为锁定意味着相反:一次只能有一个线程完成工作).什么,你需要同步是其中每个线程决定将工作在哪一行的一部分.

这是一个示例伪代码:

public void run(){
  int workRow;
  synchronized(this){
    workRow = findNextUnprosessedRow();
  }
  for(int i=0; i<matrix[workRow].length; i++){
    //do the work
  }
}
Run Code Online (Sandbox Code Playgroud)

请注意,由于上述原因,实际工作有意同步.

你使用线程的方式是正确的,所以没有问题,但是,我建议你看一下Java的并发API:线程池.以下是如何在您的上下文中使用它的示例:

//Creates a pool of 5 concurrent thread workers
ExecutorService es = Executores.newFixedThreadPool(5);

//List of results for each row computation task
List<Future<Void>> results = new ArrayList<Future<Void>>();
try{
  for(int row=0; row<matrix.length; row++){
    final int workRow = row;

    //The main part. You can submit Callable or Runnable
    // tasks to the ExecutorService, and it will run them
    // for you in the number of threads you have allocated.
    // If you put more than 5 tasks, they will just patiently
    // wait for a task to finish and release a thread, then run.
    Future<Void> task = es.submit(new Callable<Void>(){
      @Override
      public Void call(){
        for(int col=0; col<matrix[workRow].length; col++){
          //do something for each column of workRow
        }
        return null;
      }
    });
    //Store the work task in the list.
    results.add(task);
  }
}finally{
  //Make sure thread-pool is shutdown and all worker
  //threads are released. 
  es.shutdown();
}

for(Future<Void> task : results){
  try{
    //This will wait for threads to finish. 
    // i.e. same as Thread.join()
    task.get();
  }catch(ExecutionException e){
    //One of the tasks threw an exception!
    throw new RuntimeException(e);
  }
}
Run Code Online (Sandbox Code Playgroud)

这种方法更加清晰,因为工作分配是在主线程(外部for循环)完成的,因此不需要同步它.

使用线程池时,您也获得了一些奖励:

  • 它很好地处理了每个线程计算过程中的任何异常.使用裸线时,就像在你的方法中一样,很容易"丢失"异常.

  • 线程被合并.也就是说,它们会自动重用,因此您无需担心产生新线程的成本.这在你的情况下特别有用,因为你需要在矩阵中每行产生一个线程,这可能相当大,我怀疑.

  • 提交的任务ExecutorService包含在一个有用的Future<Result>对象中,这在每个计算任务实际返回某种结果时最有用.在您的情况下,如果您需要总结矩阵中的所有值,则每个计算任务都可以返回该行的总和.然后你只需要总结一下.

有点长,但希望它清除一些东西.