Что такое threadgroup и зачем он нужен
Перейти к содержимому

Что такое threadgroup и зачем он нужен

  • автор:

Многопоточность Thread, Runnable

Многопоточное программирование позволяет разделить представление и обработку информации на несколько «легковесных» процессов (light-weight processes), имеющих общий доступ как к методам различных объектов приложения, так и к их полям. Многопоточность незаменима в тех случаях, когда графический интерфейс должен реагировать на действия пользователя при выполнении определенной обработки информации. Потоки могут взаимодействовать друг с другом через основной «родительский» поток, из которого они стартованы.

В качестве примера можно привести некоторый поток, отвечающий за представление информации в интерфейсе, который ожидает завершения работы другого потока, загружающего файл, и одновременно отображает некоторую анимацию или обновляет прогресс-бар. Кроме того этот поток может остановить загружающий файл поток при нажатии кнопки «Отмена».

Создатели Java предоставили две возможности создания потоков: реализация (implementing) интерфейса Runnable и расширение(extending) класса Thread. Расширение класса — это путь наследования методов и переменных класса родителя. В этом случае можно наследоваться только от одного родительского класса Thread. Данное ограничение внутри Java можно преодолеть реализацией интерфейса Runnable, который является наиболее распространённым способом создания потоков.

Преимущества потоков перед процессами

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

Главный поток

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

Класс Thread

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

Конструкторы класса Thread
Thread(); Thread(Runnable target); Thread(Runnable target, String name); Thread(String name); Thread(ThreadGroup group, Runnable target); Thread(ThreadGroup group, Runnable target, String name); Thread(ThreadGroup group, String name);
  • target – экземпляр класса реализующего интерфейс Runnable;
  • name – имя создаваемого потока;
  • group – группа к которой относится поток.

Пример создания потока, который входит в группу, реализует интерфейс Runnable и имеет свое уникальное название :

Runnable r = new MyClassRunnable(); ThreadGroup tg = new ThreadGroup(); Thread t = new Thread(tg, r, "myThread");

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

Несмотря на то, что главный поток создаётся автоматически, им можно управлять. Для этого необходимо создать объект класса Thread вызовом метода currentThread().

Методы класса Thread

Наиболее часто используемые методы класса Thread для управления потоками :

  • long getId() — получение идентификатора потока;
  • String getName() — получение имени потока;
  • int getPriority() — получение приоритета потока;
  • State getState() — определение состояния потока;
  • void interrupt() — прерывание выполнения потока;
  • boolean isAlive() — проверка, выполняется ли поток;
  • boolean isDaemon() — проверка, является ли поток «daemon»;
  • void join() — ожидание завершения потока;
  • void join(millis) — ожидание millis милисекунд завершения потока;
  • void notify() — «пробуждение» отдельного потока, ожидающего «сигнала»;
  • void notifyAll() — «пробуждение» всех потоков, ожидающих «сигнала»;
  • void run() — запуск потока, если поток был создан с использованием интерфейса Runnable;
  • void setDaemon(bool) — определение «daemon» потока;
  • void setPriority(int) — определение приоритета потока;
  • void sleep(int) — приостановка потока на заданное время;
  • void start() — запуск потока.
  • void wait() — приостановка потока, пока другой поток не вызовет метод notify();
  • void wait(millis) — приостановка потока на millis милисекунд или пока другой поток не вызовет метод notify();
Жизненный цикл потока

При выполнении программы объект Thread может находиться в одном из четырех основных состояний: «новый», «работоспособный», «неработоспособный» и «пассивный». При создании потока он получает состояние «новый» (NEW) и не выполняется. Для перевода потока из состояния «новый» в «работоспособный» (RUNNABLE) следует выполнить метод start(), вызывающий метод run().

Поток может находиться в одном из состояний, соответствующих элементам статически вложенного перечисления Thread.State :

NEW — поток создан, но еще не запущен;
RUNNABLE — поток выполняется;
BLOCKED — поток блокирован;
WAITING — поток ждет окончания работы другого потока;
TIMED_WAITING — поток некоторое время ждет окончания другого потока;
TERMINATED — поток завершен.

Пример использования Thread

В примере ChickenEgg рассматривается параллельная работа двух потоков (главный поток и поток Egg), в которых идет спор, «что было раньше, яйцо или курица?». Каждый поток высказывает свое мнение после небольшой задержки, формируемой методом ChickenEgg.getTimeSleep(). Побеждает тот поток, который последним говорит свое слово.

