Программирование

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

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

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

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

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

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

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

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

Вот простой класс 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: использование экземпляра потока с работоспособным

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

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

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

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

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

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

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

 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. } } 

Чтобы запустить рабочий поток, вы создаете экземпляр рабочего класса и передаете его в экземпляр Thread при создании. Когда вызывается метод 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 (объяснено выше) и запускает задачу в подходящее время. Метод submit () , который принимает задачу Runnable, возвращает экземпляр класса с именем Future , который позволяет вызывающей стороне отслеживать состояние задачи. В частности, метод get () позволяет вызывающей стороне ожидать завершения задачи (и предоставляет код возврата, если таковой имеется).

В приведенном ниже примере мы создаем 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 представила новый интерфейс под названием Callable . Он аналогичен старому интерфейсу Runnable с той разницей, что метод выполнения (вызываемый call () вместо run () ) может возвращать значение. Кроме того, он также может объявить, что может быть сгенерировано исключение .

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

Вот пример класса Mango, который расширяет класс Fruit, определенный ранее, и реализует интерфейс Callable . В методе call () выполняется дорогостоящая и трудоемкая задача.

Реализация интерфейса 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. Расширение класса Thread является самым базовым и было доступно в Java 1.0.
  2. Если у вас есть класс, который должен расширять какой-то другой класс в иерархии классов, вы можете реализовать интерфейс Runnable .
  3. Более современное средство для создания потоков — это ExecutorService, который может принимать экземпляр Runnable в качестве задачи для запуска. Преимущество этого метода в том, что вы можете использовать пул потоков для выполнения задач. Пул потоков помогает в сохранении ресурсов путем повторного использования потоков.
  4. Наконец, вы также можете создать задачу, реализовав интерфейс Callable и отправив задачу в ExecutorService.

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

Похожие посты
Программирование

Что такое канал RSS? (И где его взять)

Программирование

7 причин, почему изображения не загружаются на ваш сайт

Программирование

Запустите агент SQL Server: настройте SQL Server 2012

Программирование

15 лучших бесплатных обоев дня Святого Патрика