线程间通信在Java9和更高版本中无法正常工作

时间:2019-03-08 20:32:59

标签: java multithreading java-8 java-9

在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;   
    }

一些注意事项:

  1. 从Java9和更高版本运行时,此文件是从UnNamed模块运行的

  2. 我并不是说这仅在3个线程中发生,仅举一个例子。顺便说一句,它(过度通知)对于所有Java版本的两个线程都可以正常运行

1 个答案:

答案 0 :(得分:2)

  

我相信过度通知。

不清楚为什么会相信这一点,或者希望在整个代码中撒满notifyAll()会带来什么,但是现在是时候开始怀疑了。

  

这可能并非一直都是正确的,但在这种情况下并没有什么不同。

好吧,很明显,它们确实有所作为。

是的,似乎JVM的等待队列实现的某些方面已更改,但这无关紧要,因为过时的notifyAll()调用的代码一直被破坏,只是靠运气运行

这种情况实际上很容易理解:

  1. 线程A更改状态,以使线程B可以继续执行并调用notifyAll()
  2. 线程B和C由于notifyAll()醒来,并尝试重新获取该锁。哪一个会赢,未指定
  3. 线程C获得了锁,发现自己不合格,然后再次进入wait(),但是在第二种变体中,它将首先执行伪造的notifyAll()
  4. 线程A和B由于虚假的notifyAll()而醒来(B可能已经醒了,但这没关系),然后尝试重新获取该锁。哪一个会赢,未指定
  5. 线程A获得锁定,发现自己不合格,然后再次进入wait(),但是在第二种变体中,它将首先执行伪造的notifyAll()
  6. 线程B和C由于虚假的notifyAll()而醒来(B可能已经醒了,但这没关系),然后尝试重新获取该锁。哪一个会赢,未指定
  7. 请参阅3。

如您所见,在第二个变体中,只要B从未获得锁,您就有可能永远运行下去的潜在循环。您的具有过时notifyAll()调用的变量依赖于错误的假设,即如果您通知多个线程,则正确的线程最终将获得锁定。

notifyAll()合适的地方使用notify()没问题,因为行为良好的所有线程都会重新检查其条件,如果不满足,则再次进入wait(),因此正确线程(或一个合格的线程)最终将取得进展。但是在等待之前调用notifyAll()的行为并不完善,并且可能导致线程永久性地重新检查其条件,而没有合格的线程来回事。