4 Методы написания многопоточного кода на Java

Многопоточность — это метод написания кода для параллельного выполнения задач. Java имеет отличную поддержку для написания многопоточного кода с первых дней Java 1.0. Недавние усовершенствования Java расширили способы структурирования кода для включения многопоточности в программы Java.

В этой статье мы сравниваем некоторые из этих вариантов, чтобы вы могли лучше оценить, какой вариант использовать для вашего следующего проекта Java

т.

Несколько рабочих потоков внутри процесса.

Метод 1: Расширение класса Thread

Java обеспечивает Нить класс, который может быть расширен для реализации бежать() метод. Этот метод run () используется для реализации вашей задачи. Если вы хотите запустить задачу в своем собственном потоке, вы можете создать экземпляр этого класса и вызвать его Начните() метод. Это запускает выполнение потока и завершается (или завершается в исключении).

Расширение потока позволяет запускать рабочую задачу в отдельном потоке

Вот простой класс Thread, который просто спит в течение заданного интервала как способ имитации длительной операции.

public class MyThread extends Thread
{
private int sleepFor;
public MyThread(int sleepFor) {
this.sleepFor = sleepFor;
}
@Override
public void run() {
System.out.printf("[%s] thread starting\n",
Thread.currentThread().toString());
try { Thread.sleep(this.sleepFor); }
catch(InterruptedException ex) {}
System.out.printf("[%s] thread ending\n",
Thread.currentThread().toString());
}
}

Создайте экземпляр этого класса Thread, указав количество спящих миллисекунд.

MyThread worker = new MyThread(sleepFor);

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

worker.start();
System.out.printf("[%s] main thread\n", Thread.currentThread().toString());

И вот результат выполнения этого кода. Это указывает на то, что диагностика основного потока печатается до выполнения рабочего потока.

[Thread[main,5,main]] main thread
[Thread[Thread-0,5,main]] thread starting
[Thread[Thread-0,5,main]] thread ending

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

Метод 2: Использование экземпляра потока с Runnable

Java также предоставляет интерфейс под названием Runnable который может быть реализован рабочим классом для выполнения задачи в его бежать() метод. Это альтернативный способ создания рабочего класса в отличие от расширения Нить класс (описано выше).

Класс Papaya расширяет Fruit, но реализует Runnable для возможности запуска задачи в отдельном потоке.

Вот реализация рабочего класса, который теперь реализует Runnable вместо расширения Thread.

public class MyThread2 implements Runnable {
// same as above
}

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

Что это значит?

Допустим, например, у вас есть Фрукты класс, который реализует определенные общие характеристики фруктов. Теперь вы хотите реализовать Папайя класс, который специализируется на определенных характеристиках фруктов. Вы можете сделать это, имея Папайя класс продлить Фрукты учебный класс.

public class Fruit {
// fruit specifics here
}
public class Papaya extends Fruit {
// override behavior specific to papaya here
}

Теперь предположим, что у вас есть какое-то трудоемкое задание, которое нужно поддерживать Papaya, которое можно выполнить в отдельном потоке. Этот случай может быть обработан с помощью класса Papaya, реализующего Runnable и предоставляющего метод run (), где выполняется эта задача.

public class Papaya extends Fruit implements Runnable {
// override behavior specific to papaya here
@Override
public void run() {
// time consuming task here.
}
}

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

Papaya papaya = new Papaya();
// set properties and invoke papaya methods here.
Thread thread = new Thread(papaya);
thread.start();

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

Метод 3: Выполнить Runnable с ExecutorService

ExecutorService предоставляет абстракцию для создания и управления потоками.

Начиная с версии 1.5, Java предоставляет ExecutorService как новая парадигма для создания и управления потоками в программе. Он обобщает концепцию выполнения потоков, абстрагируя их от создания.

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

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

ExecutorService принимает Runnable задача (объяснено выше) и запускает задачу в подходящее время. Отправить() метод, который принимает задачу Runnable, возвращает экземпляр класса с именем Будущее, что позволяет вызывающей стороне отслеживать состояние задачи. В частности, получить() Метод позволяет вызывающей стороне дождаться завершения задачи (и предоставляет код возврата, если таковой имеется).

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

Реализация Runnable, которую мы здесь используем, та же, что описана выше.

ExecutorService esvc = Executors.newSingleThreadExecutor();
Runnable worker = new MyThread2(sleepFor);
Future future = esvc.submit(worker);
System.out.printf("[%s] main thread\n", Thread.currentThread().toString());
future.get();
esvc.shutdown();

Обратите внимание, что служба ExecutorService должна быть надлежащим образом закрыта, когда она больше не нужна для дальнейшей отправки задач.

Метод 4: вызываемый используется с ExecutorService

Начиная с версии 1.5, Java представила новый интерфейс под названием подлежащий выкупу. Он похож на старый интерфейс Runnable с той разницей, что метод выполнения (называемый вызов() вместо бежать()) может вернуть значение. Кроме того, он также может заявить, что исключение можно бросить.

ExecutorService также может принимать задачи, реализованные как подлежащий выкупу и возвращает Будущее со значением, возвращаемым методом по завершении.

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

Реализация интерфейса Callable также может использоваться с ExecutorService

public class Mango extends Fruit implements Callable {
public Integer call() {
// expensive computation here
return new Integer(0);
}
}

А вот код для отправки экземпляра класса в ExecutorService. Приведенный ниже код также ожидает завершения задачи и печатает возвращаемое значение.

ExecutorService esvc = Executors.newSingleThreadExecutor();
MyCallable worker = new MyCallable(sleepFor);
Future future = esvc.submit(worker);
System.out.printf("[%s] main thread\n", Thread.currentThread().toString());
System.out.println("Task returned: " + future.get());
esvc.shutdown();

Что Вы предпочитаете?

В этой статье мы изучили несколько методов написания многопоточного кода на Java. Они включают:

  1. Расширение Нить Класс является самым основным и был доступен с Java 1.0.
  2. Если у вас есть класс, который должен расширять какой-то другой класс в иерархии классов, то вы можете реализовать Runnable интерфейс.
  3. Более современным средством для создания потоков является ExecutorService который может принять экземпляр Runnable в качестве задачи для запуска. Преимущество этого метода в том, что вы можете использовать пул потоков для выполнения задач. Пул потоков помогает в сохранении ресурсов путем повторного использования потоков.
  4. Наконец, вы также можете создать задачу, реализовав подлежащий выкупу интерфейс и отправка задачи в ExecutorService.

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

Ссылка на основную публикацию
Adblock
detector