Запускаем потоки в Java

User Rating: 5 / 5

Доброго времени суток, друзья.

В этой статье мы поговорим о том, как работать с потоками в Java, и научимся запускать потоки разными способами. Потоки являются неотъемлемой частью работы программ (к слову, не только написанных на Java), и, как правило, в вашей операционной системе выполняется множество различных процессов, большинство из которых запускают свои потоки для решения специфичных задач. Каждый процесс состоит как минимум из одного потока.

Процесс и поток - это связанные, но всё же разные термины. Процесс обладает собственным окружением времени выполнения и ресурсы, в частности, выделенную ему память операционной системы. Процессы взаимодействуют между собой через сокеты или каналы, причём это взаимодействие процессов может происходить как внутри одной и той же операционной системы, так и может быть распределено между процессами, работающими на разных системах.

Потоки существуют и живут внутри процесса и разделяют его ресурсы, например, открытые файлы и память процесса. Как уже было сказано выше, любой процесс состоит как минимум из одного потока, который обычно называют главным потоком. Когда вы пишете простую программу на Java, состоящую из публичного статического метода main(), то при запуске этой программы будет создан главный поток. Из главного потока уже можно создавать другие потоки, которые будут работать и исполняться в многопоточной среде, предоставляемой средствами JVM.

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

Чтобы организовать и запустить поток в Java существует два основных способа:

  • Создать класс, который реализует интерфейс Runnable
  • Создать класс-наследник от класса Thread

Первый способ является, как правило, более распространённым и даёт разработчику определённую гибкость, поскольку в этом случае класс может быть наследником других классов, отличных от Thread. Второй способ обычно подходит для простых приложений, где не столь важно, что класс является прямым наследником Thread, вследствие чего уже не может наследоваться от какого-то другого класса.

Рассмотрим оба способа создания и запуска потоков на примерах.

Первый способ создания и запуска потоков. Класс, реализующий интерфейс Runnable.

Напишем класс SomeRunnable, который будет вычислять сумму чисел от 1 до 10000, и пусть он реализует интерфейс Runnable:

package ru.allineed.samples.threads.runnable;

import ru.allineed.samples.config.Localization;

/**
 * [RU] Класс реализует интерфейс Runnable и расчитывает сумму чисел от 1 до 10000 в отедльном потоке.
 * [EN] Class implements Runnable interface and calculates the numbers sum from 1 to 10000 in a separate thread.
 */
public class SomeRunnable implements Runnable {
    private final String runnableName;

    public SomeRunnable(String runnableName) {
        this.runnableName = runnableName;
    }

    public String getRunnableName() {
        return runnableName;
    }

    private int calculateSum() throws InterruptedException {
        int sum = 0, k = 1;
        for (int i = 1; i <= 10000; i++) {
            if (i > k * 1000) {
                System.out.printf(Localization.getLocalized(
                                "Runnable-экземпляр '%s' вычислил сумму для i > %d! Промежуточная сумма равна %d. Поток #%s%n",
                                "Runnable '%s' has calculated sum for i > %d! The intermediate sum is equal to %d. Thread #%s%n"),
                        getRunnableName(), k, sum, Thread.currentThread().getId());
                k++;
            }
            if (Thread.interrupted()) {
                throw new InterruptedException();
            }
            sum += i;
        }
        return sum;
    }

    @Override
    public void run() {
        System.out.printf(Localization.getLocalized(
                "Это сообщение из класса '%s'! Имя Runnable-экземпляра: %s, Поток #%s%n",
                "This is a message from '%s' class! Runnable name: %s, Thread #%s%n"
        ), this.getClass().getName(), getRunnableName(), Thread.currentThread().getId());

        try {
            int sum = calculateSum();
            System.out.printf(Localization.getLocalized(
                "Runnable-экземпляр '%s' завершил работу! Итоговая сумма равна %d. Поток #%s%n",
                "Runnable '%s' has finished! The final sum is equal to %d. Thread #%s%n"
            ), getRunnableName(), sum, Thread.currentThread().getId());
        } catch (InterruptedException e) {
            System.out.printf(Localization.getLocalized(
                "Runnable-экземпляр '%s' не завершил работу и был прерван! Поток #%s%n",
                "Runnable '%s' has not finished and has been interrupted! Thread #%s%n"
            ), getRunnableName(), Thread.currentThread().getId());
        }
    }
}