package example; import java.util.Random; class Egg extends Thread < @Override public void run() < for(int i = 0; i < 5; i++) < try < // Приостанавливаем поток sleep(ChickenEgg.getTimeSleep()); System.out.println("Яйцо"); >catch(InterruptedException e)<> > > > public class ChickenEgg < public static int getTimeSleep() < final Random random = new Random(); int tm = random.nextInt(1000); if (tm < 10) tm *= 100; else if (tm < 100) tm *= 10; return tm; >public static void main(String[] args) < Egg egg = new Egg (); // Создание потока System.out.println( "Начинаем спор : кто появился первым ?"); egg.start(); // Запуск потока for(int i = 0; i < 5; i++) < try < // Приостанавливаем поток Thread.sleep(ChickenEgg.getTimeSleep()); System.out.println("Курица"); >catch(InterruptedException e)<> > if(egg.isAlive()) < // Cказало ли яйцо последнее слово? try < // Ждем, пока яйцо закончит высказываться egg.join(); >catch (InterruptedException e)<> System.out.println("Первым появилось яйцо . "); > else < //если оппонент уже закончил высказываться System.out.println("Первой появилась курица . "); >System.out.println("Спор закончен"); > >

При выполнении программы в консоль было выведено следующее сообщение.

Начинаем спор : кто появился первым ? Курица Курица Яйцо Курица Яйцо Яйцо Курица Курица Яйцо Яйцо Первым появилось яйцо . Спор закончен

Невозможно точно предсказать, какой поток закончит высказываться последним. При следующем запуске «победитель» может измениться. Это происходит вследствии так называемого «асинхронного выполнения кода». Асинхронность обеспечивает независимость выполнения потоков. Или, другими словами, параллельные потоки независимы друг от друга, за исключением случаев, когда бизнес-логика зависимости выполнения потоков определяется предусмотренными для этого средств языка.

Интерфейс Runnable

Интерфейс Runnable содержит только один метод run() :

interface Runnable

Метод run() выполняется при запуске потока. После определения объекта Runnable он передается в один из конструкторов класса Thread.

Пример класса RunnableExample, реализующего интерфейс Runnable
package example; class MyThread implements Runnable < Thread thread; MyThread() < thread = new Thread(this, "Дополнительный поток"); System.out.println("Создан дополнительный поток " + thread); thread.start(); >@Override public void run() < try < for (int i = 5; i >0; i--) < System.out.println( "\tдополнительный поток: " + i); Thread.sleep(500); >> catch (InterruptedException e) < System.out.println( "\tдополнительный поток прерван"); >System.out.println( "\tдополнительный поток завершён"); > > public class RunnableExample < public static void main(String[] args) < new MyThread(); try < for (int i = 5; i >0; i--) < System.out.println("Главный поток: " + i); Thread.sleep(1000); >> catch (InterruptedException e) < System.out.println("Главный поток прерван"); >System.out.println("Главный поток завершён"); > >

При выполнении программы в консоль было выведено следующее сообщение.

Создан дополнительный поток Thread[Дополнительный поток,5,main] Главный поток: 5 дополнительный поток: 5 дополнительный поток: 4 Главный поток: 4 дополнительный поток: 3 дополнительный поток: 2 Главный поток: 3 дополнительный поток: 1 дополнительный поток завершён Главный поток: 2 Главный поток: 1 Главный поток завершён

Синхронизация потоков, synchronized

В процессе функционирования потоки часто используют общие ресурсы приложения, определенные вне потока. Если несколько потоков начнут одновременно вносить изменения в общий ресурс, то результаты выполнения программы могут быть непредсказуемыми. Рассмотрим следующий пример :

package example; class CommonObject < int counter = 0; >class CounterThread implements Runnable < CommonObject res; CounterThread(CommonObject res) < this.res = res; >@Override public void run() < // synchronized(res) < res.counter = 1; for (int i = 1; i < 5; i++)< System.out.printf("'%s' - %d\n", Thread.currentThread().getName(), res.counter); res.counter++; try < Thread.sleep(100); >catch(InterruptedException e)<> > // > > > public class SynchronizedThread < public static void main(String[] args) < CommonObject commonObject= new CommonObject(); for (int i = 1; i < 6; i++) < Thread t; t = new Thread(new CounterThread(commonObject)); t.setName("Поток " + i); t.start(); >> >

В примере определен общий ресурс в виде класса CommonObject, в котором имеется целочисленное поле counter. Данный ресурс используется внутренним классом, создающим поток CounterThread для увеличения в цикле значения counter на единицу. При старте потока полю counter присваивается значение 1. После завершения работы потока значение res.counter должно быть равно 4.

Две строчки кода класса CounterThread закомментированы. О них речь пойдет ниже.

В главном классе программы SynchronizedThread.main запускается пять потоков. То есть, каждый поток должен в цикле увеличить значение res.counter с единицы до четырех; и так пять раз. Но результат работы программы, отображаемый в консоли, будет иным :

'Поток 4' - 1 'Поток 2' - 1 'Поток 1' - 1 'Поток 5' - 1 'Поток 3' - 1 'Поток 2' - 6 'Поток 4' - 7 'Поток 3' - 8 'Поток 5' - 9 'Поток 1' - 10 'Поток 2' - 11 'Поток 4' - 12 'Поток 5' - 13 'Поток 3' - 13 'Поток 1' - 15 'Поток 4' - 16 'Поток 2' - 16 'Поток 3' - 18 'Поток 5' - 18 'Поток 1' - 20

