Java 线程等待和通知方法
Java Thread wait and notify methods
我正在学习 OCJP,现在我在 "Thread" 章节,我有一些关于等待和通知方法的问题。我想我明白这里发生了什么,但我只是想确保我是正确的 way.I 写了这段代码作为例子:
package threads;
public class Main {
static Object lock = new Object();
public static void main(String[] args) {
new Main().new FirstThread().start();
new Main().new SecondThread().start();
}
class FirstThread extends Thread {
public void run() {
synchronized (lock) {
lock.notify();
System.out.println("I've entered in FirstThread");
}
}
}
class SecondThread extends Thread {
public void run() {
synchronized (lock) {
try {
lock.wait();
System.out.println("I'm in the second thread");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
在这个例子中控制台输出是I've entered in FirstThread
,因为第一个线程启动,notify()方法被调用,然后第二个线程启动,wait()方法被调用,String "I'm in the second thread" 未打印。
下一个场景是我把new Main().new FirstThread().start();
和new Main().new SecondThread().start();
的位置颠倒,输出是
I've entered in FirstThread
I'm in the second thread
因为第二个线程启动,wait()方法被调用,然后第一个线程启动,notify()方法被调用,控制台打印I've entered in FirstThread
,wait被释放I'm in the second thread
打印在控制台中。
发生这种情况是因为计算机速度如此之快,而且线程 运行 顺序进行吗?在我看来,理论上可以首先调用第二个 start() 方法是吗?
我的最后一个问题是为什么锁对象必须是静态的,因为如果我删除静态修饰符,输出总是 I've entered in FirstThread
?
我知道JVM在加载class的时候会加载静态字段,但是我看不懂lock Object的逻辑。
线程是按顺序启动的,理论上线程 1 会在线程 2 之前执行,尽管不能保证(但很确定在这种简单的情况下它会保持一致,因为没有真实或模拟的偶然延迟)。
这就是为什么当线程 2 稍早启动时,它有机会等待随后(由线程 1)通知的锁,而不是永远等待已经通知过一次的锁(因此,无打印)。
在 static
锁 Object
上:您将 [First/Second]Thread
嵌套 类 绑定到 实例 Main
,所以如果你想让它们在同一个锁上同步,那么锁必须是共同的。
如果它是一个实例对象,您的线程将在不同的锁上访问和同步,因为您的 new Main()...
习语将获得 Main
的两个实例,随后是 [=16= 的两个实例].
"Is that happening because the computer is so fast and the threads run
sequentially? Theoretically the second start() method can be called
first in my opinion is it?. "
是的,您可以引入具有随机时间的 sleep(),以实现更好的(单元)测试或演示目的。 (当然最后的运行代码应该没有那个sleep)
And the last question I have, is why the lock Object must be static
原则上锁是不是static无所谓,但你要有访问的可能,而且必须是同一个锁对象。 (每个 class 没有一个对象实例)。在您的情况下,它必须是静态的,否则它将是两个不同的对象实例。
这是错误的,因为它不会更改其他线程可以测试的任何共享状态:
synchronized (lock) {
lock.notify();
System.out.println("I've entered in FirstThread");
}
这是错误的,因为它没有测试任何东西:
synchronized (lock) {
lock.wait();
System.out.println("I'm in the second thread");
}
问题是,如果 lock.wait()
中没有线程休眠,lock.notify()
根本不会 任何事情。在您的程序中,FirstThread
可以在 SecondThread
调用 wait() 之前调用 notify()。 wait() 调用永远不会 return 在这种情况下,因为 notify() 调用在那种情况下什么都不做。
他们让您在调用 wait() 或 notify() 之前输入互斥量(即 synchronized
块)是有原因的。这是因为你应该使用互斥锁来保护服务员正在等待的共享共享状态。
"shared state" 可以像单个布尔值一样简单:
boolean firstThreadRan = false;
通知程序(a.k.a., "producer")这样做:
synchronized(lock) {
firstThreadRan = true;
lock.notify();
...
}
服务员(a.k.a.,"consumer")这样做:
synchronized(lock) {
while (! firstThreadRan) {
lock.wait();
}
...
}
在这种情况下,while 循环并不是绝对必要的,但当多个消费者竞争同一事件时,它就变得非常重要。始终使用循环是一种很好的做法。
我正在学习 OCJP,现在我在 "Thread" 章节,我有一些关于等待和通知方法的问题。我想我明白这里发生了什么,但我只是想确保我是正确的 way.I 写了这段代码作为例子:
package threads;
public class Main {
static Object lock = new Object();
public static void main(String[] args) {
new Main().new FirstThread().start();
new Main().new SecondThread().start();
}
class FirstThread extends Thread {
public void run() {
synchronized (lock) {
lock.notify();
System.out.println("I've entered in FirstThread");
}
}
}
class SecondThread extends Thread {
public void run() {
synchronized (lock) {
try {
lock.wait();
System.out.println("I'm in the second thread");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
在这个例子中控制台输出是I've entered in FirstThread
,因为第一个线程启动,notify()方法被调用,然后第二个线程启动,wait()方法被调用,String "I'm in the second thread" 未打印。
下一个场景是我把new Main().new FirstThread().start();
和new Main().new SecondThread().start();
的位置颠倒,输出是
I've entered in FirstThread
I'm in the second thread
因为第二个线程启动,wait()方法被调用,然后第一个线程启动,notify()方法被调用,控制台打印I've entered in FirstThread
,wait被释放I'm in the second thread
打印在控制台中。
发生这种情况是因为计算机速度如此之快,而且线程 运行 顺序进行吗?在我看来,理论上可以首先调用第二个 start() 方法是吗?
我的最后一个问题是为什么锁对象必须是静态的,因为如果我删除静态修饰符,输出总是 I've entered in FirstThread
?
我知道JVM在加载class的时候会加载静态字段,但是我看不懂lock Object的逻辑。
线程是按顺序启动的,理论上线程 1 会在线程 2 之前执行,尽管不能保证(但很确定在这种简单的情况下它会保持一致,因为没有真实或模拟的偶然延迟)。
这就是为什么当线程 2 稍早启动时,它有机会等待随后(由线程 1)通知的锁,而不是永远等待已经通知过一次的锁(因此,无打印)。
在 static
锁 Object
上:您将 [First/Second]Thread
嵌套 类 绑定到 实例 Main
,所以如果你想让它们在同一个锁上同步,那么锁必须是共同的。
如果它是一个实例对象,您的线程将在不同的锁上访问和同步,因为您的 new Main()...
习语将获得 Main
的两个实例,随后是 [=16= 的两个实例].
"Is that happening because the computer is so fast and the threads run sequentially? Theoretically the second start() method can be called first in my opinion is it?. "
是的,您可以引入具有随机时间的 sleep(),以实现更好的(单元)测试或演示目的。 (当然最后的运行代码应该没有那个sleep)
And the last question I have, is why the lock Object must be static
原则上锁是不是static无所谓,但你要有访问的可能,而且必须是同一个锁对象。 (每个 class 没有一个对象实例)。在您的情况下,它必须是静态的,否则它将是两个不同的对象实例。
这是错误的,因为它不会更改其他线程可以测试的任何共享状态:
synchronized (lock) {
lock.notify();
System.out.println("I've entered in FirstThread");
}
这是错误的,因为它没有测试任何东西:
synchronized (lock) {
lock.wait();
System.out.println("I'm in the second thread");
}
问题是,如果 lock.wait()
中没有线程休眠,lock.notify()
根本不会 任何事情。在您的程序中,FirstThread
可以在 SecondThread
调用 wait() 之前调用 notify()。 wait() 调用永远不会 return 在这种情况下,因为 notify() 调用在那种情况下什么都不做。
他们让您在调用 wait() 或 notify() 之前输入互斥量(即 synchronized
块)是有原因的。这是因为你应该使用互斥锁来保护服务员正在等待的共享共享状态。
"shared state" 可以像单个布尔值一样简单:
boolean firstThreadRan = false;
通知程序(a.k.a., "producer")这样做:
synchronized(lock) {
firstThreadRan = true;
lock.notify();
...
}
服务员(a.k.a.,"consumer")这样做:
synchronized(lock) {
while (! firstThreadRan) {
lock.wait();
}
...
}
在这种情况下,while 循环并不是绝对必要的,但当多个消费者竞争同一事件时,它就变得非常重要。始终使用循环是一种很好的做法。