【问题标题】:Thread Scheduling - Shared Array线程调度 - 共享数组
【发布时间】:2012-11-25 15:06:58
【问题描述】:

我需要两个线程来编写一个共享整数数组。两个线程都需要写入该数组的所有元素。每个线程将写入 1 或 7,结果应类似于 171717171(或 71717171)。为此,我将第一个 Thread1 写入位置 0,然后等待。 Thread2 现在写入位置 0 和 1,通知 Thread1,然后等待。 Thread1 在位置 1 和 2 写入,通知 Thread2 并等待,等等。使用以下代码,我得到正确的输出,尽管使用 JPF 运行时发现死锁。它变得非常令人沮丧,因为我找不到它有什么问题。任何意见,将不胜感激。

import java.util.logging.Level;
import java.util.logging.Logger;


public class WriterThreadManager {

    private int[] array = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
    private Thread thread7;
    private Thread thread1;

    public static void main(String[] args) {
        WriterThreadManager mng = new WriterThreadManager();
        mng.exec();

    }

    public WriterThreadManager() {
        thread7 = new Thread(new WriterRunnable(this, 7));
        thread1 = new Thread(new WriterRunnable(this, 1));
    }

    public void overwriteArray(int pos, int num) {
        array[pos] = num;
        printArray();
    }

    private  void printArray() {
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i]);
        }
        System.out.println("");
    }

    public synchronized void stopThread() {
        try {
            this.wait();
        } catch (InterruptedException ex) {
            Logger.getLogger(WriterThreadManager.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public synchronized void wakeUpThread() {
        notifyAll();
    }

    private void exec() {
        thread7.start();
        thread1.start();
    }

    public int length() {
        return array.length;
    }
}



public class WriterRunnable implements Runnable {

    private WriterThreadManager mng;
    private int numberToWrite;
    private static boolean flag = true;

    @Override
    public void run() {
        int counter = 0;
        int j = 0;

        //first thread to get in should write only at 
        //position 0 and then wait.
        synchronized (mng) {
            if (flag) {
                flag = false;
                mng.overwriteArray(0, numberToWrite);
                j = 1;
                waitForOtherThread();
            }
        }
        for (int i = j; i < mng.length(); i++) {
            mng.overwriteArray(i, numberToWrite);
            counter++;
            if (i == mng.length() - 1) {
                mng.wakeUpThread();
                break;
            }
            if (counter == 2) {
                waitForOtherThread();
                counter = 0;
            }
        }
    }

    private void waitForOtherThread() {
        mng.wakeUpThread();
        mng.stopThread();
    }

    public WriterRunnable(WriterThreadManager ar, int num) {
        mng = ar;
        numberToWrite = num;
    }
}

ps:执行示例:

1000000000
7000000000
7700000000
7100000000
7110000000
7170000000
7177000000
7171000000
7171100000
7171700000
7171770000
7171710000
7171711000
7171717000
7171717700
7171717100
7171717110
7171717170
7171717177
7171717171

来自 JPF 的错误快照如下:

thread java.lang.Thread:{id:1,name:Thread-1,status:WAITING,priority:5,lockCount:1,suspendCount:0}
  waiting on: WriterThreadManager@152
  call stack:
    at java.lang.Object.wait(Object.java)
    at WriterThreadManager.stopThread(WriterThreadManager.java:43)
    at WriterRunnable.waitForOtherThread(WriterRunnable.java:53)
    at WriterRunnable.run(WriterRunnable.java:45)

thread java.lang.Thread:{id:2,name:Thread-2,status:WAITING,priority:5,lockCount:1,suspendCount:0}
  waiting on: WriterThreadManager@152
  call stack:
    at java.lang.Object.wait(Object.java)
    at WriterThreadManager.stopThread(WriterThreadManager.java:43)
    at WriterRunnable.waitForOtherThread(WriterRunnable.java:53)
    at WriterRunnable.run(WriterRunnable.java:45)

【问题讨论】:

  • 是否要求严格依次进行写入?这似乎是使用信号量的经典案例。
  • 好吧,您在 3 个不同的对象(管理器和两个可运行对象)上进行同步。您不会在检查条件的循环内等待()。并且对数组的每次访问都不同步。绝对使用更高级别的抽象(例如信号量)来同步您的线程。
  • 既然是功课,OP可能会受限于他能用的东西。
  • @Perception 我只熟悉信号量的概念,并不熟悉 Java 中的具体实现。关于轮流写入:由于两个线程都需要在数组中的每个位置写入,这是我找到的唯一解决方案。
  • @Giannis JB Nizet 的 cmets 确实适用,即使您坚持解决问题的低级方法(不同的显示器、等待时没有循环、缺乏同步......)

标签: java multithreading jpf


【解决方案1】:

我相信比赛是由于这种方法:

private void waitForOtherThread() {
    mng.wakeUpThread();
    mng.stopThread();
}

虽然单独的 wakeUpThread()stopThread() 方法是同步的,但您有机会在这些调用之间进行意外的线程调度。

考虑:

thread7 - notify thread1 to wakup
thread1 - wake up
thread1 - work to completion
thread1 - notify thread7 to wakeup
thread1 - wait to be notified to wakeup
thread7 - wait to be notified to wakeup

在这种情况下,你已经死锁了,因为 thread1 在 thread7 有机会 wait() 之前发送了它的 notifyAll()。

在不同的环境中跑步可能会打乱你的时间并导致出现这些类型的行为。

为避免这种情况,我建议这样做:

private void waitForOtherThread() {
    synchronized(mng) {
        mng.wakeUpThread();
        mng.stopThread();
    }
}

或者更好的是,使用@KumarVivekMitra 建议的信号量。信号量结合了通知系统和计数器,因此通知和等待的顺序无关紧要。

【讨论】:

  • 我尝试同步方法声明,但没有尝试阻止...谢谢!我一定要检查信号量。
  • 是的,方法同步在那里不起作用,因为它在 Runnable 类上,这意味着每个线程都有一个单独的实例。他们需要在一个共同的对象上同步; mng 在这种情况下。
【解决方案2】:

-我认为这里更好的方法是java.util.Semaphores,它可以帮助您一次通过特定数量的线程来决定对对象资源的访问。

- 那么你也可以使用SingleThreadExecutor 来解决这个问题,它会在进入第二个任务之前启动并完成一个任务,因此不需要同步从你这边来。

【讨论】:

    【解决方案3】:

    我认为您在这里不需要任何形式的协调。只需让一个线程写入偶数位置,另一个线程写入奇数位置。让他们俩尽可能快地走。完成!

    【讨论】:

    • 这是两个线程都需要在数组的每个位置上写入的要求的一部分。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多