Многопоточное программирование позволяет разделить представление и обработку информации на несколько «легковесных» процессов (light-weight processes), имеющих общий доступ как к методам различных объектов приложения, так и к их полям. Многопоточность незаменима в тех случаях, когда графический интерфейс должен реагировать на действия пользователя при выполнении определенной обработки информации. Потоки могут взаимодействовать друг с другом через основной «родительский» поток, из которого они стартованы.
В качестве примера можно привести некоторый поток, отвечающий за представление информации в интерфейсе, который ожидает завершения работы другого потока, загружающего файл, и одновременно отображает некоторую анимацию или обновляет прогресс-бар. Кроме того этот поток может остановить загружающий файл поток при нажатии кнопки «Отмена».
Создатели Java предоставили две возможности создания потоков: реализация (implementing) интерфейса Runnable и расширение(extending) класса Thread . Расширение класса - это путь наследования методов и переменных класса родителя. В этом случае можно наследоваться только от одного родительского класса Thread . Данное ограничение внутри Java можно преодолеть реализацией интерфейса Runnable , который является наиболее распространённым способом создания потоков.
Каждое java приложение имеет хотя бы один выполняющийся поток. Поток, с которого начинается выполнение программы, называется главным. После создания процесса, как правило, JVM начинает выполнение главного потока с метода main(). Затем, по мере необходимости, могут быть запущены дополнительные потоки. Многопоточность - это два и более потоков, выполняющихся одновременно в одной программе. Компьютер с одноядерным процессором может выполнять только один поток, разделяя процессорное время между различными процессами и потоками.
В классе 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);
Пример создания потока, который входит в группу, реализует интерфейс Runnable и имеет свое уникальное название:
Runnable r = new MyClassRunnable(); ThreadGroup tg = new ThreadGroup(); Thread t = new Thread(tg, r, "myThread");
Группы потоков удобно использовать, когда необходимо одинаково управлять несколькими потоками. Например, несколько потоков выводят данные на печать и необходимо прервать печать всех документов поставленных в очередь. В этом случае удобно применить команду ко всем потокам одновременно, а не к каждому потоку отдельно. Но это можно сделать, если потоки отнесены к одной группе.
Несмотря на то, что главный поток создаётся автоматически, им можно управлять. Для этого необходимо создать объект класса Thread вызовом метода currentThread() .
Наиболее часто используемые методы класса Thread для управления потоками:
При выполнении программы объект Thread может находиться в одном из четырех основных состояний: «новый», «работоспособный», «неработоспособный» и «пассивный». При создании потока он получает состояние «новый» (NEW) и не выполняется. Для перевода потока из состояния «новый» в «работоспособный» (RUNNABLE) следует выполнить метод start(), вызывающий метод run().
Поток может находиться в одном из состояний, соответствующих элементам статически вложенного перечисления Thread.State:
NEW - поток создан, но еще не запущен;
RUNNABLE - поток выполняется;
BLOCKED - поток блокирован;
WAITING - поток ждет окончания работы другого потока;
TIMED_WAITING - поток некоторое время ждет окончания другого потока;
TERMINATED - поток завершен.
В примере 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 содержит только один метод run() :
Interface Runnable { void run(); }
Метод run() выполняется при запуске потока. После определения объекта Runnable он передается в один из конструкторов класса Thread .
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 Главный поток завершён
В процессе функционирования потоки часто используют общие ресурсы приложения, определенные вне потока. Если несколько потоков начнут одновременно вносить изменения в общий ресурс, то результаты выполнения программы могут быть непредсказуемыми. Рассмотрим следующий пример:
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 , монитор объекта блокируется, предоставляя монопольный доступ к блоку кода только одному потоку, который произвел блокировку. После окончания работы блока кода, монитор объекта освобождается и он становится доступным для других потоков.
Примечание: для синхронизации потоков можно использовать объекты синхронизации Synchroniser"s пакета java.util.concurrent .
С использованием блокировок необходимо быть очень внимательным, чтобы не создать «взаимоблокировку», которая хорошо известна разработчикам. Этот термин означает, что один из потоков ждет от другого освобождения заблокированного им ресурса, в то время как сам также заблокировал один из ресурсов, доступа к которому ждёт второй поток. В данном процессе могут участвовать два и более потоков.
Основные условия возникновения взаимоблокировок в многопотоковом приложении:
При взаимодействии потоков часто возникает необходимость приостановки одних потоков и их последующего извещения о завершении определенных действий в других потоков. Так например, действия первого потока зависят от результата действий второго потока, и надо каким-то образом известить первый поток, что второй поток произвел/завершил определенную работу. Для подобных ситуаций используются методы:
Все эти методы вызываются только из синхронизированного контекста (синхронизированного блока или метода).
Рассмотрим пример «Производитель-Склад-Потребитель» (Producer-Store-Consumer). Пока производитель не поставит на склад продукт, потребитель не может его забрать. Допустим производитель должен поставить 5 единиц определенного товара. Соответственно потребитель должен весь товар получить. Но, при этом, одновременно на складе может находиться не более 3 единиц товара. При реализации данного примера используем методы wait() и notify() .
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 реализуют интерфейс 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 (в методе 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
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) будет вести себя.
Зачем нужно два вида реализации многопоточности; какую из них и когда использовать? Ответ несложен. Реализация интерфейса Runnable используется в случаях, когда класс уже наследует какой-либо родительский класс и не позволяет расширить класс Thread . К тому же хорошим тоном программирования в java считается реализация интерфейсов. Это связано с тем, что в java может наследоваться только один родительский класс. Таким образом, унаследовав класс Thread , невозможно наследовать какой-либо другой класс.
Расширение класса Thread целесообразно использовать в случае необходимости переопределения других методов класса помимо метода run().
Иногда разработчики используют приоритеты выполнения потока. В Java есть планировщик потоков (Thread Scheduler ), который контролирует все запущенные потоки и решает, какие потоки должны быть запущены и какая строка кода должна выполняться. Решение основывается на приоритете потока. Поэтому потоки с меньшим приоритетом получают меньше процессорного времени по сравнению с потоками с бо́льшим приоритет. Данное разумное решением может стать причиной проблем при злоупотреблении. То есть, если бо́льшую часть времени исполняются потоки с высоким приоритетом, то низкоприоритетные потоки начинают «голодать», поскольку не получают достаточно времени для того, чтобы выполнить свою работу должным образом. Поэтому рекомендуется задавать приоритет потока только тогда, когда для этого имеются веские основания.
Неочевидный пример «голодания» потока даёт метод finalize() , предоставляющий возможность выполнить код перед тем, как объект будет удалён сборщиком мусора. Однако приоритет финализирующего потока невысокий. Следовательно, возникают предпосылки для потокового голодания, когда методы finalize() объекта тратят слишком много времени (большие задержки) по сравнению с остальным кодом.
Другая проблема со временем исполнения может возникнуть от того, что не был определен порядок прохождения потоком блока synchronized . Когда несколько параллельных потоков должны выполнить некоторый код, оформленный блоком synchronized , может получиться так, что одним потокам придётся ждать дольше других, прежде чем войти в блок. Теоретически они могут вообще туда не попасть.
Рассмотренные на странице примеры многопоточности и синхронизации потоков в виде проекта Eclipse можно скачать (14Кб).
Немного о принципе многопоточности
Многопоточность — это принцип построения программы, при котором несколько блоков могут выполняться одновременно и не мешать друг другу. Предположим, нам необходимо, чтобы какие-то операции выполнялись одновременно.
Например, у нас существует два объекта, выполняющие конкретные операции независимо друг от друга. Но они используют один и тот же метод, который добавляет к статической переменной число, передаваемое методу в параметре. Однако если эти операции будут выполняться одновременно, то непонятно будет, что запишется в этой переменной, ведь мы одновременно прибавляем к исходному значению переменной два разных числа.
Возникнет странная ситуация. Для того чтобы такого не происходило, для этого метода необходимо установить так называемый признак блокировки. Если какой-либо объект начинает выполнять метод, то происходит блокировка этого метода, и никто больше не может его выполнить, пока данный объект не закончит работу с ним. Все другие объекты, которые должны выполнить этот метод, будут находиться в состоянии ожидания.
Создание потоков на основе класса Thread
Чтобы создать свой поток, необходимо создать дочерний класс класса Thread. Давайте создадим такой класс и затем разберем весь текст программы (листинг 5.7).
Листинг 5.7.
Создание потока на основе класса Thread
Public class MyThread extends Thread{ private int seconds; public MyThread(int seconds) { this.seconds = seconds; } public void run() { try { for (int i = 0; i < this.seconds; i++) { Thread.sleep(1000); System.out.println("Идет секунда: " + i); } } catch (InterruptedException e) { System.out.println("У нас проблемы с потоком"); } } public static void main(String args) { MyThread myThread = new MyThread(5); myThread.start(); new MyThread(15); } }
Вначале мы создаем дочерний класс для класса Thread, затем — конструктор с параметром, который установит количество секунд выполнения потока. После этого мы определяем метод run. который начинает действовать при запуске потока.
Чтобы его вызвать, необходимо вызвать метод start () — «стартовать поток».
У класса Thread есть метод sleep, который позволяет приостановить выполнение потока на заданное количество миллисекунд (внимание: отсчет идет в миллисекундах). Чтобы использовать данный метод, необходимо поместить его в блок try, а блок catch сделать обрабатывающим исключение InterruptedException. Затем мы запускаем два потока, которые будут выполняться одновременно. Чтобы их различить, можно переделать код так (листинг 5.8).
Листинг 5.8.
Создание двух потоков, которые будут выполняться в классе Thread одновременно
Public class MyThread extends Thread{ private int seconds; private static int numbers = 0; private int number; public static int setNumber() { return ++numbers; } public MyThread(int seconds) { this.seconds = seconds; number = MyThread.setNumber(); } public void run() { try { for (int i = 0; i < this.seconds; i++) { Thread.sleep(1000); System.out.println("Идет секунда: " + i + " выполнения потока под номером " + this.number); } } catch (InterruptedException e) { System.out.println("У нас проблемы с потоком"); } } public static void main(String args) { MyThread myThread = new MyThread(5); myThread.start(); new MyThread(15).start(); } }
Теперь мы каждому потоку присвоили свой уникальный номер.
Использование интерфейса Runnable
Можно создавать классы потоков, реализуя интерфейс Runnable. Это бывает порой значительно удобнее. Например, код из предыдущего примера можно переписать так (листинг 5.9).
Листинг 5.9.
Создание классов потоков с использованием интерфейса Runnable
В некоторых случаях так представляется удобней: обычно у подобного создания потоков более короткая запись.
И в том и в другом случае потоку можно присвоить имя. Обычно оно задается в конструкторе в качестве последнего параметра.
Чтобы ускорить процесс, претендентов на должность можно разделить на две группы и распределить их между двумя HR-менеджерами (если таковые есть в компании). В результате мы получаем ускорение процесса за счёт параллельной (parallel ) работы по оформлению.
Если же кадровик в компании один, то придётся как-то выкручиваться. Например, можно снова- таки разбить всех на две группы, например, собеседовать поочерёдно девушек и юношей.
Или по другому принципу: так как в нижней группе больше народа, будем чередовать на одного юношу двух девушек.
Такой способ организации работы называется многопоточным . Наш утомлённый кадровик переключается на разные группы для оформления из них очередного сотрудника. Групп, может быть, одиннадцать, а кадровиков – четыре. В этом случае многопоточная (multithreading ) обработка будет происходить параллельно несколькими HR-ами, которые могут брать очередного человека из любой группы для обработки его документов.
В этом руководстве мы рассмотрим, как выполняется многопоточность Java , более подробно узнаем о потоках и синхронизации между ними.
Пример одного потока :
package demotest; public class GuruThread1 implements Runnable { /** * @param args */ public static void main(String args) { Thread guruThread1 = new Thread("Guru1"); Thread guruThread2 = new Thread("Guru2"); guruThread1.start(); guruThread2.start(); System.out.println("Thread names are following:"); System.out.println(guruThread1.getName()); System.out.println(guruThread2.getName()); } @Override public void run() { } }
Преимущества одного потока :
Многопоточность в Java - это выполнение двух или более потоков одновременно для максимального использования центрального процесса.
Многопоточные приложения - это приложения, где параллельно выполняются два или более потоков. Данное понятие известно в Java как многопотоковое выполнение. При этом несколько процессов используют общие ресурсы, такие как центральный процессор, память и т. д.
Все потоки выполняются параллельно друг другу. Для каждого отдельного потока не выделяется память, что приводит к ее экономии. Кроме этого переключение между потоками занимает меньше времени.
Пример многопоточности :
package demotest; public class GuruMultithread implements Runnable{ /** * @param args */ public static void main(String args) { Thread guruthread1 = new Thread(); guruThread1.start(); Thread guruthread2 = new Thread(); guruThread2.start(); } @Override public void run() { // TODO Автоматически сгенерированный метод stub } }
Преимущества многопоточности :
Жизненный цикл потока :
Часто используемые методы для управления многопоточностью Java :
Например : В этом примере создается поток, и применяются перечисленные выше методы.
package demotest; public class thread_example1 implements Runnable { @Override public void run() { } public static void main(String args) { Thread guruthread1 = new Thread(); guruThread1.start(); try { guruthread1.sleep(1000); } catch (InterruptedException e) { // TODO Автоматически сгенерированный блок catch e.printStackTrace(); } guruthread1.setPriority(1); int gurupriority = guruthread1.getPriority(); System.out.println(gurupriority); System.out.println("Thread Running"); } }
Строка кода 2
: создаем класс «thread_Example1
Runnable
» (готовый к выполнению
). Он должен быть реализован любым классом, экземпляры которого предназначены для выполнения потоком.
Строка 4
: переопределяется метод run
для готового к запуску интерфейса, так как он является обязательным при переопределении этого метода.
Строка кода 6
: определяется основной метод, в котором начнется выполнение потока.
Строка кода 7
: создается новое имя потока «guruthread1
«, инициализируя новый класс потока.
Код строка 8
: используется метод «Start
» в экземпляре «guruthread1
«. Здесь поток начнет выполняться.
Строка 10
: используется метод «sleep
» в экземпляре «guruthread1
«. Поток приостановит свое выполнение на 1000 миллисекунд.
Строки 9-14
: применяется метод «sleep
» в блоке «try catch
», так как есть проверяемое исключение «Interrupted exception
».
Строка кода 15
: для потока назначается приоритет «1
», независимо от того, каким приоритет был до этого.
Строка кода 16
: получаем приоритет потока с помощью getPriority()
.
Строка кода 17
: значение, извлеченное из getPriority
.
Строка кода 18
: пишем текст, что поток выполняется.
5 - это приоритет потока, а «Thread Running » - текст, который является выводом нашего кода.
В многопоточности Java присутствует асинхронное поведение. Если один поток записывает некоторые данные, а другой в это время их считывает, в приложении может возникнуть ошибка. Поэтому при необходимости доступа к общим ресурсам двум и более потоками используется синхронизация.
В Java есть свои методы для обеспечения синхронизации. Как только поток достигает синхронизированного блока, другой поток не может вызвать этот метод для того же объекта. Все другие потоки должны ожидать, пока текущий не выйдет из синхронизированного блока.
Таким образом, решается проблема в многопоточных приложениях. Один поток ожидает, пока другой не закончит свое выполнение, и только тогда другим потокам будет разрешено их выполнение.
Это можно написать следующим образом:
Synchronized(object) { //Блок команд для синхронизации }
В этом Java многопоточности примере мы задействуем два потока и извлекаем имена потоков.
GuruThread1.java package demotest; public class GuruThread1 implements Runnable{ /** * @param args */ public static void main(String args) { Thread guruThread1 = new Thread("Guru1"); Thread guruThread2 = new Thread("Guru2"); guruThread1.start(); guruThread2.start(); System.out.println("Thread names are following:"); System.out.println(guruThread1.getName()); System.out.println(guruThread2.getName()); } @Override public void run() { } }
Строка кода 3
: задействуем класс «GuruThread1
«, который реализует интерфейс «Runnable
» (он должен быть реализован любым классом, экземпляры которого предназначены для выполнения потоком
).
Строка 8
: основной метод класса.
Строка 9
: создаем класс Thread
, экземпляр с именем «guruThread1
» и поток.
Строка 10
: создаем класс Thread
, экземпляр с именем «guruThread2
» и поток.
Строка 11
: запускаем поток guruThread1
.
Строка 12
: запускаем поток guruThread2
.
Строка 13
: выводим текст «Thread names are following:
«.
Строка 14
: получаем имя потока 1, используя метод getName()
класса thread
.
Строка кода 15
: получаем имя потока 2, используя метод getName()
класса thread
.
Имена потоков выводятся как:
Из этого Java многопоточности урока мы узнаем о переопределяющих методах Run () и методе Start () интерфейса runnable . Создадим два потока этого класса и выполним их.
Также мы задействуем два класса:
package demotest; public class GuruThread2 { public static void main(String args) { // TODO Автоматически сгенерированный метод stub GuruThread3 threadguru1 = new GuruThread3("guru1"); threadguru1.start(); GuruThread3 threadguru2 = new GuruThread3("guru2"); threadguru2.start(); } } class GuruThread3 implements Runnable { Thread guruthread; private String guruname; GuruThread3(String name) { guruname = name; } @Override public void run() { System.out.println("Thread running" + guruname); for (int i = 0; i < 4; i++) { System.out.println(i); System.out.println(guruname); try { Thread.sleep(1000); } catch (InterruptedException e) { System.out.println("Thread has been interrupted"); } } } public void start() { System.out.println("Thread started"); if (guruthread == null) { guruthread = new Thread(this, guruname); guruthread.start(); } } }
Строка кода 2
: принимаем класс «GuruThread2
«, содержащий метод main
.
Строка 4
: принимаем основной метод класса.
Строки 6-7
: создаем экземпляр класса GuruThread3
(создается в строках внизу
) как «threadguru1
» и запускаем поток.
Строки 8-9
: создаем еще один экземпляр класса GuruThread3
(создается в строках внизу
) как «threadguru2
» и запускаем поток.
Строка 11
: для многопоточности Java
создаем класс «GuruThread3
«, который реализует интерфейс «Runnable
». Он должен быть реализован любым классом, экземпляры которого предназначены для выполнения потоком.
Строки 13-14
: принимаем две переменные класса, из которых одна - потоковый класс, другая - строковый класс.
Строки 15-18
: переопределение конструктора GuruThread3
, который принимает один аргумент как тип String
(являющийся именем потока
). Имя будет присвоено переменной класса guruname
и сохраняется имя потока.
Строка 20
: переопределяется метод run()
интерфейса runnable
.
Строка 21
: выводится имя потока с использованием набора команд println
.
Строки 22-31
: используется цикл «for
» со счетчиком, инициализированным на «0
», который не должен быть меньше 4
. Выводится имя потока, а также выполняется приостановка потока на 1000 миллисекунд в блоке try-catch
, поскольку метод sleep
вызвал проверяемое исключение.
Строка 33
: переопределяется метод start
интерфейса runnable
.
Строка 35
: выводится текст «Thread started
«.
Строки 36-40
: проверяем, содержит ли переменная класса guruthread
значение. Если оно равно NULL
, создается экземпляр класса thread
. После этого запускается поток с использованием класса start()
.
6 ответов
Существует ровно один способ создать новый поток в Java и создать экземпляр java.lang.Thread (чтобы запустить этот поток, вам также нужно позвонить start()).
Все остальное, что создает потоки в Java-коде, возвращается к этому одному пути за крышкой (например, реализация ThreadFactory будет создавать экземпляры объектов Thread в какой-то момент,...).
Существует два разных способа указать, какой код следует запускать в этом потоке:
Первый подход (реализация Runnable) обычно считается более правильным, потому что вы обычно не создаете новый "вид" Thread, но просто хотите запустить некоторый код (т.е. a Runnable) в выделенный поток.
Темы могут быть созданы главным образом тремя различными способами.
Использовать интерфейс Callable с инфраструктурой Executor для объединения потоков.
Интерфейс Runnable или Callable предпочтительнее расширения класса Thread
Есть только два способа создания потока, о котором вы уже упоминали, но третий способ - вызвать Thread.
В java1.5 есть еще один способ вызвать поток. То есть "Исполнительная служба". Все эти классы взяты из пакета java.util.concurrent. Существуют различные способы создания "ExecutorService" с использованием класса "Исполнители" factory. Ниже приведен один из способов создания "ExecutorService" .
ExecutorService es = Executors.newSingleThreadExecutor();
RunnableImpl r = new RunnableImpl();
Будущее fu = es.submit(r);
Используя методы "ExecutorService" , мы можем отправить истребителю Runnable или Callable в службу для выполнения.
Как бы то ни было, это невозможно назвать новым способом создания Thread. Это связано с тем, что ExecutorService внутренне использует класс ThreadFactory для создания нового потока, который внутренне использует истребитель первого или второго метода. Поэтому мы должны сказать, что есть только два способа создания потоков, но есть новый способ в java1.5 для вызова потока, но не для создания потока.