我有这样的代码:
public class OtherClass {
// OtherClass
public synchronized static void firstMethod() {
System.out.println("FIRST METHOD");
}
public synchronized static void secondMethod() {
System.out.println("SECOND METHOD");
// In actual code I would have try catch for this but here I just didn't
// include it
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class MainClass {
// main method of MainClass
public static void main(String args[]) {
Thread firstThread = new Thread() {
public void run() {
while (true) {
OtherClass.firstMethod();
}
}
};
Thread secondThread = new Thread() {
public void run() {
while (true) {
OtherClass.secondMethod();
}
}
};
secondThread.start();
firstThread.start();
}
}
我首先启动第二个线程的原因是因为我希望首先执行secondMethod
OtherClass
。我应该每隔5秒在控制台输出中查看"FIRST METHOD"
和"SECOND METHOD"
。原因是因为Thread.sleep
没有放弃锁,5秒钟第一个线程无法访问第一个方法,因为第二个线程已经锁定了类,而它在第二个方法中告诉线程睡5秒钟。但是我得到了非常意外的结果。我在控制台输出中获得的所有内容都是每5秒"SECOND METHOD"
。 firstMethod
未被调用。
答案 0 :(得分:4)
忽略代码中的编译问题。
这只是巧合。线程调度程序只是决定继续执行第二个线程,该第二个线程过快地重新获取锁定。
运行它足够长(或者使用较短的睡眠时间以获得更快的结果)并且您将看到调用其他方法。
答案 1 :(得分:2)
我首先启动第二个线程的原因是因为我希望首先执行OtherClass的secondMethod。
这不是线程的工作方式。这不是线程的用途。除了在它们之间提供显式同步之外,线程提供 no 保证之前发生的事情。一般来说,使用的同步越多,拥有多个线程的好处就越少。
在您的示例中,您具有显式同步,可以阻止firstMethod()和secondMethod()的任何并发执行,但是您没有任何保证首先运行哪个,以及哪个将运行第二个。有可能,main()将在它们中的任何一个运行之前终止。此时,由调度程序决定选择哪一个将在何时运行。不要求它以与您的代码称为start()方法相同的顺序启动它们。
您的示例可能具有教育意义,但它也是不何时使用线程的示例。你的同步非常繁重。你的程序基本上做了两件事,firstMethod()和secondMethod(),并且同步确保它们不能重叠。在生产软件中,如果您有两个必须不重叠的任务,那么如果它们总是由同一个线程执行,那么它将简化您的程序逻辑。
我进入控制台输出的所有内容都是"第二种方法"每5秒钟。第一种方法没有被召唤。
我的问题在我看到之前就已经过编辑,因此我不知道您是在谈论原始版本还是固定版本。但无论如何:
程序中的同步确实不保证两个线程轮流。它只是阻止它们同时打印。
你的每个线程都运行一个循环来抓取锁,打印东西,释放锁,然后立即尝试再次抓住锁。当一个正在运行的线程释放一个锁,然后立即尝试再次获取它时,它有可能成功。一些其他线程正在等待锁定并不重要。操作系统不知道你的程序想要完成什么,但 知道它可以通过让线程继续运行而不是阻塞它来更有效地使用CPU阻止一些其他线程。
你需要使用一些额外的同步来让线程轮流,但正如我所说,在实际程序中,你使用的同步越多,使用线程的好处就越少。
答案 2 :(得分:0)
您的处理器不会同时执行您的主题;每次在你的第一个线程上运行你的第二个线程。
行为很明确:您的处理器执行您的第二个线程。然后,处理器执行您的第一个线程,并看到它被第二个线程锁定。 5秒后,再次调用第二个线程。它产生输出,释放锁并再次锁定它。如果再次调用第一个线程,它将再次被锁定。
要解决此问题,请在结束时添加Thread.yield()
。这将使处理器在继续执行第二个线程之前调用第一个线程(第一个线程不是唯一被调用的线程,它只是将第二个线程从其执行中移除一次)。然后你的第一个线程获得锁定,等待5秒,输出并调用Thread.yield()
;然后你的第二个线程再次获得锁定,依此类推。
答案 3 :(得分:0)
您遇到的是线程饥饿。在您的情况下,一个线程被无限期地阻塞,等待进入同步块,因为另一个线程一直被允许访问。
要克服线程饥饿,您需要采用某种公平锁定。被设想为公平的锁优先于等待最长时间获取所述锁的线程。这种锁定策略消除了线程不足的可能性,并确保所有等待线程将在某个时间执行。
使用公平参数设置为true的ReentrantLock可以轻松实现Java中的公平锁定。