Обратите внимание, что в нашем классе SomeRunnable мы обязаны реализовать абстрактный метод run(), предоставляемый интерфейсом Runnable. Этот метод run() и выполняет основной код потока. В нашем примере в реализации метода мы отображаем на консоли сообщение с именем класса и именем экземпляра потока, который инициализируем в конструкторе класса и сохраняем в переменной класса с именем runnableName.

Далее в блоке try мы вызываем метод calculateSum(), который должен просуммировать числа от 1 до 10000 и вернуть результат в локальную переменную sum. Если по какой-то причине исполнение потока было прервано (например, в момент работы метода calculateSum() и вычисления суммы), то мы обрабатываем исключение InterruptedException, которое говорит нам о факте прерывания потока до момента его планового самостоятельного завершения. Если исключение случилось, мы выведем сообщение об ошибке на консоль.

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

Также внутри цикла происходит проверка - не был ли прерван текущий поток - при помощи вызова Thread.interrupted() и сверки значения, которое вернул этот метод (значение true при возврате из метода означает, что поток был прерван).

Давайте теперь создадим простой класс RunnableExample, который будет содержать метод main() для запуска примера и запустим 4 отдельных потока для решения одной и той же задачи: вычисление суммы чисел от 1 до 10000:

package ru.allineed.samples.threads.runnable;

import ru.allineed.samples.common.OutputUtils;

public class RunnableExample {
    public static void main(String[] args) {
        OutputUtils.printSampleTitle(
                "Запускаем потоки в Java",
                "Running threads in Java ",
                "https://allineed.ru/development/java-development/83-java-starting-threads");

        (new Thread(new SomeRunnable("A"))).start();
        (new Thread(new SomeRunnable("B"))).start();
        (new Thread(new SomeRunnable("C"))).start();
        (new Thread(new SomeRunnable("D"))).start();
    }
}

Как видно, мы присвоили четырём тестовым потокам тестовые имена-маркеры "A", "B", "C" и "D". Давайте запустим наш класс RunnableExample (например, я это делаю в среде разработки IntelliJ IDEA. Подробнее о запуске тестовых примеров - смотрите ссылку на инструкцию внизу данной статьи).

Результат вывода на консоль в моём случае следующий (у вас он может отличаться - в части последовательности вывода сообщений. У меня также с каждым запуском вывод консоли меняется, и это понятно - ведь нельзя заранее предугадать, какому потоку будет отдан приоритет при их параллельном выполнении):

