Многопоточная программа не завершает работу правильно?

#java #multithreading

#java #многопоточность

Вопрос:

Итак, как и фрагмент вопроса. Я пытаюсь изучить многопоточное программирование. У меня есть неудобная программа, которая помогает мне понять, что многопоточность быстрее обычного выполнения. Программа содержит семь классов в одном файле Java, один тестовый класс, три класса, реализующих Runnable, и три обычных класса. Все шесть классов выполняют одно и то же, считая до 10 миллионов и возвращая результат. Моя проблема в том, что три класса используют три потока для запуска, но они не вернули правильные значения, как я ожидал. Однако три обычных класса работают нормально.

Я действительно ценю, что кто-нибудь может помочь мне понять, почему это происходит! Я использую JDK 9 и Eclipse 2018-12.

 import java.time.Duration;
import java.time.Instant;

class MyMultiThreadExample{

    public static void main(String[] args) {

        GameOne g1 = new GameOne();
        GameTwo g2 = new GameTwo();
        GameThree g3 = new GameThree();

        Thread thread1 = new Thread(g1);
        Thread thread2 = new Thread(g2);
        Thread thread3 = new Thread(g3);

        Instant start1 = Instant.now();

        thread1.start();

        thread2.start();

        thread3.start();

        Instant end1 = Instant.now();

        long elapsed = Duration.between(start1, end1).toMillis();

        int total = g1.getCount()   g2.getCount()   g3.getCount();

        System.out.println("MultiThread running cost "   elapsed   " to count "   total   " times");

        GameFour g4 = new GameFour();
        GameFive g5 = new GameFive();
        GameSix g6 = new GameSix();

        Instant start2 = Instant.now();

        g4.run();
        g5.run();
        g6.run();

        Instant end2 = Instant.now();

        long elapsed2 = Duration.between(start2, end2).toMillis();

        int total2 = g3.getCount()   g4.getCount()   g5.getCount();

        System.out.println("Sequential running cost "   elapsed2   " to count "   total2   " times");
    }

}
  

 class GameOne implements Runnable {

    int count1 = 0;

    @Override
    public void run() {

        for (int i = 0; i < 10000000; i  ) {
            // System.out.print("Game1 at round "   count   " now");
            count1  ;
        }
    }

    public int getCount() {
        System.out.println("GameOne counts "   count1);
        return count1;
    }
}
  

 class GameTwo implements Runnable {

    int count2 = 0;

    @Override
    public void run() {

        for (int i = 0; i < 10000000; i  ) {
            // System.out.print("Game2 at round "   count   " now");
            count2  ;
        }
    }

    public int getCount() {
        System.out.println("GameTwo counts "   count2);
        return count2;
    }
}
  

 class GameThree implements Runnable {

    int count3 = 0;

    @Override
    public void run() {

        for (int i = 0; i < 10000000; i  ) {
            // System.out.print("Game3 at round "   count   " now");
            count3  ;
        }
    }

    public int getCount() {
        System.out.println("GameThree counts "   count3);
        return count3;
    }
}
  

 class GameFour {

    int count4 = 0;

    public void run() {

        for (int i = 0; i < 10000000; i  ) {
            // System.out.print("Game3 at round "   count   " now");
            count4  ;
        }
    }

    public int getCount() {
        System.out.println("GameFour counts "   count4);
        return count4;
    }
}
  

 class GameFive {

    int count5 = 0;

    public void run() {

        for (int i = 0; i < 10000000; i  ) {
            // System.out.print("Game3 at round "   count   " now");
            count5  ;
        }
    }

    public int getCount() {
        System.out.println("GameFive counts "   count5);
        return count5;
    }
}
  

 class GameSix {

    int count6 = 0;

    public void run() {

        for (int i = 0; i < 10000000; i  ) {
            // System.out.print("Game3 at round "   count   " now");
            count6  ;
        }
    }

    public int getCount() {
        System.out.println("GameFive counts "   count6);
        return count6;
    }
}
  

Ответ №1:

У меня есть неудобная программа, которая помогает мне понять, что многопоточность быстрее обычного выполнения.

Важно понимать, что это не всегда так. Вы должны использовать несколько потоков только тогда, когда у вас есть длительные задачи, которые могут выполняться параллельно. ЕСЛИ ваши задачи короткие, они почти наверняка будут выполняться быстрее при выполнении в одном потоке, поскольку есть накладные расходы на создание специальной синхронизации между потоками.

Учитывая это, вы на самом деле не измеряете правильное время здесь.

Когда вы вызываете Thread.start() , она запускает соответствующую Runnable функцию параллельно с кодом внутри вашей функции.

Чтобы позволить потокам выполняться до их завершения, прежде чем продолжить, вы должны вызвать Thread#join() :

 thread1.start();
thread2.start();
thread3.start();

// all 3 Threads may be running now, but maybe not even started!
// let's wait for them to finish running by joining them
thread1.join();
thread2.join();
thread3.join();
  

Это самый простой способ подождать… но есть и другие, и это сложная тема.

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

Чтобы узнать больше о параллелизме в Java, я рекомендую вам прочитать об этом. Учебные пособия Baeldung превосходны.

Ответ №2:

Вы забываете вызвать thread.join() — это ожидает завершения выполнения потока.

В противном случае вы читаете counter s в середине выполнения.

Ваш код должен быть:

 thread1.start()
thread2.start()
thread3.start()

thread1.join()
thread2.join()
thread3.join()
  

Кроме того, все ваши классы могут быть сведены в один class Game :

 class Game implements Runnable {
    String name;
    int count = 0;

    public Game(String name) {
        this.name = name;
    }

    @Override
    public void run() {

        for (int i = 0; i < 10000000; i  ) {
            // System.out.print(name   " at round "   count   " now");
            count  ;
        }
    }

    public int getCount() {
        System.out.println(name   " counts "   count);
        return count;
    }
}
  

У каждого будет свой собственный счетчик, и вы можете запускать их в потоке или в том же потоке, вызывая run() — ваш основной метод остается в основном неизменным, за исключением того, где они создаются. Они могут быть созданы как:

 Game g1 = new Game("GameOne");
Game g2 = new Game("GameTwo");
Game g3 = new Game("GameThree");
Game g4 = new Game("GameFour");
Game g5 = new Game("GameFive");
Game g6 = new Game("GameSix");
  

Комментарии:

1. Спасибо Дж. Я понимаю, что эти шесть забавных уроков после того, как я опубликовал этот вопрос, и должны быть написаны так, как вы сказали.