【问题标题】:Multithreading--Why one thread is doing all of the work?多线程——为什么一个线程做所有的工作?
【发布时间】:2012-05-14 14:43:44
【问题描述】:

我正在使用两个线程将两个矩阵相乘(但是,该程序也是为按比例编写的,因此我可以使用三个、四个等线程代替)。每个线程计算/完成最终矩阵的一行(或列)的工作。如果一个线程在一行上工作,那么其他线程不应该在该行上工作。它/他们应该移动到下一个可用行。

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

其次,按照我的做法,每次我测试它(使用不同大小的矩阵——甚至是巨大的矩阵)时,只有一个线程可以完成这项工作。也就是说,每次都是同一个线程访问 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++;
            }
        }
    }
} 

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

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);
        }
    }

我想我的问题是双重的——我的方法对于手头的问题是否正确?如果是这样,(如果不是),为什么一个线程总是抓住锁并完成所有工作?我在 20x20 矩阵上检查了最多 6 个线程的程序,并且始终只有一个线程在做这项工作。

【问题讨论】:

  • 我一直这样做:启动线程,然后立即等待它们完成工作。这是我在教科书中看到的方法……您有其他建议吗?
  • 关于四把锁的评论,不应该是这样。只创建了一个 MyRunnable 实例 (r)。我在线程构造函数中将单个实例传递给每个线程,因此所有线程应该只有一个锁。如果这不正确,请告诉我。
  • @whistler 你是对的。锁。问题是你用synchronized(this) { 锁定了整个区域。这意味着一次只有一个线程可以输入代码,也意味着一次只有一个线程可以计算
  • 我也不是 Java 专家 :) 但我很确定 (99%) join() 不是这种情况。
  • 一个(长)建议:(1)如果这将是您应用程序中的常见操作,则使用某种类型的执行器以避免创建新线程的开销; (2) 使用 Callable 而不是 Runnable 并让 callable 接受一行和一列,然后返回该操作的结果; (3) 将操作所需的所有Callables提交给Executor; (4) 让主线程将每个可调用的结果组合成最终结果。

标签: java multithreading synchronized matrix-multiplication


【解决方案1】:

正如一些 cmets 所建议的,问题在于锁定(即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
  }
}

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

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

//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);
  }
}

这种方法更简洁,因为工作分配主要完成 线程(外部for循环),因此不需要同步它。

在使用线程池时,您还可以获得一些好处:

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

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

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

有点长,但希望它能解决一些问题。

【讨论】:

  • 谢谢...感谢您提供有关 Java 并发 API 的额外信息。这对我来说是一个陌生的领域,我真的想了解更多。你给了我一个很好的起点!
  • 很高兴我能帮上忙。祝你好运!
【解决方案2】:

正如 mru 在他的评论中已经说明的那样,您的问题是所有行计算都是在“同步(this)”块内执行的。因此,所有线程都将等待一行被处理,然后再开始下一行,并且始终获取锁的同一线程可能是优化的结果,因为您几乎是单线程进行计算的。您可以考虑只决定在同步块内处理哪一行:

int rowToProcess;
synchronized (this) {
    if (i < number of columns in final matrix){
        rowToProcess = i;
        i++;
        }
    else
        return;
    }

【讨论】:

  • 你会按顺序分配 i 吗(如果是,你会在哪里增加它?)。我将在我的 run 方法中尝试这个,并让你知道它是如何工作的。谢谢。
  • 对不起,我有点搞砸了。您应该在将 i 分配给“rowToProcess”后立即增加它 - 修正了我的答案。还要确保“rowToProcess”变量不在线程之间共享。
【解决方案3】:

线程调度取决于特定的 VM 实现。在某些实现中,线程将继续运行,直到它以某种方式阻塞或被更高优先级的线程抢占。在您的情况下,所有线程都具有相同的优先级,因此进入 synchronized 块的第一个线程永远不会阻塞,它不会被抢占。一些调度程序实现了优先级老化,因此饥饿的线程最终会增加优先级,但您可能运行的时间不够长,无法产生效果。

synchronized 块的末尾添加一个Thread.yield() 调用。这告诉调度程序选择一个新线程来运行(可能是同一个,但可能是不同的)。

【讨论】:

    【解决方案4】:

    您的问题是您将整个区域与synchronized(this) 同步。这意味着一次只允许一个线程进入循环进行计算。当然,这可能意味着多个线程可以计算不同的部分,但不能同时计算多个线程。这也意味着您的“并行”解决方案并不比一个线程快。

    如果您想并行计算,请查看应该涵盖主题的 Parallel Matrix Multiplication in Java 6Fork Join Matrix Multiplication in Java

    【讨论】:

    • 该代码比我想要做的要复杂一些。我还在学习,这超出了我的想象。我已经完成了一个简单的多线程矩阵 mult 程序,其中 m x n 个线程分别计算产品中的每个元素。现在,关键是只使用 2 个线程来完成工作(当一个线程在一行或一个元素上工作时,另一个应该在另一行上工作)。有人发表评论说我的解决方案并不比单线程更好——我认为他是正确的。但是,如何解决它,我仍然不清楚。
    • @whistler 看看 lifelongcoug 第 2 部分的评论。您基本上必须拆分工作,为您的工作线程提供必要的参数,然后再加入。
    【解决方案5】:

    您的 run 函数让第一个获得锁的线程在仍然拥有锁的情况下连续完成所有工作。对于下一行,也许另一个线程会获得锁,但它会阻塞所有其他线程,直到它完成。

    我要做的是拥有一个与行数相同的布尔数组,并使用它们来声明处理每一行的任务。类似于以下伪代码:

    //before creating the threads, pre-fill BoolList with trues
    function run()
    {
      while (true)
      {
        lock(BoolList)
        {
          //find first true value and set it to false
          //if no true found, return
        }
        //do the actual math of multiplying the row we claimed above
      }
    }
    

    另外请记住,创建一个新线程的开销已经足够多线程这个程序只对大型矩阵是值得的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-04-08
      • 2012-01-09
      • 1970-01-01
      相关资源
      最近更新 更多