========================================================================================================================
>> Запуск примера для статьи "Запускаем потоки в Java"
>> Ссылка на статью: https://allineed.ru/development/java-development/83-java-starting-threads
========================================================================================================================
Это сообщение из класса 'ru.allineed.samples.threads.runnable.SomeRunnable'! Имя Runnable-экземпляра: A, Поток #14
Это сообщение из класса 'ru.allineed.samples.threads.runnable.SomeRunnable'! Имя Runnable-экземпляра: B, Поток #15
Это сообщение из класса 'ru.allineed.samples.threads.runnable.SomeRunnable'! Имя Runnable-экземпляра: C, Поток #16
Это сообщение из класса 'ru.allineed.samples.threads.runnable.SomeRunnable'! Имя Runnable-экземпляра: D, Поток #17
Runnable-экземпляр 'C' вычислил сумму для i > 1! Промежуточная сумма равна 500500. Поток #16
Runnable-экземпляр 'B' вычислил сумму для i > 1! Промежуточная сумма равна 500500. Поток #15
Runnable-экземпляр 'A' вычислил сумму для i > 1! Промежуточная сумма равна 500500. Поток #14
Runnable-экземпляр 'B' вычислил сумму для i > 2! Промежуточная сумма равна 2001000. Поток #15
Runnable-экземпляр 'C' вычислил сумму для i > 2! Промежуточная сумма равна 2001000. Поток #16
Runnable-экземпляр 'D' вычислил сумму для i > 1! Промежуточная сумма равна 500500. Поток #17
Runnable-экземпляр 'C' вычислил сумму для i > 3! Промежуточная сумма равна 4501500. Поток #16
Runnable-экземпляр 'B' вычислил сумму для i > 3! Промежуточная сумма равна 4501500. Поток #15
Runnable-экземпляр 'A' вычислил сумму для i > 2! Промежуточная сумма равна 2001000. Поток #14
Runnable-экземпляр 'B' вычислил сумму для i > 4! Промежуточная сумма равна 8002000. Поток #15
Runnable-экземпляр 'C' вычислил сумму для i > 4! Промежуточная сумма равна 8002000. Поток #16
Runnable-экземпляр 'D' вычислил сумму для i > 2! Промежуточная сумма равна 2001000. Поток #17
Runnable-экземпляр 'C' вычислил сумму для i > 5! Промежуточная сумма равна 12502500. Поток #16
Runnable-экземпляр 'B' вычислил сумму для i > 5! Промежуточная сумма равна 12502500. Поток #15
Runnable-экземпляр 'A' вычислил сумму для i > 3! Промежуточная сумма равна 4501500. Поток #14
Runnable-экземпляр 'B' вычислил сумму для i > 6! Промежуточная сумма равна 18003000. Поток #15
Runnable-экземпляр 'C' вычислил сумму для i > 6! Промежуточная сумма равна 18003000. Поток #16
Runnable-экземпляр 'D' вычислил сумму для i > 3! Промежуточная сумма равна 4501500. Поток #17
Runnable-экземпляр 'C' вычислил сумму для i > 7! Промежуточная сумма равна 24503500. Поток #16
Runnable-экземпляр 'B' вычислил сумму для i > 7! Промежуточная сумма равна 24503500. Поток #15
Runnable-экземпляр 'A' вычислил сумму для i > 4! Промежуточная сумма равна 8002000. Поток #14
Runnable-экземпляр 'B' вычислил сумму для i > 8! Промежуточная сумма равна 32004000. Поток #15
Runnable-экземпляр 'C' вычислил сумму для i > 8! Промежуточная сумма равна 32004000. Поток #16
Runnable-экземпляр 'D' вычислил сумму для i > 4! Промежуточная сумма равна 8002000. Поток #17
Runnable-экземпляр 'C' вычислил сумму для i > 9! Промежуточная сумма равна 40504500. Поток #16
Runnable-экземпляр 'B' вычислил сумму для i > 9! Промежуточная сумма равна 40504500. Поток #15
Runnable-экземпляр 'A' вычислил сумму для i > 5! Промежуточная сумма равна 12502500. Поток #14
Runnable-экземпляр 'B' завершил работу! Итоговая сумма равна 50005000. Поток #15
Runnable-экземпляр 'C' завершил работу! Итоговая сумма равна 50005000. Поток #16
Runnable-экземпляр 'D' вычислил сумму для i > 5! Промежуточная сумма равна 12502500. Поток #17
Runnable-экземпляр 'A' вычислил сумму для i > 6! Промежуточная сумма равна 18003000. Поток #14
Runnable-экземпляр 'D' вычислил сумму для i > 6! Промежуточная сумма равна 18003000. Поток #17
Runnable-экземпляр 'A' вычислил сумму для i > 7! Промежуточная сумма равна 24503500. Поток #14
Runnable-экземпляр 'D' вычислил сумму для i > 7! Промежуточная сумма равна 24503500. Поток #17
Runnable-экземпляр 'A' вычислил сумму для i > 8! Промежуточная сумма равна 32004000. Поток #14
Runnable-экземпляр 'D' вычислил сумму для i > 8! Промежуточная сумма равна 32004000. Поток #17
Runnable-экземпляр 'A' вычислил сумму для i > 9! Промежуточная сумма равна 40504500. Поток #14
Runnable-экземпляр 'D' вычислил сумму для i > 9! Промежуточная сумма равна 40504500. Поток #17
Runnable-экземпляр 'A' завершил работу! Итоговая сумма равна 50005000. Поток #14
Runnable-экземпляр 'D' завершил работу! Итоговая сумма равна 50005000. Поток #17
Process finished with exit code 0

