Связь между потоками не работает должным образом в Java9 и более поздних версиях - PullRequest
0 голосов
/ 08 марта 2019

Что-то изменилось с Java8 на Java9 за кулисами в Thread Scheduler.Я пытаюсь сузить изменения в приведенной ниже программе.

Ниже программа порождает 3 потока, которые работают параллельно и синхронно, правильно проходя блокировку монитора, печатая

Aa0Bb1Cc2Dd3.......Zz25

Текущий код уже работаетхорошо во всех версиях Java, и я не ищу каких-либо оптимизаций.

Я использовал Object.notifyAll () перед прохождением блокировки с помощью Object.wait () (это может быть не всегда правильно, но в этой ситуации это не имело значения в Java 1.8).Вот почему есть две версии этого кода: версия 1 и версия 2.

версия 1 отлично работает во всех версиях Java (Java8 и более ранние версии, Java9 и более поздние версии).Но не версия 2. Когда вы комментируете версию 1 и не комментируете версию 2 для экспертизы, как это

//obj.wait();//version 1
obj.notifyAll();obj.wait();//version 2

Она работает точно так же в Java8, что и в Java9 и более поздних версиях JDK - нет.Он не может захватить блокировку или захватывает блокировку, но условие уже перевернуто, и нет очереди нити.

(например, в, скажем, оцепенелом потоке завершил свою работу, и теперь только поток, который может захватить блокировку и продолжить, является 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. Это выполняется из модуля UnNamed при запуске из Java9 и более поздних версий

  2. Я не говорю, что это происходит только с 3 потоками, просто приведу пример.Кстати, он (с переоценкой) работает нормально для двух потоков для всех версий Java

1 Ответ

2 голосов
/ 11 марта 2019

Я верю в чрезмерное уведомление.

Непонятно, почему вы верите в это или что вы надеетесь получить от разбрасывания notifyAll() по всему коду, но сейчас настало время стать скептиком.

, что может быть не всегда правильно, но в этой ситуации это не имеет значения.

Ну, это действительно имеет значение , очевидно.

Да, похоже, что некоторые аспекты реализации очереди ожидания JVM были изменены, но это не имеет значения, так как ваш код с устаревшими вызовами notifyAll() постоянно нарушался и просто выполнялся по чистой случайности.

Ситуацию на самом деле легко понять:

  1. Поток A изменяет состояние так, что поток B имеет право продолжить и вызывает notifyAll()
  2. Нити B и C просыпаются из-за notifyAll() и пытаются снова получить блокировку. Кто из них победит, не уточняется
  3. Поток C получает блокировку, оказывается непригодным и снова переходит на wait(), но во втором варианте он будет выдавать ложные notifyAll() first
  4. Нити A и B просыпаются из-за ложного notifyAll() (B, возможно, уже проснулся, но это не имеет значения) и пытаются восстановить блокировку. Кто из них победит, не уточняется
  5. Поток A получает блокировку, оказывается непригодным и снова переходит на wait(), но во втором варианте он будет выдавать ложное notifyAll() first
  6. Потоки B и C просыпаются из-за ложного notifyAll() (B может уже проснуться, но это не имеет значения) и пытаются восстановить блокировку. Кто из них победит, не уточняется
  7. См. 3.

Как вы можете видеть, со вторым вариантом у вас есть потенциальный цикл, который может работать вечно, пока B никогда не получит блокировку. Ваш вариант с устаревшими вызовами notifyAll() основан на неверном предположении, что правильный поток в конечном итоге получит блокировку, если вы уведомите более одного потока.

Нет проблем с использованием notifyAll() в местах, где notify() было бы уместно, поскольку все хорошо работающие потоки перепроверили бы свои условия и снова вернулись бы к wait(), если не выполнялись, поэтому правильный поток (или один подходящий поток) ) будет в конечном итоге прогрессировать. Но вызов notifyAll() перед ожиданием не ведет себя должным образом и может привести к тому, что потоки будут постоянно перепроверять свои условия, при этом поток, не отвечающий требованиям, никогда не получит свой ход.

...