То есть, с общим ресурсов res.counter работают все потоки одновременно, поочередно изменяя значение.

Чтобы избежать подобной ситуации, потоки необходимо синхронизировать. Одним из способов синхронизации потоков связан с использованием ключевого слова synchronized. Оператор synchronized позволяет определить блок кода или метод, который должен быть доступен только одному потоку. Можно использовать synchronized в своих классах определяя синхронизированные методы или блоки. Но нельзя использовать synchronized в переменных или атрибутах в определении класса.

Блокировка на уровне объекта

Блокировать общий ресурс можно на уровне объекта, но нельзя использовать для этих целей примитивные типы. В примере следует удалить строчные комментарии в классе CounterThread, после чего общий ресурс будет блокироваться как только его захватит один из потоков; остальные потоки будут ждать в очереди освобождения ресурса. Результат работы программы при синхронизации доступа к общему ресурсу резко изменится :

'Поток 1' - 1 'Поток 1' - 2 'Поток 1' - 3 'Поток 1' - 4 'Поток 5' - 1 'Поток 5' - 2 'Поток 5' - 3 'Поток 5' - 4 'Поток 4' - 1 'Поток 4' - 2 'Поток 4' - 3 'Поток 4' - 4 'Поток 3' - 1 'Поток 3' - 2 'Поток 3' - 3 'Поток 3' - 4 'Поток 2' - 1 'Поток 2' - 2 'Поток 2' - 3 'Поток 2' - 4

Следующий код демонстрирует порядок использования оператора synchronized для блокирования доступа к объекту.

synchronized (оbject) < // other thread safe code >
Блокировка на уровне метода и класса

Блокировать доступ к ресурсам можно на уровне метода и класса. Следующий код показывает, что если во время выполнения программы имеется несколько экземпляров класса DemoClass, то только один поток может выполнить метод demoMethod(), для других потоков доступ к методу будет заблокирован. Это необходимо когда требуется сделать определенные ресурсы потокобезопасными.

public class DemoClass < public synchronized static void demoMethod()< // . >> // или public class DemoClass < public void demoMethod()< synchronized (DemoClass.class) < // . >> >

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

Некоторые важные замечания использования synchronized
  1. Синхронизация в Java гарантирует, что два потока не могут выполнить синхронизированный метод одновременно.
  2. Оператор synchronized можно использовать только с методами и блоками кода, которые могут быть как статическими, так и не статическими.
  3. Если один из потоков начинает выполнять синхронизированный метод или блок, то этот метод/блок блокируются. Когда поток выходит из синхронизированного метода или блока JVM снимает блокировку. Блокировка снимается, даже если поток покидает синхронизированный метод после завершения из-за каких-либо ошибок или исключений.
  4. Синхронизация в Java вызывает исключение NullPointerException, если объект, используемый в синхронизированном блоке, не определен, т.е. равен null.
  5. Синхронизированные методы в Java вносят дополнительные затраты на производительность приложения. Поэтому следует использовать синхронизацию, когда она абсолютно необходима.
  6. В соответствии со спецификацией языка нельзя использовать synchronized в конструкторе, т.к. приведет к ошибке компиляции.

Примечание : для синхронизации потоков можно использовать объекты синхронизации Synchroniser’s пакета java.util.concurrent.

Взаимная блокировка

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

Основные условия возникновения взаимоблокировок в многопотоковом приложении :

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

Взаимодействие между потоками в Java, wait и notify

При взаимодействии потоков часто возникает необходимость приостановки одних потоков и их последующего извещения о завершении определенных действий в других потоков. Так например, действия первого потока зависят от результата действий второго потока, и надо каким-то образом известить первый поток, что второй поток произвел/завершил определенную работу. Для подобных ситуаций используются методы :

  • wait() — освобождает монитор и переводит вызывающий поток в состояние ожидания до тех пор, пока другой поток не вызовет метод notify();
  • notify() — продолжает работу потока, у которого ранее был вызван метод wait();
  • notifyAll() — возобновляет работу всех потоков, у которых ранее был вызван метод wait().

Все эти методы вызываются только из синхронизированного контекста (синхронизированного блока или метода).

Рассмотрим пример «Производитель-Склад-Потребитель» (Producer-Store-Consumer). Пока производитель не поставит на склад продукт, потребитель не может его забрать. Допустим производитель должен поставить 5 единиц определенного товара. Соответственно потребитель должен весь товар получить. Но, при этом, одновременно на складе может находиться не более 3 единиц товара. При реализации данного примера используем методы wait() и notify().

Листинг класса Store
package example; public class Store < private int counter = 0; public synchronized void get() < while (counter < 1) < try < wait(); >catch (InterruptedException e) <> > counter--; System.out.println("-1 : товар забрали"); System.out.println( "\tколичество товара на складе : " + counter); notify(); > public synchronized void put() < while (counter >= 3) < try < wait(); >catch (InterruptedException e) <> > counter++; System.out.println("+1 : товар добавили"); System.out.println( "\tколичество товара на складе : " + counter); notify(); > >