Итак, какие выводы можно сделать, глядя на консоль? 

Во-первых, можно увидеть, что во всех случаях потоки выдали одну и ту же итоговую сумму, и её значение равно 50005000.

Во-вторых, можно заметить, что "быстрее всех" сумму рассчитал поток "B" (его идентификатор "thread id", или tid, присвоенный JVM равен 15). С небольшим опозданием сумму рассчитал поток "C" (tid = 16). Далее какое-то время ещё продолжали выполняться потоки "A" (tid = 14) и "D" (tid = 17), но в результате они тоже выполнили задачу и отразили расчётную сумму на экране консоли.

Вспомним, в каком порядке мы запускали наши потоки:

        (new Thread(new SomeRunnable("A"))).start();
        (new Thread(new SomeRunnable("B"))).start();
        (new Thread(new SomeRunnable("C"))).start();
        (new Thread(new SomeRunnable("D"))).start();

Можно сделать заключение о том, что несмотря на то, что поток "A" был запущен раньше, чем "B", всё равно первым свою работу завершил поток "B".

Важно отметить, что последовательность работы потоков может (и, скорее всего, будет) варьироваться при каждом запуске программы, т.е. нет никаких гарантий, что при следующем запуске программы снова быстрее всех  остальных потоков рассчитает итоговую сумму поток "B". Это обусловлено тем, что управление потоками возложено на JVM и её внутренние компоненты, отвечающие за исполнение потоков. В частности, одним из таких компонентов, является thread scheduler, или планировщик потоков. Планировщик потоков решает - на основании состояния операционной системы и других факторов, - какому именно потоку нужно выделить в конкретный момент времени ресурсы компьютера (например, процессорное время) для его выполнения. В нашем случае мы явно не задали потокам приоритет, поэтому их приоритет одинаковый.

Однако приоритетами потоков можно управлять. Для этого в классе Thread есть метод-сеттер setPriority(int newPriority), а также три целочисленные константы для возможности регулирования приоритета потока:

  • MAX_PRIORITY (значение 10) - максимальный приоритет для потока
  • NORM_PRIORITY (значение 5) - нормальный (обычный) приоритет для потока
  • MIN_PRIORITY (значение 1) - минимальный приоритет для потока

Если потоку при его создании явно не был задан приоритет, то по умолчанию он унаследует приоритет создающего потока. В нашем случае мы создавали дочерние потоки "A", "B", "C", "D", находясь (или исполняясь) в главном потоке, поэтому они все унаследовали приоритет нашего главного потока. Получить/узнать текущий приоритет потока можно с помощью геттера getPriority(), который есть в классе Thread.

Разработчик вправе передать в сеттер и своё собственное значение приоритета для потока, но нужно, чтобы оно находилось в пределах от 1 до 10, иначе будет выброшено исключение IllegalArgumentException (т.е. JVM не позволит разработчику установить потоку приоритеты -1, 0, 11, 12 и т. д.).

Давайте попробуем закомментировать четыре строки, где мы запускаем наши потоки, и перепишем часть метода main() следующим образом:

//        (new Thread(new SomeRunnable("A"))).start();
//        (new Thread(new SomeRunnable("B"))).start();
//        (new Thread(new SomeRunnable("C"))).start();
//        (new Thread(new SomeRunnable("D"))).start();

        // Вариант с указанием явного приоритета для каждого потока:
        Thread threadA = new Thread(new SomeRunnable("A"));
        Thread threadB = new Thread(new SomeRunnable("B"));
        Thread threadC = new Thread(new SomeRunnable("C"));
        Thread threadD = new Thread(new SomeRunnable("D"));

        threadA.setPriority(Thread.MAX_PRIORITY);
        threadB.setPriority(Thread.MIN_PRIORITY);
        threadC.setPriority(Thread.MIN_PRIORITY);
        threadD.setPriority(Thread.MIN_PRIORITY);

        threadA.start();
        threadB.start();
        threadC.start();
        threadD.start();

