【问题标题】:How to synchronize multithreaded app in one class, without worker threads and controller?如何在一个类中同步多线程应用程序,没有工作线程和控制器?
【发布时间】:2018-02-18 18:24:58
【问题描述】:

我有多个线程粗略地计算给定数组的总和。我不能使用单独的工作线程和控制器,而是整个程序必须写在一个类中。该程序的重​​点是打印计算结果,线程 id 0 是第一个打印结果,然后是线程 id 1,然后是线程 id 2,依此类推。

我在类中编写了两个同步方法:finished - 在特定线程完成计算后通知所有线程,waitForThread - 如果 id 小于其自己的线程仍未完成,则等待。

因为我不能使用单独的控制器类,所以我决定创建一个静态数组doneArr,其中每个单元格代表一个线程ID,一旦线程完成其工作,它就会更新doneArr 中的相应单元格。这样waitForThread 就知道何时等待。

我似乎无法让它工作,你能指出正确的方向吗?

编辑:使用join() 可以轻松解决问题,但我正在寻找使用同步的解决方案。

这是代码:

public class WorkThread extends Thread {
    private int[] vec;
    private int id;
    private int result;
    private static int[] doneArr;
    private static int progress = 0;

    public WorkThread(int[] vec, int id) {
        this.vec = vec;
        this.id = id;
        doneArr = new int[vec.length];
        for(int i = 0; i < doneArr.length; i++) {
            doneArr[i] = -1; //default value
        }
    }
    private boolean finishedUpToMe() {
        for(int i = 0; i < id; i++) {
            if(doneArr[i] == -1)
                return false;
        }
        return true;
    }
    private synchronized void waitForThread() throws InterruptedException {
        while(!finishedUpToMe() && id > 0)
            this.wait();
        progress++;
    }
    private synchronized void finished() throws IllegalMonitorStateException {
        this.notifyAll();
    }
    public int process(int[] vec, int id) {
        int result = 0;
        System.out.format("id = %d\n", this.id);
        for(int i = 0; i < vec.length; i++) {
            vec[i] = vec[i] + 1;
            result = result + vec[i];
        }
        return result;
    }
    public void run() {
        try {
            this.waitForThread();
        }catch (InterruptedException e) {
            System.out.println("waitForThread exception");
        }
        result = process(vec, id);
        doneArr[id] = id;
        System.out.format("id = %d, result = %d\n", id, result);
        try{
            this.finished();
        }catch (IllegalMonitorStateException e) {
            System.out.format("finished exception\n");
        }
    }
    public static void main(String[] args) {
        int[] vec = {1,2,3,4};
        WorkThread[] workers = new WorkThread[3];
        for(int i = 0; i < workers.length; i++) {
            workers[i] = new WorkThread(vec, i);
        }
        for(int i = 0; i < workers.length; i++) {
            workers[i].start();
        }
        System.out.format("main\n");
    }
}

【问题讨论】:

  • "整个程序必须在一个类中编写"....请问原因?为什么要摆出这么奇怪的限制来挑战自己?
  • 这是一个非常奇怪的限制。但是,它实际上不应该有太大的区别,因为您不需要控制器来完成如此简单的任务。如果您真的想要一个,您可以简单地使用线程池或普通线程对象;它们都采用 runnables 或等效的 lambdas。基本上,您只需编写控制器类并让它分派工作人员,而不是像您那样对 Thread 进行子类化。
  • @user69513 这是我想要做的,在processrun 中编写所有逻辑,但我的同步不正常。
  • @Yos 没看太仔细,但是为什么每个线程都要单独初始化静态成员doneArr呢?
  • @user69513 因为我需要某种全局变量来跟踪哪些线程完成了他们的工作。

标签: java multithreading


【解决方案1】:

您反复初始化同一个静态成员,虽然在这里无害,但这是一种相当糟糕的做法。相反,使用静态初始化块来初始化静态变量(而不是构造函数)。

private static final int[] doneArr;
static {
    for(...) {...}
}

当然,在这种情况下,您会遇到长度可变的问题。我还注意到doneArr 包含整数,但您只需将其用作标志(即-1 或!-1)。所以改为使用Map&lt;Integer, Boolean&gt;

private static final Map<Integer, Boolean> threadIsDone = new HashMap<>();

public WorkThread(int[] vec, int id) {
    threadIsDone.put(id, false);
    ...
}

一旦你这样做了,虽然在这种特殊情况下不是绝对必要的,但在检查它时防止缺失值仍然是一种很好的做法。

private boolean finishedUpToMe() {
    for(int i = 0; i < id; i++) {
        final Boolean threadDone = threadIsDone.get(i);
        if (threadDone != null && !threadDone)
            return false;
    }
    return true;
}

至于您的线程问题,您似乎使用了声明为同步的方法。这对于共享对象上的方法非常有用,但这实际上并不是您所拥有的——您有多个唯一的工作对象。因此,您实际上并没有在同一个对象上同步。这意味着:

  • JVM 不必使写入对其他线程可见。
  • 如果写入确实可见,则说明存在竞争条件。
  • notifyAll() 的调用实际上并没有完成任何事情。

改为使用声明为static final 的锁对象。

private static final Object lockObj = new Object();

private void waitForThread() {
    synchronized(lockObj) {...}
}

private void finished() {
    synchronized(lockObj) {...}
}

编辑:其他想法

  • 如果您声明的变量更接近于它们的实际使用位置(即紧邻相应方法的上方),您的代码将更具可读性。
  • 您的process(...) 方法是一个实例方法,但将引用它已经有权访问的实例变量作为参数。
  • 如果您不打算修改变量,则应声明变量final,为variety of reasons

【讨论】:

  • 感谢您的反馈!我想澄清一下使用lockObj 我的新waitForThread 看起来像这样:private void waitForThread() { synchronized(lockObj) {while(!finishedUpToMe() &amp;&amp; id &gt; 0) this.wait(); progress++;} }
  • @Yos 是的,但是this.wait() 也变成了lockObj.wait()。 Java 中的同步方法等效于synchronized(this) {...}wrapping the entire method,并且您将this 替换为静态成员lockObj。当然this.notifyAll() 变成lockObj.notifyAll() 等等。
猜你喜欢
  • 2012-01-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-27
  • 1970-01-01
  • 1970-01-01
  • 2014-08-13
相关资源
最近更新 更多