【问题标题】:Indefinite thread waiting in JavaJava中的无限线程等待
【发布时间】:2016-08-05 16:11:19
【问题描述】:

我正在尝试使用等待通知的 2 个线程生成奇数/偶数。

但它只是打印 1

下面是代码:

Even.java

public class Even implements Runnable {
    private int i; private Object ob
    public Even(int i,Object o) {
    this.i=i;
    this.ob=o;
    }
    @Override
    public void run() {
        while (true) {
            synchronized (ob) {
                while (i % 2 == 0) {
                    try {
                        ob.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                i++;
                System.out.println(i);
                ob.notifyAll();
            }
        }
    }
}

Odd.java

public class Odd implements Runnable {
    private int i; private Object ob;
    public Odd(int i) {
        this.i=i;
        this.ob=o;
    }
    @Override
    public void run() {
        while (true) {
            synchronized (ob) {
                while (i % 2 == 1) {
                    try {
                        ob.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                i++;
                System.out.println(i);
                ob.notifyAll();
            }
        }
    }
}

Test.java

public class Test {
    public static void main(String[] args) {
        int i = 0;
        Object lock = new Object();
        Thread t1 = new Thread(new Even(i),lock);
        Thread t2 = new Thread(new Odd(i),lock);
        t1.start();
        t2.start(); 
    }       
}

输出:

1

谁能告诉我哪里出错了?

【问题讨论】:

  • 您的 Odd 构造函数缺少 o 参数。而且您将lock 传递给您的线程构造函数,而不是传递给您的runnables。也许发布一些可以编译的东西。
  • 你的内部 while 循环没有意义,并且会无限循环,因为 i 不在线程之间共享。
  • 如果您尝试调试代码,您会清楚地看到发生了什么以及为什么。

标签: java multithreading synchronization wait notify


【解决方案1】:

嗯,我认为 Integer 在 Java 中似乎是不可变的可能是一个问题。因此,如果两个类中的一个更改了 i 值(由于第一个输出为 1 显然会这样做),您将不再对相同的对象进行同步。

因此,由于您将存储在 i 中的值更改为 Odd 并在新对象 I 上调用 notifyAll,Java 会抱怨,因为您在实际上从未被锁定的对象上调用 notifyAll

【讨论】:

  • 我在锁定中使用了一个固定的对象
  • 好的,你更新了你的代码。美好的。现在您遇到了两个 while 循环都会产生死锁的问题。第一个说while(i % 2 == 0) { ...} i++;。但请注意,在开头i=0 并且因此该对象永远不会从while 循环中逃脱。在 Odd while(i % 2 == 1) { ...} i++; 中,它将超越 while 循环一次,打印一个然后也被捕获。
  • 我需要在哪里进行更改?
  • 目前我只想说您可以删除所有同步的东西,因为您没有要更改的两个线​​程之间共享的全局变量。所以Oddrun 方法可能如下所示:public void run() { while(true) { i++;if (i % 2 == 1) {System.out.println(i); }}}}(对不起,丑陋的代码格式:()
【解决方案2】:

线程已进入 INDEFINITE WAITING 状态。请注意(t1Even 的实例和t2Odd 的实例),每个都有实例变量i 的单独副本。

幕后发生的事情如下:

  • 最初,线程t1 (Even) 和t2 (Odd) 都处于READY,然后是RUNNABLE 状态
  • 让我们假设t1 被调度并让处理器执行。 t1 进入 RUNNING 状态。调用t1run()
    • while(true)成功,控制进入外层while循环
    • 因为synchronized(ob)t1锁定对象ob
    • 因为i最初是0i % 2 == 0 条件评估为 true
    • 控制现在进入内部while循环的主体,然后try块并调用ob.wait();
    • t1 进入 WAITING 状态并等待直到有人通知对象 ob
  • 现在,t2 被调度并让处理器执行。 t2 进入 RUNNING 状态
    • while(true) 成功,控制进入外层while循环
    • 因为synchronized(ob)t2锁定对象ob
    • i 最初是 0(请记住,以前递增的 it1 的本地 - Even.i)。在这种情况下,变量it2 的本地变量,即Odd.i
    • 因此 i % 2 == 1 的计算结果为 false 并且控制跳过内部 while 循环
    • 控制到达i++; i0 增加到1
    • 语句System.out.println(i);1 打印到控制台
    • contorl 移至下一行并调用ob.notifyAll();,并通知所有等待对象ob(在我们的例子中为t1)的线程
  • 此时,t1t2 再次回到 RUNNABLE 状态
  • 取决于要调度哪个线程的处理器
  • 让我们假设t1 被调度并让处理器执行
    • t1 从之前离开的地方恢复操作(即ob.wait(); 之后的语句)
    • 控制到达catch (InterruptedException e),由于没有异常,跳过控制回到while ( i % 2 == 0)检查
    • 请记住,t1i(即Even.i)仍然是0,因为控制无法到达i++; 类中的i++;
    • 因此i % 2 == 0 计算为true 并且控制进入while 循环体,然后进入try 块并调用ob.wait();
    • t1 再次进入WAITING状态并等待直到有人通知对象ob
  • 现在,t2 被调度并让处理器执行。 t2 进入 RUNNING 状态
    • t2 从之前离开的地方恢复操作(即ob.notifyAll(); 之后的语句)
    • 由于ob.notifyAll();之后没有其他语句,控制到达外部while循环
    • while(true) 被评估并且控制进入外部while循环的主体
    • 因为synchronized(ob)t1锁定对象ob
    • 记住 t2i 现在是 1,因为它之前已递增并打印在控制台上
    • 因此,while ( i % 2 == 1) 被评估为 true 并且控制进入内部 while 循环的主体,然后 try 块并调用 ob.wait();
    • 因此,t2 进入 WAITING 状态
  • t1t2 现在都处于 WAITING 状态;等待对象ob。等待有人在对象ob 上通知他们。可悲的是,没有人可以拯救

因此 无限等待

以下代码应该有助于您实现目标

public class EvenOddTest {

    public static void main(String[] args) {
        Lock lock = new Lock();
        Thread t1 = new Thread(new Even(lock));
        Thread t2 = new Thread(new Odd(lock));
        t1.start();
        t2.start();
    }

}

class Lock {
    private int data;

    public void increment() {
        data++;
    }

    public int getData() {
        return data;
    }
}

class Even implements Runnable {
    private Lock ob;

    public Even(Lock o) {
        this.ob = o;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (ob) {
                while (ob.getData() % 2 == 0) {
                    try {
                        ob.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                ob.increment();
                System.out.println(ob.getData());
                ob.notifyAll();
            }
        }
    }
}

class Odd implements Runnable {
    private Lock ob;

    public Odd(Lock o) {
        this.ob = o;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (ob) {
                while (ob.getData() % 2 == 1) {
                    try {
                        ob.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                ob.increment();
                System.out.println(ob.getData());
                ob.notifyAll();
            }
        }
    }
}

注意:

  1. 我已经重构了您的代码,以便线程尝试获取锁定和等待的对象也可以保存数据。
  2. lock 对象现在是 EvenOdd 线程之间的共享实例。
  3. 根据我之前的推理,代码应该是不言自明的。
  4. 这不是并行计算中的实际处理方式,以利用多线程能力/功能。不过,这应该是一个很好的入门练习。

【讨论】:

  • 以上工作,我知道我的 int 变量没有在 2 个线程之间共享。如果不创建额外的类并且只使用 Integer/int 之一,我们是否无法完成上述任务?
【解决方案3】:

你可以将 i 变量作为类属性放在 Test 类中,然后创建一个 Test 类型的对象并将其作为参数传递给构造函数:

public class Test {

public Integer i = 0;

public static void main(String[] args) {
    Object o = new Object();
    Test t = new Test();
    Thread t1 = new Thread(new Even(t,o));
    Thread t2 = new Thread(new Odd(t,o));
    t1.start();
    t2.start(); 
}       

}

public class Even implements Runnable {
private Test t;
private Object o;
public Even(Test t, Object o) {
this.t=t;
this.o=o;
}
@Override
public void run() {
    while (true) {
        synchronized (o) {
            while (t.i % 2 == 0) {
                try {
                    o.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            t.i++;
            System.out.println(t.i);
            o.notifyAll();
        }
    }
}

}

public class Odd implements Runnable {
private Test t;
private Object o;
public Odd(Test t, Object o) {
    this.t=t;
    this.o = o;
}
@Override
public void run() {
    while (true) {
        synchronized (o) {
            while (t.i % 2 == 1) {
                try {
                    o.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            t.i++;
            System.out.println(t.i);
            o.notifyAll();
        }
    }
}

}

【讨论】:

  • 实际上,该解决方案似乎非常可行。但是,我会避免使用公共变量,而是使用 getter 和 setter(封装原则)。另一方面,我认为应该重新考虑整个方法。我的意思是你为什么使用线程?通常是为了提高你的速度。如果是关于创建偶数和奇数之类的事情,我看不到通过同步整数变量来实现真正的效率提升。我想我了解这项任务的学习背景,但我认为记住这些内容也很有用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-10-23
  • 1970-01-01
  • 2013-08-25
  • 1970-01-01
  • 2015-07-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多