在Thread Scheduler中,某些事情从Java8更改为Java9。我正在尝试缩小下面程序中的更改。
下面的程序产生3个线程,这些线程并行运行并同步通过监视器锁,进行打印
Aa0Bb1Cc2Dd3.......Zz25
当前代码在所有Java版本中都可以正常工作,我不寻求任何优化。
在使用Object.wait()传递锁之前,我曾使用过Object.notifyAll()(这可能并非始终正确,但是在这种情况下,它在Java 1.8中没有任何作用)。这就是为什么此代码版本1和版本2有两个版本的原因。
版本1在所有Java版本(Java8和更低版本,Java9和更高版本)中均可正常运行。但是不包括版本2。当您像这样对示例进行注释时,注释版本1和取消注释版本
//obj.wait();//version 1
obj.notifyAll();obj.wait();//version 2
它在Java8中运行完全相同,而在Java9和更高版本的JDK中则没有。它无法抓住锁,也无法抓住锁,但条件已经被翻转,该轮到任何线程了。
(例如,假设numb线程完成了其工作,现在只有可以抓住该锁并继续执行的线程是ThreadCapital,但是不知何故isCapital变成了错误-这只是一种推测,无法证明这一点或不确定这是甚至发生)
我几乎没有使用线程的经验,所以我确定我没有利用监视器上的锁,即使我认为它在所有JDK中都应该反映出来。除非Java9及更高版本中有所更改。线程调度程序内部发生了什么变化吗?
package Multithreading_misc;
public class App {
public static void main(String[] args) throws InterruptedException {
SimpleObject obj = new SimpleObject();
ThreadAlphaCapital alpha = new ThreadAlphaCapital(obj);
ThreadAlphaSmall small = new ThreadAlphaSmall(obj);
ThreadNum num = new ThreadNum(obj);
Thread tAlpha = new Thread(alpha);
Thread tSmall = new Thread(small);
Thread tNum = new Thread(num);
tAlpha.start();
tSmall.start();
tNum.start();
}
}
class ThreadAlphaCapital implements Runnable{
char c = 'A';
SimpleObject obj;
public ThreadAlphaCapital(SimpleObject obj){
this.obj = obj;
}
@Override
public void run() {
try {
synchronized (obj) {
while(c < 'Z')
{
if(!obj.isCapitalsTurn || obj.isNumsTurn)
{
obj.wait();//version 1
//obj.notifyAll();obj.wait();//version 2
}
else
{
Thread.sleep(500);
System.out.print(c++);
obj.isCapitalsTurn = !obj.isCapitalsTurn;
obj.notifyAll();//version 1
//obj.notifyAll();obj.wait();//version 2
}
}
obj.notifyAll();
}
}
catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
class ThreadAlphaSmall implements Runnable{
char c = 'a';
SimpleObject obj;
public ThreadAlphaSmall(SimpleObject obj){
this.obj = obj;
}
@Override
public void run() {
try {
synchronized (obj) {
while(c < 'z')
{
if(obj.isCapitalsTurn || obj.isNumsTurn)
{
obj.wait();//version 1
//obj.notifyAll();obj.wait();//version 2
}
else
{
Thread.sleep(500);
System.out.print(c++);
obj.isCapitalsTurn = !obj.isCapitalsTurn;
obj.isNumsTurn = !obj.isNumsTurn;
obj.notifyAll();//version 1
//obj.notifyAll();obj.wait();//version 2
}
}
obj.notifyAll();
}
}
catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
class ThreadNum implements Runnable{
int i = 0;
SimpleObject obj;
public ThreadNum(SimpleObject obj){
this.obj = obj;
}
@Override
public void run() {
try {
synchronized (obj) {
while(i < 26)
{
if(!obj.isNumsTurn)
{
obj.wait();//version 1
//obj.notifyAll();obj.wait();//version 2
}
else
{
Thread.sleep(500);
System.out.print(i++);
obj.isNumsTurn = !obj.isNumsTurn;
obj.notifyAll();//version 1
//obj.notifyAll();obj.wait();//version 2
}
}
obj.notifyAll();
}
}
catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
class SimpleObject{
public boolean isNumsTurn = false;
public boolean isCapitalsTurn = true;
}
一些注意事项:
从Java9和更高版本运行时,此文件是从UnNamed模块运行的
我并不是说这仅在3个线程中发生,仅举一个例子。顺便说一句,它(过度通知)对于所有Java版本的两个线程都可以正常运行
答案 0 :(得分:2)
我相信过度通知。
不清楚为什么会相信这一点,或者希望在整个代码中撒满notifyAll()
会带来什么,但是现在是时候开始怀疑了。
这可能并非一直都是正确的,但在这种情况下并没有什么不同。
好吧,很明显,它们确实有所作为。
是的,似乎JVM的等待队列实现的某些方面已更改,但这无关紧要,因为过时的notifyAll()
调用的代码一直被破坏,只是靠运气运行
这种情况实际上很容易理解:
notifyAll()
notifyAll()
醒来,并尝试重新获取该锁。哪一个会赢,未指定wait()
,但是在第二种变体中,它将首先执行伪造的notifyAll()
notifyAll()
而醒来(B可能已经醒了,但这没关系),然后尝试重新获取该锁。哪一个会赢,未指定wait()
,但是在第二种变体中,它将首先执行伪造的notifyAll()
notifyAll()
而醒来(B可能已经醒了,但这没关系),然后尝试重新获取该锁。哪一个会赢,未指定如您所见,在第二个变体中,只要B从未获得锁,您就有可能永远运行下去的潜在循环。您的具有过时notifyAll()
调用的变量依赖于错误的假设,即如果您通知多个线程,则正确的线程最终将获得锁定。
在notifyAll()
合适的地方使用notify()
没问题,因为行为良好的所有线程都会重新检查其条件,如果不满足,则再次进入wait()
,因此正确线程(或一个合格的线程)最终将取得进展。但是在等待之前调用notifyAll()
的行为并不完善,并且可能导致线程永久性地重新检查其条件,而没有合格的线程来回事。