#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. Спасибо Дж. Я понимаю, что эти шесть забавных уроков после того, как я опубликовал этот вопрос, и должны быть написаны так, как вы сказали.