Класс Store содержит два синхронизированных метода для получения товара get() и для добавления товара put(). При получении товара выполняется проверка счетчика counter. Если на складе товара нет, то есть counter < 1, то вызывается метод wait(), который освобождает монитор объекта Store и блокирует выполнение метода get(), пока для этого монитора не будет вызван метод notify().

При добавлении товара также выполняется проверка количества товара на складе. Если на складе больше 3 единиц товара, то поставка товара приостанавливается и вызывается метод notify(), который передает управление методу get() для завершения цикла while().

Листинги классов Producer и Consumer

Классы Producer и Consumer реализуют интерфейс Runnable, методы run() у них переопределены. Конструкторы этих классов в качестве параметра получают объект склад Store. При старте данных объектов в виде отдельных потоков в цикле вызываются методы put() и get() класса Store для «добавления» и «получения» товара.

package example; public class Producer implements Runnable < Store store; Producer(Store store) < this.store=store; >@Override public void run() < for (int i = 1; i < 6; i++) < store.put(); >> > public class Consumer implements Runnable < Store store; Consumer(Store store) < this.store=store; >@Override public void run() < for (int i = 1; i < 6; i++) < store.get(); >> >
Листинг класса Trade

В главном потоке класса Trade (в методе main) создаются объекты Producer-Store-Consumer и стартуются потоки производителя и потребителя.

package example; public class Trade < public static void main(String[] args) < Store store = new Store(); Producer producer = new Producer(store); Consumer consumer = new Consumer(store); new Thread(producer).start(); new Thread(consumer).start(); >>

При выполнении программы в консоль будут выведены следующие сообщения :

+1 : товар добавили количество товара на складе : 1 +1 : товар добавили количество товара на складе : 2 +1 : товар добавили количество товара на складе : 3 -1 : товар забрали количество товара на складе : 2 -1 : товар забрали количество товара на складе : 1 -1 : товар забрали количество товара на складе : 0 +1 : товар добавили количество товара на складе : 1 +1 : товар добавили количество товара на складе : 2 -1 : товар забрали количество товара на складе : 1 -1 : товар забрали количество товара на складе : 0

Поток-демон, daemon

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

Однако это правило не относится к потоков-демонам (daemon). Если завершился последний обычный поток процесса, и остались только daemon потоки, то они будут принудительно завершены и выполнение приложения закончится. Чаще всего daemon потоки используются для выполнения фоновых задач, обслуживающих процесс в течение его жизни.

Объявить поток демоном достаточно просто. Для этого нужно перед запуском потока вызвать его метод setDaemon(true). Проверить, является ли поток daemon‘ом можно вызовом метода isDaemon(). В качестве примера использования daemon-потока можно рассмотреть класс Trade, который принял бы следующий вид :

package example; public class Trade < public static void main(String[] args) < Producer producer = new Producer(store); Consumer consumer = new Consumer(store); // new Thread(producer).start(); // new Thread(consumer).start(); Thread tp = new Thread(producer); Thread tc = new Thread(consumer); tp.setDaemon(true); tc.setDaemon(true); tp.start(); tc.start(); try < Thread.sleep(100); >catch (InterruptedException e) <> System.out.println("\nГлавный поток завершен\n"); System.exit(0); > >

Здесь можно самостоятельно поэкспериментировать с определением daemon-потока для одного из классов (producer, consumer) или обоих классов, и посмотреть, как система (JVM) будет вести себя.

Thread и Runnable, что выбрать ?

Зачем нужно два вида реализации многопоточности; какую из них и когда использовать? Ответ несложен. Реализация интерфейса Runnable используется в случаях, когда класс уже наследует какой-либо родительский класс и не позволяет расширить класс Thread. К тому же хорошим тоном программирования в java считается реализация интерфейсов. Это связано с тем, что в java может наследоваться только один родительский класс. Таким образом, унаследовав класс Thread, невозможно наследовать какой-либо другой класс.

Расширение класса Thread целесообразно использовать в случае необходимости переопределения других методов класса помимо метода run().

Приоритеты выполнения и голодание

Иногда разработчики используют приоритеты выполнения потока. В Java есть планировщик потоков (Thread Scheduler), который контролирует все запущенные потоки и решает, какие потоки должны быть запущены и какая строка кода должна выполняться. Решение основывается на приоритете потока. Поэтому потоки с меньшим приоритетом получают меньше процессорного времени по сравнению с потоками с бо́льшим приоритет. Данное разумное решением может стать причиной проблем при злоупотреблении. То есть, если бо́льшую часть времени исполняются потоки с высоким приоритетом, то низкоприоритетные потоки начинают «голодать», поскольку не получают достаточно времени для того, чтобы выполнить свою работу должным образом. Поэтому рекомендуется задавать приоритет потока только тогда, когда для этого имеются веские основания.