Здесь мы попытались явным образом установить потоку "A" максимальный приоритет (10), а всем остальным потокам - минимальный (1).

При запуске программы лично я убедился в том, что... всё равно нет гарантий того, что первым выполнится поток "A"! Вы можете проверить сами, но вывод напрашивается следующий: несмотря на явную установку максимального приоритета потоку "A", существуют какие-то другие факторы, которые могут привести к более раннему исполнению другого потока, пусть даже с более низким приоритетом. Спрашивается тогда, для чего в этом случае вообще нужны приоритеты для потоков, если результат исполнения потоков с этими приоритетами не гарантирован? Дело в том, что здесь на порядок выполнения потока влияют другие факторы, например, сложность и длительность выполняемой потоком задачи, состояние операционной системы в момент запуска потоков и т. д. Лично я попробовал поэкспериментировать и в цикле, где производится суммирование чисел, увеличил граничное значение для счётчика цикла до 1000 000 (а также коэффициент для граничных значений промежуточных сумм - поднял его до 100 000):

        for (int i = 1; i <= 1000000; i++) {
            if (i > k * 100000) {
                System.out.printf(Localization.getLocalized(
                                "Runnable-экземпляр '%s' вычислил сумму для i > %d! Промежуточная сумма равна %d. Поток #%s%n",
                                "Runnable '%s' has calculated sum for i > %d! The intermediate sum is equal to %d. Thread #%s%n"),
                        getRunnableName(), k, sum, Thread.currentThread().getId());
                k++;
            }
            if (Thread.interrupted()) {
                throw new InterruptedException();
            }
            sum += i;
        }

Можно обратить внимание, что в этом случае поток "A" будет теперь чаще "вырываться вперёд" и завершаться первым, относительно других потоков, поскольку ему был явно задан максимальный приоритет. То есть при "более тяжелой" (или длительной) задаче, возложенной на поток, планировщик потоков будет в любом случае выделять больше процессорного времени потокам с более высоким приоритетом. Я здесь провожу параллель с "бегом на длинной дистанции": на длинной дистанции поток с более высоким приоритетом имеет больше шансов добежать первым, чем остальные. На "коротких дистанциях" явно заданный приоритет для потока может быть не столь ощутим. В любом случае, при явной установке приоритета потока не стоит надеяться на то, что он обязательно исполнится первым. Скорее лучше интерпретировать это как то, что этот поток будет чаще получать ресурсы компьютера, чем потоки с более низкими приоритетами, и с большей вероятностью сможет завершиться первым.

Теперь, когда мы в деталях поговорили о приоритетах потоков и их влияние на "скорость работы" потоков во время выполнения программы, посмотрим на другой способ создания и запуска потоков в Java.

Второй способ создания и запуска потоков. Класс, наследующий Thread.

Второй способ для создания и запуска потоков - это создать собственный класс, который является наследником класса Thread

Давайте, с учётом этого, напишем тот же самый код для вычисления суммы чисел, что уже рассмотрели выше, но поместим его в этот раз в новый класс-наследник от класса Thread с именем SomeThread:

package ru.allineed.samples.threads.thread;

import ru.allineed.samples.config.Localization;

/**
 * [RU] Класс является наследником родительского класса Thread
 * [EN] Class is a child of Thread parent class
 */
public class SomeThread extends Thread {
    private final String threadName;

    public SomeThread(String threadName) {
        this.threadName = threadName;
    }

    public String getThreadName() {
        return threadName;
    }

    private int calculateSum() throws InterruptedException {
        int sum = 0, k = 1;
        for (int i = 1; i <= 10000; i++) {
            if (i > k * 1000) {
                System.out.printf(Localization.getLocalized(
                                "Поток с именем '%s' вычислил сумму для i > %d! Промежуточная сумма равна %d. Поток #%s%n",
                                "Thread '%s' has calculated sum for i > %d! The intermediate sum is equal to %d. Thread #%s%n"),
                        getThreadName(), k, sum, Thread.currentThread().getId());
                k++;
            }
            if (Thread.interrupted()) {
                throw new InterruptedException();
            }
            sum += i;
        }
        return sum;
    }

