我正在学习OCJP,现在我正处于“线程”一章,我对等待和通知方法有一些疑问。我想我明白这里发生了什么,但我只是想确保我的方法正确。我写这段代码作为例子:
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()方法,字符串“我是在第二个帖子中“没有打印。
下一个场景是我反转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
,等待被释放,I'm in the second thread
在控制台中打印。
是否发生这种情况是因为计算机速度如此之快且线程顺序运行?理论上第二个start()方法可以先调用,我认为是吗?
我的最后一个问题是,为什么锁定对象必须是静态的,因为如果我删除静态修饰符,输出总是I've entered in FirstThread
?
我知道在加载类时,静态字段是在JVM中加载的,但我无法理解lock Object的逻辑。
答案 0 :(得分:3)
线程是按顺序启动的,理论上线程1会在线程2之前执行,虽然它不能保证(非常确定它在这个简单的情况下会保持一致,因为没有真正的或模拟的随意延迟)。
这就是为什么当线程2稍前启动时,它有机会等待随后被通知(通过线程1)的锁定,而不是永远等待已经被通知一次的锁定(因此,没有打印) )。
在static
锁Object
上:您将[First/Second]Thread
嵌套类绑定到Main
的实例,因此锁必须如果您希望它们在同一个锁上同步,则两者都是通用的。
如果它是一个实例对象,您的线程将访问并同步不同的锁定,因为new Main()...
成语将获得两个Main
实例,随后是两个lock
实例。
答案 1 :(得分:3)
"是否发生这种情况是因为计算机速度太快且线程运行 顺序?从理论上讲,可以调用第二个start()方法 首先在我看来是吗? "
是的,您可以使用随机时间引入sleep(),以获得更好的(单元)测试或演示目的。 (当然,最终运行的代码不应该有睡眠)
我的最后一个问题是,为什么锁定对象必须是静态的
原则上,锁是否是静态无关紧要,但您必须有可能访问它,并且它必须是相同的锁对象。 (不是每个类的一个对象实例)。在你的情况下,它必须是静态的,否则它将是两个不同的对象实例。
答案 2 :(得分:0)
这是错误的,因为它不会更改其他线程可以测试的任何共享状态:
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.notify()
中没有线程处于休眠状态,lock.wait()
根本不会执行任何事情。在您的程序中,FirstThread
可以在SecondThread
调用wait()之前调用notify()。在这种情况下,wait()调用永远不会返回,因为在这种情况下notify()调用将不执行任何操作。
有一个原因可以让你在调用wait()或notify()之前输入一个互斥锁(即synchronized
块)。这是因为您应该使用互斥锁来保护服务员正在等待的共享共享状态。
“共享状态”可以像单个布尔一样简单:
boolean firstThreadRan = false;
通知人(a.k.a。,“生产者”)这样做:
synchronized(lock) {
firstThreadRan = true;
lock.notify();
...
}
服务员(a.k.a。,“消费者”)这样做:
synchronized(lock) {
while (! firstThreadRan) {
lock.wait();
}
...
}
在这种情况下,while循环并不是绝对必要的,但是当多个消费者竞争同一事件时,它变得非常重要。总是使用循环是一种好习惯。