Неочевидный пример «голодания» потока даёт метод finalize(), предоставляющий возможность выполнить код перед тем, как объект будет удалён сборщиком мусора. Однако приоритет финализирующего потока невысокий. Следовательно, возникают предпосылки для потокового голодания, когда методы finalize() объекта тратят слишком много времени (большие задержки) по сравнению с остальным кодом.

Другая проблема со временем исполнения может возникнуть от того, что не был определен порядок прохождения потоком блока synchronized. Когда несколько параллельных потоков должны выполнить некоторый код, оформленный блоком synchronized, может получиться так, что одним потокам придётся ждать дольше других, прежде чем войти в блок. Теоретически они могут вообще туда не попасть.

Скачать примеры

Рассмотренные на странице примеры многопоточности и синхронизации потоков в виде проекта Eclipse можно скачать здесь (14Кб).

Уровень 28. Ответы на вопросы к собеседованию по теме уровня

Java-университет

Уровень 28. Ответы на вопросы к собеседованию по теме уровня - 1

  1. Какие приоритеты нитей бывают? Ответ на этот вопрос есть в лекциях JavaRush. Для оптимизации параллельной работы нитей в Java имеется возможность устанавливать приоритеты нитей. Нити с большим приоритетом имеют преимущество в получении времени процессора перед нитями с более низким приоритетом. Работа с приоритетами обеспечивается следующими методами класса Thread : public final void setPriority(int newPriority) Устанавливает приоритет нити. public final int getPriority() Позволяет узнать приоритет нити. Значение параметра в методе setPriority не может произвольным. Оно должно находиться в пределах от MIN_PRIORITY до MAX_PRIORITY . При своем создании нить имеет приоритет NORM_PRIORITY . MIN_PRIORITY = 1.
    NORM_PRIORITY =5.
    MAX_PRIORITY = 10.
  2. Можно ли остановить нить, снизив ее приоритет до 0? Ответ в статье: «Топ 50 вопросов на собеседовании. Тема: Многопоточность (Multithreading)» На форуме нашел. Есть вариант этой статьи на английском языке: Top 50 Java Thread Interview Questions Answers for Freshers, Experienced Programmers Java предоставляет богатые API для всего, но, по иронии судьбы, не предоставляет удобных способов остановки нити. В JDK 1.0 было несколько управляющих методов, например stop() , suspend() и resume() , которые были помечены как deprecated в будущих релизах из-за потенциальных угроз взаимной блокировки, с тех пор разработчики Java API не предприняли попыток представить стойкий, ните-безопасный и элегантный способ остановки нитей. Программисты в основном полагаются на факт того, что нить останавливается сама, как только заканчивает выполнять методы run() или call() . Для остановки вручную, программисты пользуются преимуществом volatile boolean переменной и проверяют её значение в каждой итерации, если в методе run() есть циклы, или прерывают нити методом interrupt() для внезапной отмены заданий. Конкретно по вопросу: Нигде не видел, чтобы кто-то приоритет выставлял в 0. Если кто знает об этом что-нибудь, то напишите в комментариях.
  3. Зачем нужен класс ThreadGroup ? ThreadGroup представляет собой набор нитей, которые так же могут содержать в себе другие группы потоков. Группа нитей образует дерево, в котором каждая другая группа нитей имеет родителя (кроме исходной). Поток имеет право доступа к данным из своей группы нитей, но не имеет такого доступа к другим группам или к родительской группе потоков.
  4. В какой группе нитей состоит main-thread ? Нигде не нашел)) Подскажите где это есть))
  5. Что такое паттерн ThreadPool ? На это есть выдержка из статьи на википедии: In computer programming, the thread pool pattern (also replicated workers or worker-crew model) is where a number of threads are created to perform a number of tasks, which are usually organized in a queue. The results from the tasks being executed might also be placed in a queue, or the tasks might return no result (for example, if the task is for animation). Typically, there are many more tasks than threads. As soon as a thread completes its task, it will request the next task from the queue until all tasks have been completed. The thread can then terminate, or sleep until there are new tasks available. The number of threads used is a parameter that can be tuned to provide the best performance. Additionally, the number of threads can be dynamic based on the number of waiting tasks. For example, a web server can add threads if numerous web page requests come in and can remove threads when those requests taper down. The cost of having a larger thread pool is increased resource usage. The algorithm used to determine when to create or destroy threads will have an impact on the overall performance:
    • create too many threads, and resources are wasted and time also wasted creating any unused threads
    • destroy too many threads and more time will be spent later creating them again
    • creating threads too slowly might result in poor client performance (long wait times)

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