    @Override
    public void run() {
        System.out.printf(Localization.getLocalized("Это сообщение из класса '%s'! Имя потока: %s, Поток #%s%n", "This is a message from '%s' class! Runnable name: %s, Thread #%s%n"),
                this.getClass().getName(), getThreadName(), Thread.currentThread().getId());

        try {
            int sum = calculateSum();
            System.out.printf(Localization.getLocalized("Поток с именем '%s' завершил работу! Итоговая сумма равна %d. Поток #%s%n","Runnable '%s' has finished! The final sum is equal to %d. Thread #%s%n"), getThreadName(), sum, Thread.currentThread().getId());
        } catch (InterruptedException e) {
            System.out.printf(Localization.getLocalized("Поток с именем '%s' не завершил работу и был прерван! Поток #%s%n","Runnable '%s' has not finished and has been interrupted! Thread #%s%n"), getThreadName(), Thread.currentThread().getId());
        }
    }
}

Теперь для того, чтобы запустить потоки, нужно просто создать экземпляры нашего класса SomeThread и так же, как и раньше, вызвать у них метод start(). Создадим отдельный класс ThreadExample, который также будет иметь метод main() для запуска тестовых потоков: 

package ru.allineed.samples.threads.thread;

import ru.allineed.samples.common.OutputUtils;

public class ThreadExample {

    public static void main(String[] args) {
        OutputUtils.printSampleTitle(
                "Запускаем потоки в Java",
                "Running threads in Java",
                "https://allineed.ru/development/java-development/83-java-starting-threads");

        Thread th1 = new SomeThread("A");
        Thread th2 = new SomeThread("B");
        Thread th3 = new SomeThread("C");
        Thread th4 = new SomeThread("D");

        th1.start();
        th2.start();
        th3.start();
        th4.start();
    }

}

В моём случае на консоли отразился следующий вывод:

