【问题标题】:Java thread causes deadlockJava线程导致死锁
【发布时间】:2013-05-01 14:43:34
【问题描述】:

我正在尝试实现一些基本的启动、停止、暂停和恢复功能,以允许我进行以下状态转换:

  • 停止运行
  • 跑到停止
  • 运行到暂停
  • 暂停运行
  • 暂停到停止(导致死锁)

大部分都按预期工作,但最后的状态转换是不可能的,因为它使线程冻结。有人可以解释一下为什么会发生这种情况以及如何预防吗?以下是代码的相关部分:

public class ThreadTest implements Runnable {

    private volatile boolean running = false;
    private volatile boolean paused = false;
    private Thread thread;

    public ThreadTest() {
        thread = new Thread(this);
    }

    public void run() {
        while (running) {
            try {
                if (paused) {
                    synchronized (this) {
                        while (paused)
                        wait(); 
                    }
                }   
            } 
            catch (InterruptedException e) {
            }
        }
    }

    public synchronized void start() {
        if(running && !thread.isAlive())
            return; 
        running = true;
        thread = new Thread(this);
        thread.start();
    }

    public synchronized void stop() {
        if(!running && thread.isAlive())
            return;  
        running = false;
        try {
            thread.join(); 
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
         System.exit(0);    
    }

    public synchronized void resume() { 
        if(paused) {
        paused = false; 
        notify();
        }
        else {
            return;
        }
    }

    public synchronized void pause() { 
        if(!paused) {
            paused = true; 
            }
            else {
                return;
            } 
        }

}

【问题讨论】:

  • paused的情况下尝试在stop()方法中调用notify()。你通过wait() 调用锁定了你的线程。
  • @SeniorJD:请参阅我的回答和我对 xagyg 的评论,说明为什么 notify() 不起作用。
  • +1 因为你的问题对我有用(让我思考)!

标签: java multithreading concurrency locking


【解决方案1】:

运行方法中的wait(); 将永远等待,因为这些不是notify(); 当调用停止时,线程正在运行,因为永远等待,所以thread.join() 将锁定。 您需要在停止时调用通知或更改等待永远为wait(1000);

【讨论】:

  • wait(1000) 将不起作用。这是因为,当等待调用超时时,运行 run() 的可运行线程(让我们称之为 T2)将自动尝试并重新获取 ThreadTest 对象上的锁(它在进入等待之前释放)。但是那个锁已经被主线程持有(在 stop() 方法中,它正在等待 T2 线程上的 join() )!因此,即使 wait(1000) 超时,它也永远不会在 run() 方法中恢复执行。
【解决方案2】:

让我们看看这里到底发生了什么: 让我们将这里涉及的线程命名为 T2(您在代码中显式实例化和启动的线程)和 T1(调用 T2 线程 Object 上的启动、停止方法)。 T1 可能是您的主线程,具体取决于您其他未显示的代码。

由于以下事件序列,您会遇到死锁: (注意1:这只是一个可能的序列,此代码中可能还有其他可能的序列也可能导致死锁)

假设我们对 ThreadTest 对象执行 start()、pause() 和 stop(),如下所示(例如在 main() 中):

ThreadTest t = new ThreadTest();
t.start();
t.pause();
t.stop();
  1. 在 T1 中执行 pause() 后,T2 通过在“if(paused)”条件中输入“synchronized(this)”块来获取 ThreadTest 对象的锁。 (注意2:这里的“this”不是指T2线程对象,而是指ThreadTest对象,因为run()是ThreadTest类的一个方法。)

  2. T2 进入 wait() 并在进入 wait() 调用时释放 ThreadTest 对象锁(隐式)。

  3. 当 T1 进入 stop() 时,它会获取 ThreadTest 对象上的锁,因为 stop() 是一个同步方法。在 stop() 内部,T1 调用 t2.join(),并等待 T2 完成。 但是 T2 已经处于 wait() 状态并且没有人唤醒它!

因此陷入僵局!

Note3:即使我们通过在 wait() 调用中指定超时或通过调用 notify() 来唤醒 T2(正如其他人所建议的那样),它仍然无法退出等待,因为它无法重新获取(隐式)锁(在 ThreadTest 对象上),因为 T1 已经在 join() 中等待!

一种可能的解决方案: 虽然可能有很多可能的解决方案,但您可以试试这个吗?

在 stop() 方法中,而不是

thread.join();

你能用吗:

if (!paused) {
  thread.join();
} else {
  thread.interrupt();
}

【讨论】:

  • 感谢您的详细解释。剩下一个问题:为什么这里不能使用单独的锁对象?
  • 一个单独的锁对象在哪里?你能添加更多你想要的细节吗>
【解决方案3】:
  1. stop 方法中,仅在 running = false; 之后调用thread.notify();。 (这将通知等待线程)。

  2. 那么您必须在您的通知调用之前设置paused = false;

  3. 从您的 run 方法中删除 if (paused) 块。

  4. 将您的 while (paused) 循环更改为 while (paused && running)。或者,您可以使用while (paused) { wait(); if (!running) break;},具体取决于您想要的控制流。

  5. 为了更好地衡量,请将volatile 关键字添加到pausedrunning 变量声明(以创建跨线程的内存栅栏)。

【讨论】:

  • 这不起作用,因为当我尝试在暂停时结束/退出时,应用程序仍然挂起。
  • 请在下面查看我的回答,了解为什么 notify() 不起作用! notify() 可能不起作用的另一个原因是当 T1 在 T2 进入等待之前执行 stop()(以及因此 notify())时!这会导致错过唤醒!
  • @user1812379 你需要从你的运行方法中删除if (paused) 块。
  • @vendhan 没错。我添加了使用标志 running 来解决这个问题。
  • @user1812379 我已经更新了我的答案。现在应该可以工作了。如果您还有其他问题,请告诉我。
猜你喜欢
  • 1970-01-01
  • 2018-11-09
  • 2021-03-14
  • 1970-01-01
  • 2020-08-29
  • 2021-08-02
  • 1970-01-01
  • 1970-01-01
  • 2011-07-01
相关资源
最近更新 更多