Как правило, существует гораздо больше задач, чем потоков. Как только поток завершит свою задачу, он будет запрашивать следующую задачу из очереди, пока все задачи не будут завершены. Поток может затем прерваться или заснуть. Количество используемых потоков, это параметр, который может быть настроен, для обеспечения наилучшей производительности. Кроме того, число потоков может быть динамическим на основе количества возникающих задач. Например, веб-сервер может добавлять потоки, если запросы многочисленных веб-страниц приходят и может удалить потоки, когда этих запросов становится меньше. С увеличением размера пула потоков увеличивается использование ресурсов компьютера. Алгоритм, используемый для определения того, когда создавать или уничтожать потоки, будет иметь влияние на общую производительность: — Создать слишком много потоков значит тратить ресурсы и время впустую.

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

 Листинг Создание задачи с помощью интерфейса Callable 10 1 import java.util.concurrent.Callable; 11 2 public class CallableSample implements Callable < 12 3 public String call() throws Exception < 13 4 if(какое-то условие) < 14 5 throw new IOException("error during task processing"); 15 6 >16 7 System.out.println("task is processing"); 17 8 return "result "; 18 9 > 19 10 > 
 if (mayInterruptIfRunning)

Как использовать JMeter для нагрузочного тестирования и тестирования производительности

Apache JMeter — это инструмент для проведения нагрузочного тестирования, представляющий собой десктопное приложение с открытым исходным кодом на базе Java. JMeter позволяет определить, может ли тестируемое веб-приложение удовлетворять требованиям высокой нагрузки или нет. Он также помогает проанализировать работу всего сервера под высокой нагрузкой.

Тестирование производительности приложения с JMeter

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

Этот инструмент предлагает следующие преимущества при тестировании производительности:

  • JMeter можно использовать для тестирования производительности как статических ресурсов, таких как JavaScript и HTML, так и динамических ресурсов, таких как JSP, сервлеты и AJAX.
  • С помощью JMeter можно определить максимальное количество пользователей в единицу времени, которое может выдержать тестируемый сайт.
  • JMeter предоставляет разнообразные визуальные отчеты проводимого анализа производительности.

Тестирование производительности с JMeter включает в себя:

  • Нагрузочное тестирование: моделирование ожидаемого использования веб-приложения путем имитации одновременного доступа множества пользователей.
  • Стресс-тестирование: каждый веб-сервер имеет предел максимальной нагрузки. Когда нагрузка превышает предел, веб-сервер начинает отвечать медленно и выдавать ошибки. Цель стресс-тестирования — определить максимальную нагрузку, которую может выдержать веб-сервер.

На рисунке ниже показано, как JMeter во время нагрузочного тестирования имитирует высокую нагрузку:

Создание плана тестирования производительности в JMeter

В этом руководстве мы проводим анализ производительности Google.com для 1000 пользователей с использованием различных инструментов тестирования производительности. Наша цель — убедиться, что сайт может справиться с нагрузкой и обеспечить непрерывный и беспроблемный пользовательский опыт.

Перед тестированием производительности веб-приложения мы должны определить:

  • Нормальную нагрузку: среднее количество пользователей, посещающих сайт.
  • Большую нагрузку: максимальное количество пользователей, посещающих сайт.
  • Цель проводимого тестирования.

Вот дорожная карта этого практического примера

Performance Testing using Jmeter

Шаг 1) Добавить Thread Group (группа потоков)

  1. Запустите JMeter
  2. Выберите Test Plan (План теста)
  3. Добавить Thread Group

Щелкните правой кнопкой мыши на «Test Plan» и добавьте новую группу потоков: Add -> Threads (Users) (Потоки (Пользователи)) -> Thread Group

Performance Testing using Jmeter

На панели управления Thread Group введите Thread Properties (Свойства потока) следующим образом:

Performance Testing using Jmeter

  • Number of Threads (Количество потоков): 100 (Количество пользователей, подключенных к веб-сайту: 100)
  • Loop Count (Количество итераций): 10 (Количество раз выполнения тестирования)
  • Ramp-Up Period : 100

Thread Count (Счетчик потоков) и Loop Counts (Счетчик итераций) отличаются.

Performance Testing using Jmeter

Ramp-Up Period указывает JMeter, какую задержку перед запуском следующего пользователя нужно сделать. Например, если у нас 100 пользователей и период Ramp-Up 100 секунд, то задержка между запуском пользователей составит 1 секунду (100 секунд /100 пользователей).

Шаг 2) Добавление элементов JMeter

Теперь мы определяем, какие элементы JMeter будут в этом тесте. Этими элементами являются

Этот элемент можно добавить, щелкнув правой кнопкой мыши по Thread Group и выбрав: Add -> Config Element (Конфигурационные элементы) -> HTTP Request Defaults.

Performance Testing using Jmeter

В панели управления HTTP Request Defaults введите адрес тестируемого веб-сайта (http://www.google.com).

Performance Testing using Jmeter

  • HTTP Request (HTTP-запрос)

Щелкните правой кнопкой мыши на Thread Group и выберите: Add -> Sampler ( Сэмплер ) -> HTTP Request.

Performance Testing using Jmeter

В панели управления HTTP-запросами (HTTP Request) поле Path указывает, какой URL-запрос (URL request) вы хотите отправить на сервер Google.

Performance Testing using Jmeter

Например, если вы введете «calendar» в поле Path, JMeter создаст URL-запрос http://www.google.com/calendar к серверу Google.

Performance Testing using Jmeter

Если оставить поле Path пустым, JMeter создаст URL-запрос http://www.google.com на сервер Google.

В этом тесте оставляем поле Path пустым, чтобы JMeter создал URL-запрос http://www.google.com на сервер Google.

Шаг 3) Добавление Graph Result (Графические результаты)