========================================================================================================================
>> Запуск примера для статьи "Запускаем потоки в Java"
>> Ссылка на статью: https://allineed.ru/development/java-development/83-java-starting-threads
========================================================================================================================
Это сообщение из класса 'ru.allineed.samples.threads.thread.SomeThread'! Имя потока: D, Поток #17
Это сообщение из класса 'ru.allineed.samples.threads.thread.SomeThread'! Имя потока: B, Поток #15
Это сообщение из класса 'ru.allineed.samples.threads.thread.SomeThread'! Имя потока: C, Поток #16
Это сообщение из класса 'ru.allineed.samples.threads.thread.SomeThread'! Имя потока: A, Поток #14
Поток с именем 'C' вычислил сумму для i > 1! Промежуточная сумма равна 500500. Поток #16
Поток с именем 'B' вычислил сумму для i > 1! Промежуточная сумма равна 500500. Поток #15
Поток с именем 'D' вычислил сумму для i > 1! Промежуточная сумма равна 500500. Поток #17
Поток с именем 'B' вычислил сумму для i > 2! Промежуточная сумма равна 2001000. Поток #15
Поток с именем 'C' вычислил сумму для i > 2! Промежуточная сумма равна 2001000. Поток #16
Поток с именем 'A' вычислил сумму для i > 1! Промежуточная сумма равна 500500. Поток #14
Поток с именем 'C' вычислил сумму для i > 3! Промежуточная сумма равна 4501500. Поток #16
Поток с именем 'B' вычислил сумму для i > 3! Промежуточная сумма равна 4501500. Поток #15
Поток с именем 'D' вычислил сумму для i > 2! Промежуточная сумма равна 2001000. Поток #17
Поток с именем 'B' вычислил сумму для i > 4! Промежуточная сумма равна 8002000. Поток #15
Поток с именем 'C' вычислил сумму для i > 4! Промежуточная сумма равна 8002000. Поток #16
Поток с именем 'A' вычислил сумму для i > 2! Промежуточная сумма равна 2001000. Поток #14
Поток с именем 'C' вычислил сумму для i > 5! Промежуточная сумма равна 12502500. Поток #16
Поток с именем 'B' вычислил сумму для i > 5! Промежуточная сумма равна 12502500. Поток #15
Поток с именем 'D' вычислил сумму для i > 3! Промежуточная сумма равна 4501500. Поток #17
Поток с именем 'B' вычислил сумму для i > 6! Промежуточная сумма равна 18003000. Поток #15
Поток с именем 'C' вычислил сумму для i > 6! Промежуточная сумма равна 18003000. Поток #16
Поток с именем 'A' вычислил сумму для i > 3! Промежуточная сумма равна 4501500. Поток #14
Поток с именем 'C' вычислил сумму для i > 7! Промежуточная сумма равна 24503500. Поток #16
Поток с именем 'B' вычислил сумму для i > 7! Промежуточная сумма равна 24503500. Поток #15
Поток с именем 'D' вычислил сумму для i > 4! Промежуточная сумма равна 8002000. Поток #17
Поток с именем 'B' вычислил сумму для i > 8! Промежуточная сумма равна 32004000. Поток #15
Поток с именем 'C' вычислил сумму для i > 8! Промежуточная сумма равна 32004000. Поток #16
Поток с именем 'A' вычислил сумму для i > 4! Промежуточная сумма равна 8002000. Поток #14
Поток с именем 'C' вычислил сумму для i > 9! Промежуточная сумма равна 40504500. Поток #16
Поток с именем 'B' вычислил сумму для i > 9! Промежуточная сумма равна 40504500. Поток #15
Поток с именем 'D' вычислил сумму для i > 5! Промежуточная сумма равна 12502500. Поток #17
Поток с именем 'B' завершил работу! Итоговая сумма равна 50005000. Поток #15
Поток с именем 'C' завершил работу! Итоговая сумма равна 50005000. Поток #16
Поток с именем 'A' вычислил сумму для i > 5! Промежуточная сумма равна 12502500. Поток #14
Поток с именем 'D' вычислил сумму для i > 6! Промежуточная сумма равна 18003000. Поток #17
Поток с именем 'A' вычислил сумму для i > 6! Промежуточная сумма равна 18003000. Поток #14
Поток с именем 'D' вычислил сумму для i > 7! Промежуточная сумма равна 24503500. Поток #17
Поток с именем 'A' вычислил сумму для i > 7! Промежуточная сумма равна 24503500. Поток #14
Поток с именем 'D' вычислил сумму для i > 8! Промежуточная сумма равна 32004000. Поток #17
Поток с именем 'A' вычислил сумму для i > 8! Промежуточная сумма равна 32004000. Поток #14
Поток с именем 'D' вычислил сумму для i > 9! Промежуточная сумма равна 40504500. Поток #17
Поток с именем 'A' вычислил сумму для i > 9! Промежуточная сумма равна 40504500. Поток #14
Поток с именем 'D' завершил работу! Итоговая сумма равна 50005000. Поток #17
Поток с именем 'A' завершил работу! Итоговая сумма равна 50005000. Поток #14

Process finished with exit code 0

Как видим, в этом примере вычисленная всеми потоками сумма также равна 50005000.

Заключение

Какой именно способ создания потоков выбрать - первый или второй, зависит, в конечном счёте, от тех задач, которые преследует разработчик при проектировании структуры своей программы. Но на мой взгляд, первый вариант - с реализацией интерфейса Runnable - является более удобным и гибким, поскольку даёт больше свободы и позволяет использовать возможности наследования классов (не ограничиваясь тем, что класс обязательно должен наследоваться от Thread).

Тема многопоточности в Java является очень обширной, и в одной статье крайне сложно описать всё многообразие тем, связанных с управлением потоками в программе на Java. Существуют целые книги, посвящённые теме многопоточности в Java. Но при написании других статей по потокам в Java я постараюсь также рассказать про способы синхронизации потоков, про проблемы, которые могут существовать в многопоточных приложениях, а также другие вопросы.

На этом пока всё, спасибо за внимание. Ссылку на примеры кода из данной статьи, а также ссылку на инструкцию по запуску примеров вы найдете внизу в виде отдельного блока.

Делитесь своими вопросами и комментариями внизу под статьёй, а также расскажите о том, часто ли вам приходится сталкиваться с потоками в ваших программах на Java.

 

 

Яндекс.Метрика