Java中的多线程矩阵相乘

4
我正在尝试构建一个程序,用于使用a*d个线程(将用于打印完成矩阵中一个索引的总和)来相乘两个矩阵(A [a,b],B [c,d])。 为此,我正在使用一个“监视器”类作为控制器来同步线程之间的操作,“乘法器”类来表示单个线程以及主程序类。 我的想法是线程将进行计算,并且当线程(0,0)打印出它的总和时,它将向下一个线程发信号。 由于某种原因,在打印第一个索引后,所有线程都处于等待模式,不会测试我的条件。 您能够查看我的代码并告诉我错误在哪里吗?
监视器类:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

final class Monitor {
    private Lock lock;
    int index;
    Condition cond; 
    public Monitor () {
        lock = new ReentrantLock();
        cond = lock.newCondition();
        this.index = 0;
    }

    public synchronized void finished(int x, double sum) throws InterruptedException {
        lock.lock();
        if(index != x) {
            while(index != x) cond.await();
            System.out.printf("%9.2f ",sum);
            index++;
            lock.unlock();
            cond.signalAll();
          }
        else {
            System.out.printf("%9.2f ",sum);
            index++;
            try { lock.unlock(); }
            catch (java.lang.IllegalMonitorStateException e) {};
            try { lock.unlock(); }
            catch (java.lang.IllegalMonitorStateException e) {};
        }
        if(index % 5 == 0) System.out.println();
    }
}

乘数:

public class Multiplier extends Thread {
    private int index;
    private double [] vectorOne;
    private double [] vectorTwo;
    private Monitor monitor;
    private double sum;

    //constructor
    public Multiplier(int index, Monitor monitor,double [] vectorOne,double [] vectorTwo) {
        this.index = index;
        this.monitor = monitor;
        this.vectorOne = vectorOne;
        this.vectorTwo = vectorTwo;
    }

    public void VecMulti() {
        sum = 0;
        for (int i = 0 ; i < vectorOne.length ; i++) 
            sum += vectorOne[i] * vectorTwo[i];
    }

    public double getSum() {
        return sum;
    }

    public void run() {
        VecMulti();
        try {
            monitor.finished(index, sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

主要类:

public class MatrixMultiTest {
    public static void main(String[] args) {
        Monitor monitor = new Monitor(3*5);
        Matrix A = Matrix.random(3,4);
        Matrix B = Matrix.random(4,5);
        System.out.println("Matrix No1");
        A.show();
        System.out.println();
        System.out.println("Matrix No2");
        B.show();
        System.out.println();
        System.out.println("Multi Matrix");

        for (int i = 0; i < 3; i++)
            for (int j = 0; j < 5; j++) {
                Multiplier myThr = new Multiplier(i*5+j,
                        monitor,A.getRow(i),B.getCol(j));
                myThr.start();
                try {
                    myThr.join();
                } catch (InterruptedException e) {
                //  TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
    }
}

while(index != x) 这个循环永远不会停止,因为方法参数x和index永远不会改变,你需要检查一个在所有线程之间共享的变量,而且在这里Java是按值传递的。 - zapl
@zapl "monitor"上的索引不可以被认为是所有线程共享的值吗?每当一个线程运行“finished”时,它就会发生变化。 - Eddie Romanenco
哦,糟糕,index 确实是一个共享变量。我会尝试使用 new Multiplier(i*5+j..,否则你的索引就会出问题。 - zapl
让我来打个赌吧:如果你将你的多线程解决方案与一个简单的单线程解决方案进行基准测试,那么在尺寸约为10*10左右时,你的解决方案会慢得多...因为你引入了巨大的操作开销。 - Ralf Kleberhoff
@RalfKleberhoff 或许是这样,但这不是重点。我在这个任务上并不追求效率。 - Eddie Romanenco
@EddieRomanenco 作为多线程练习,这是一个不错的选择。加油! - Ralf Kleberhoff
1个回答

0

finished()方法存在很多问题:

  • 第一个问题是synchronized关键字。必须将其删除。使用此关键字,如果第一个进入的线程具有非零索引,则程序将死锁 - 线程将永远被停放等待条件信号,但这个信号永远不会到来,因为没有其他线程可以进入finished()方法。

  • 第二个错误在于else块:

else {
    System.out.printf("%9.2f ",sum);
    index++;
    try { lock.unlock(); }
    catch (java.lang.IllegalMonitorStateException e) {};
    try { lock.unlock(); }
    catch (java.lang.IllegalMonitorStateException e) {};
}

它从不调用cond.signalAll(),因此在具有index=0的线程通过后,其他线程将永远停留在等待状态。

  • 第三个问题是,在if(index != x) {..块中,cond.signalAll()lock.unlock()的顺序错误:
lock.unlock();
cond.signalAll();

ConditionsignalAll() 方法 文档 中指出:

实现可能(并且通常)要求当前线程在调用此方法时持有与此 Condition 相关联的锁。实现必须记录此前提条件以及如果未持有锁时采取的任何操作。通常会抛出诸如 IllegalMonitorStateException 的异常。

这两行代码必须交换顺序,否则将抛出 IllegalMonitorStateException 异常。

该方法的工作版本可能如下所示:

public void finished(int x, double sum) throws InterruptedException {
    try {
        lock.lock();
        while (index != x) {
            cond.await();
        }
        System.out.printf("%9.2f ", sum);
        index++;
        cond.signalAll();
    } finally {
        lock.unlock();
    }
    if (index % 5 == 0) System.out.println();
}

有趣的是,尽管存在所有这些错误,但 OP 提供的代码实际上仍然有效,但这仅归功于 MatrixMultiTest 类中的这个代码块:
try {
    myThr.join();
} catch (InterruptedException e) {
//  TODO Auto-generated catch block
    e.printStackTrace();
}

每个线程都是按顺序创建、启动和加入的,因此在任何时刻只有一个线程尝试获取同步的finished()方法上的隐式锁,并且i*5+j索引值确保线程按其索引顺序依次获取此隐式锁:0、1、2等。这意味着在方法内部,index始终等于x,并且每个线程都通过finished()中的else块,从而使程序完成执行。cond.await()实际上永远不会被调用。
如果移除join块,则可能会打印一些值,但程序最终将发生死锁。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接