JMeter может показать результат теста в формате графика. Щелкните правой кнопкой мыши на Test Plan (План тестирования), Add (Добавить) -> Listener (Слушатель) -> Graph Results

Performance Testing using Jmeter

Шаг 4) Запустите тест и получите результат тестирования

Нажмите кнопку Run (Ctrl + R) на панели инструментов, чтобы запустить процесс тестирования. Результат тестирования будет показан на графике в режиме реального времени. На рисунке ниже представлен график плана тестирования, в котором мы смоделировали 100 пользователей, зашедших на сайт www.google.com.

В нижней части рисунка приведена следующая статистика, представленная в цветах:

  • Черный: Общее текущее количество отправленных сэмплов.
  • Синий: Текущее среднее значение всех отправленных сэмплов.
  • Красный: Текущее стандартное отклонение (Deviation).
  • Зеленый: Пропускная способность (Throughput), которая представляет собой количество запросов в минуту, обработанных сервером.

Проанализируем производительность сервера Google на рисунке ниже.

Performance Testing using Jmeter

Чтобы проанализировать производительность тестируемого веб-сервера, необходимо сосредоточиться на 2 параметрах:

  • Пропускная способность (Throughput)
  • Отклонение/погрешность (Deviation)

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

В данном тесте пропускная способность сервера Google составляет 1 491,193 в минуту. Это означает, что сервер Google может обрабатывать 1 491,193 запросов в минуту. Это довольно высокое значение, поэтому можно сделать вывод, что у сервера Google хорошая производительность.

Отклонение показано красным цветом — оно указывает на отклонение от среднего значения. Чем меньше, тем лучше.

Давайте сравним производительность сервера Google с другими веб-серверами. Вот результат тестирования производительности сайта http://www.yahoo.com/ (вы можете выбрать другие сайты)

Performance Testing using Jmeter

Пропускная способность тестируемого сайта http://www.yahoo.com составляет 867,326 в минуту. Это означает, что данный сервер обрабатывает 867,326 запросов в минуту, что ниже, чем у Google.

Отклонение составляет 2689, что намного больше, чем у Google (577). Таким образом, мы можем определить, что производительность этого сайта меньше, чем у сервера Google.

Примечание: Приведенные выше значения зависят от нескольких факторов, таких как текущая загрузка сервера Google, скорость интернета, мощность процессора и т.д. Следовательно, очень маловероятно, что вы получите те же результаты, что и выше. Поэтому не паникуйте! Если вы хотите понять, как нагрузка на сервер влияет на производительность и как ее проверить, вам может быть полезно это руководство по нагрузочному тестированию.

Устранение ошибок:

Если вы столкнулись с проблемой при выполнении вышеописанного сценария, сделайте следующее:

  1. Проверьте, подключены ли вы к интернету через прокси-сервер. Если да, удалите прокси.
  2. Откройте новый экземпляр Jmeter.
  3. Откройте PerformanceTestPlan.jmx в Jmeter
  4. Дважды щелкните на Thread Group -> Graph Result
  5. Запустите тест

Собеседование по Java — многопоточность (вопросы и ответы)

Вопросы и ответы для собеседования Java по теме — многопоточность.

К списку вопросов по всем темам

Вопросы

1. Дайте определение понятию “процесс”.
2. Дайте определение понятию “поток”.
3. Дайте определение понятию “синхронизация потоков”.
4. Как взаимодействуют программы, процессы и потоки?
5. В каких случаях целесообразно создавать несколько потоков?
6. Что может произойти если два потока будут выполнять один и тот же код в программе?
7. Что вы знаете о главном потоке программы?
8. Какие есть способы создания и запуска потоков?
9. Какой метод запускает поток на выполнение?
10. Какой метод описывает действие потока во время выполнения?
11. Когда поток завершает свое выполнение?
12. Как синхронизировать метод?
13. Как принудительно остановить поток?
14. Дайте определение понятию “поток-демон”.
15. Как создать поток-демон?
16. Как получить текущий поток?
17. Дайте определение понятию “монитор”.
18. Как приостановить выполнение потока?
19. В каких состояниях может пребывать поток?
20. Что является монитором при вызове нестатического и статического метода?
21. Что является монитором при выполнении участка кода метода?
22. Какие методы позволяют синхронизировать выполнение потоков?
23. Какой метод переводит поток в режим ожидания?
24. Какова функциональность методов notify и notifyAll?
25. Что позволяет сделать метод join?
26. Каковы условия вызова метода wait/notify?
27. Дайте определение понятию “взаимная блокировка”.
28. Чем отличаются методы interrupt, interrupted, isInterrupted?
29. В каком случае будет выброшено исключение InterruptedException, какие методы могут его выбросить?
30. Модификаторы volatile и метод yield().
31. Пакет java.util.concurrent
32. Есть некоторый метод, который исполняет операцию i++. Переменная i типа int. Предполагается, что код будет исполнятся в многопоточной среде. Следует ли синхронизировать блок?
33. Что используется в качестве mutex, если метод объявлен static synchronized? Можно ли создавать новые экземпляры класса, пока выполняется static synchronized метод?
34. Предположим в методе run возник RuntimeException, который не был пойман. Что случится с потоком? Есть ли способ узнать о том, что Exception произошел (не заключая все тело run в блок try-catch)? Есть ли способ восстановить работу потока после того как это произошло?
35. Какие стандартные инструменты Java вы бы использовали для реализации пула потоков?
36.Что такое ThreadGroup и зачем он нужен?
37.Что такое ThreadPool и зачем он нужен?
38.Что такое ThreadPoolExecutor и зачем он нужен?
39.Что такое «атомарные типы» в Java?
40.Зачем нужен класс ThreadLocal?
41.Что такое Executor?
42.Что такое ExecutorService?
43.Зачем нужен ScheduledExecutorService?

Ответы

1. Дайте определение понятию “процесс”.

Процесс — это совокупность кода и данных, разделяющих общее виртуальное адресное пространство. Процессы изолированы друг от друга, поэтому прямой доступ к памяти чужого процесса невозможен (взаимодействие между процессами осуществляется с помощью специальных средств). Для каждого процесса ОС создает так называемое «виртуальное адресное пространство», к которому процесс имеет прямой доступ. Это пространство принадлежит процессу, содержит только его данные и находится в полном его распоряжении. Операционная система же отвечает за то, как виртуальное пространство процесса проецируется на физическую память.

Многопоточность в Java: http://habrahabr.ru/post/164487/
2. Дайте определение понятию “поток”.

Один поток («нить» или «трэд») – это одна единица исполнения кода. Каждый поток последовательно выполняет инструкции процесса, которому он принадлежит, параллельно с другими потоками этого процесса.

Thinking in Java.Параллельное выполнение. http://wikijava.it-cache.net/index.php@title=Glava_17_Thinking_in_Java_4th_edition.html
3. Дайте определение понятию “синхронизация потоков”.

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

Java поддерживает несколько потоков для выполнения. Это может привести к тому, что два или более потока получат доступ к одному и тому же полю или объекту. Синхронизация — это процесс, который позволяет выполнять все параллельные потоки в программе синхронно. Синхронизация позволяет избежать ошибок согласованности памяти, вызванных непоследовательным доступом к общей памяти.
Когда метод объявлен как синхронизированный — нить держит монитор для объекта, метод которого исполняется. Если другой поток выполняет синхронизированный метод, ваш поток заблокируется до тех пор, пока другой поток не отпустит монитор.
Синхронизация достигается в Java использованием зарезервированного слова synchronized. Вы можете использовать его в своих классах определяя синхронизированные методы или блоки. Вы не сможете использовать synchronized в переменных или атрибутах в определении класса.

Синхронизация потоков, блокировка объекта и блокировка класса info.javarush.ru: http://goo.gl/gW4ONp
4. Как взаимодействуют программы, процессы и потоки?

Чаще всего одна программа состоит из одного процесса, но бывают и исключения (например, браузер Chrome создает отдельный процесс для каждой вкладки, что дает ему некоторые преимущества, вроде независимости вкладок друг от друга). В каждом процессе может быть создано множество потоков. Процессы разделены между собой (>программы), потоки в одном процессе могут взаимодействовать друг с другом (методы wait, notify, join и т.д.).

5. В каких случаях целесообразно создавать несколько потоков?

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

6. Что может произойти если два потока будут выполнять один и тот же код в программе?

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

7. Что вы знаете о главном потоке программы?

Маленькие программы на Java обычно состоят из одной нити, называемой «главной нитью» (main thread). Но программы побольше часто запускают дополнительные нити, их еще называют «дочерними нитями». Главная нить выполняет метод main и завершается. Аналогом такого метода main, для дочерних нитей служит метод run интерфейса Runnable. Много потоков — много методов main (run()).

8. Какие есть способы создания и запуска потоков?

Существует несколько способов создания и запуска потоков.

С помощью класса, реализующего Runnable

  • Создать объект класса Thread .
  • Создать объект класса, реализующего интерфейс Runnable
  • Вызвать у созданного объекта Thread метод start() (после этого запустится метод run() у переданного объекта, реализующего Runnable )

С помощью класса, расширяющего Thread

  • Создать объект класса ClassName extends Thread .
  • Переопределить run() в этом классе (смотрите пример ниже, где передается имя потока ‘Second’)

С помощью класса, реализующего java.util.concurrent.Callable

  • Создать объект класса, реализующего интерфейс Callable
  • Создать объект ExecutorService с указанием пула потоков.
  • Создать объект Future. Запуск происходит через метод submit() ; Сигнатура: Future submit(Callable task)

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *