Вы достигли нового уровня

Уровень 16

1. Что такое многопоточность и зачем

- Привет, Амиго! У нас новая и очень трудная тема. Сочувствую. Часто она считается одной из самых сложных не только в Java, но и в программировании вообще. Это – многонитиевость (multithreading).

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

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

Вспомни, как мы решили «проблему большой сложности» в том примере про рост курьерской компании.

- Мы разделили ее на независимые отделы и жестко задали (стандартизировали) способы их взаимодействия.

- Но что делать, когда независимым частям нужно выполнить какой-то объем работы, параллельно с другими частями?! Ответ на этот вопрос – нити(трэды) (или как их неправильно называют – потоки).

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

- Представил. Ничего сложного!

- Отлично. А теперь представь, что таких роботов у тебя несколько. И пока один занимается вводом от пользователя, второй меняет объекты в соответствии с ним. Третий выполняет код, по отображению этих объектов на экран, а четвертый несколько раз в секунду проверяет – не столкнулись ли корабли и, в случае столкновения, просчитывает его результаты.

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

Представь, что ты смог заменить менеджера – скриптом, рассылающим письма. А остальные отделы компании об этом даже не догадались. Такие примеры уже имели место в 26 веке и показали отличные результаты. Большинство менеджеров, и даже топ-менеджеров, может быть успешно заменено скриптом средней сложности. Только после вмешательства «профсоюза офисного планктона» удалось остановить массовые увольнения менеджеров. Но это так – отвлечение от темы.

- Как интересно.

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

- Порождать новых роботов?

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

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

А почему это называется «нити»?

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

У каждого такого «маленького робота» есть задание, для исполнения которого его создали. И нить – это набор команд, выполненных в процессе исполнения этого задания.

Допустим, ты летишь на звездолете, чтобы доставить груз. Тогда «доставить груз» - это твое задание, ты в процессе его исполнения. А путь, который ты пролетел – это твоя нить. Можно сказать, что каждому новому заданию, каждой еще не решенной задаче соответствует своя нить – путь, который еще предстоит пройти.

- Другими словами, есть задание и «маленький робот», который его исполняет, а нить – это всего лишь взгляд на текущее положение дел со стороны?

- Именно так.

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

2. Способы создания и запуска новых потоков

- Привет, Амиго! Элли рассказала тебе о нитях, а я расскажу тебе о том, как с ними работать. Чтобы породить новую нить нужно:

1) Создать объект класса Thread (нить)

2) Передать в него метод, который нужно выполнить

3) Вызвать у созданного объекта Thread метод start.

Пример:

КодОписание
class Printer implements Runnable
{
 public void run()
 {
  System.out.println("I’m printer");
 }
}
Класс, который реализует интерфейс Runnable.
public static void main(String[] args)
{
 Printer printer = new Printer();
 Thread childThread = new Thread(printer);
 childThread.start();

}
1 Создали объект класса Printer, который содержит метод run.
2 Создали новый объект класса Thread, передали ему в конструкторе объект printer, чей метод run() нужно будет исполнить.
3 Запустили новую нить в работу, вызовом метода start().

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

- Ага, много нитей, много методов main.

- Чтобы указать, с какого именно метода нужно начать выполнение объекту Thread, нужно как-то передать метод этому объекту. В Java это реализовано с помощью интерфейса Runnable. Этот интерфейс содержит единственный абстрактный метод – void run(). Класс Thread имеет конструктор Thread(Runnable Runnable), в который можно передать любой объект, который реализует интерфейс Runnable.

Ты должен унаследовать свой класс от интерфейса Runnable, затем переопределить метод run в своем классе. Именно с вызова этого метода начнется работа новой нити. В методе run ты можешь написать все, что хочешь.

КодОписание
class Printer implements Runnable
{
 private String name;
 public Printer(String name)
 {
  this.name = name;
 }
 public void run()
 {
  System.out.println("I’m " + this.name);
 }
}
Класс, который реализует интерфейс Runnable.
public static void main(String[] args)
{
 Printer printer1 = new Printer("Коля");
 Thread thread1 = new Thread(printer1);
 thread1.start();

 Printer printer2 = new Printer("Вася");
 Thread thread2 = new Thread(printer2);
 thread2.start();
}
Создаем две нити, каждая на основе своего объекта типа Printer.
public static void main(String[] args)
{
 Printer printer = new Printer("Наташа");

 Thread thread1 = new Thread(printer);
 thread1.start();

 Thread thread2 = new Thread(printer);
 thread2.start();

 Thread thread3 = new Thread(printer);
 thread3.start();
}
Создаем три нити, на основе одного объекта Printer.

Более того, можно совместить это все в одном классе. Класс Thread унаследован от интерфейса Runnable, и достаточно просто переопределить его метод run:

Второй способ создания новой нити 
class Printer extends Thread
{
 private String name;
 public Printer(String name)
 {
  this.name = name;
 }
 public void run()
 {
  System.out.println("I’m " + this.name);
 }
}
Унаследовались от класса Thread, который реализует интерфейс Runnable, и переопределили метод run.
public static void main(String[] args)
{
 Printer printer = new Printer("Вася");
 printer.start();

 Printer printer2 = new Printer("Коля");
 printer2.start();

}
Создаем две нити, каждая на основе своего объекта типа Printer.

- Это решение красивее.

- Да, но у него есть минусы:

1) Вам может понадобиться запустить несколько нитей на основе одного единственного объекта, как это сделано в «примере с Наташей».

2) Если вы унаследовались от класса Thread, вы не можете добавить еще один класс-родитель к своему классу.

3) Если у вашего класса есть класс-родитель, вы не можете добавить второго – Thread.

- Т.е. каждая из нитей после вызова метода start начнет выполнять метод run того объекта, который ему передали в конструкторе?

- Да. А если в конструкторе ничего не передали, то Thread просто исполняет свой внутренний метод run.

- А почему нельзя просто вызвать этот метод, например, так:

Код
public static void main(String[] args)
{
 Printer printer1 = new Printer("Коля");
 printer1.run();
}

- Когда главная нить дойдет до метода run, наш «маленький роботик», просто задет внутрь и начнет исполнять все команды, которые там есть внутри, и только после их выполнения, вернется в метод main, и продолжит работу дальше. Создания второго «маленького робота» не произойдет, и вся работа будет делаться последовательно, а не параллельно (одновременно).

- Ясно. А можно вызвать какой-нибудь другой метод, а не run?

- Нет. Все привязано к интерфейсу Runnable, а он «знает» только об одном своем методе – run.

3. Задачи на потоки

- Привет, Амиго!

Задачи
1. My first thread

Создать public static class TestThread - нить с помощью интерфейса Runnable.
TestThread должен выводить в консоль "My first thread".
2. My second thread

1. Создать public static класс TestThread унаследовавшись от класса Thread.
2. Создать статик блок внутри TestThread, который выводит в консоль "it's static block inside TestThread".
3. Метод run должен выводить в консоль "it's run method".
3. Список и нити

В методе main добавить в статический объект list 5 нитей SpecialThread - различных объектов.
4. Вывод стек-трейса

1. Создать таск - класс public static SpecialThread - на основании интерфейса Runnable
2. SpecialThread должен выводить в консоль свой стек-трейс.

Подсказка: main thread уже выводит в консоль свой стек-трейс.
5. Поговорим о музыке?

1. Измените класс Violin так, чтоб он стал таском для нити. Используйте интерфейс MusicalInstrument
2. Реализуй необходимый метод в нити Violin. Реализация должна быть следующей:
2.1. Считай время начала игры - метод startPlaying().
2.2. Подожди 1 секунду - метод sleepNSeconds(int n), где n - количество секунд.
2.3. Считай время окончания игры - метод stopPlaying().
2.4. Выведи на консоль продолжительность игры в миллисекундах. Пример "Playing 1002 ms".

4. join- ожидание завершения потока

- Привет, Амиго! Я смотрю, ты делаешь большие успехи в изучении нитей.

- Это оказалось не сложно.

- Это же отлично! Сегодня легкий урок, и тема этого урока – метод join.

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

- Да, что делать главной нити?

- Для этого есть метод join. Смысл его в следующем. Одна нить ждет, пока полностью завершится работа второй нити:

КодОписание
class Printer implements Runnable
{
 private String name;
 public Printer(String name)
 {
  this.name = name;
 }
 public void run()
 {
  System.out.println("I’m " + this.name);
 }
}
Класс, который реализует интерфейс Runnable.
public static void main(String[] args)
{
 Printer printer1 = new Printer("Коля");
 Thread thread1 = new Thread(printer1);
 thread1.start();

 thread1.join();
}
Главная нить создает дочернюю нить – объект thread1.

Затем запускает ее – вызов thread1.start();

И ждет ее завершения – thread1.join();

Одна нить может вызвать метод join у объекта второй нити. В результате первая нить (которая вызвала метод) приостанавливает свою работу до окончания работы второй нити (у объекта которой был вызван метод).

Тут стоит различать две вещи: есть, собственно, нить – отдельный процесс выполнения команд, а есть объект этой нити (объект Thread).

- И это все?

- Да.

- А зачем нужно создавать нить и сразу же ждать ее завершения?

- Сразу же может и не нужно. А вот спустя какое-то время это может и понадобится. Главная нить после запуска первой дочерней нити может раздать еще много заданий другим нитям (создав их и вызвав метод start), а потом все – работы ей больше не осталось, нужно обрабатывать результаты работы первой дочерней нити. В таких случаях, когда нужно обязательно дождаться завершения работы другой нити и нужно вызывать метод join.

- Понятно.

5. Задача на join

- Привет, Амиго!

Задачи
1. join

Подумайте, в каком месте и для какого объекта нужно вызвать метод join, чтобы результат выводился по-порядку сначала для firstThread, а потом для secondThread.
Вызовите метод join в нужном месте.

Пример вывода:
firstThread : String 1
firstThread : String 2
...
firstThread : String 19
firstThread : String 20
secondThread : String 1
...
secondThread : String 20
2. Horse Racing

Разберись, что делает программа.
Реализуй метод calculateHorsesFinished. Он должен:
1. Посчитать количество финишировавших лошадей и возвратить его. Используй метод isFinished().
2. Если лошадь еще не пришла к финишу (!isFinished()), то:
2.1. Вывести в консоль "Waiting for " + horse.getName().
2.2. Подождать, пока она завершит гонку. Подумай, какой метод нужно использовать для этого.
3. Продвижение на политических дебатах

1. Разберитесь, что делает программа.
2. Нужно сделать так, чтобы Иванов сказал больше всего речей на политических дебатах.
3. Подумай, какой метод можно вызвать у объекта ivanov, чтобы Иванов разговаривал, пока не завершится всё свободное время.
4. Справедливость

1. Разберитесь, что делает программа.
2. Нужно сделать так, чтобы все мыши ели одновременно.
3. Подумай, какой метод позволяет альфа-самцу мыши есть первым, и почему остальные мыши ждут.
4. Удали вызов этого метода.
5. Расставь вызовы методов join()

1. Разберитесь, что делает программа.
2. Расставь вызовы методов join() так, чтобы для каждой кошки выполнялось следующее:
2.1. Сначала кошка рожает котят.
2.2. Потом все котята вылазят из корзинки в произвольном порядке.
2.3. В конце кошка собирает их назад в корзинку.
2.4. Все события для одной кошки могут быть перемешаны с событями для другой кошки.
2.5. Добавить сон котят (200 мс) в investigateWorld

6. sleep-спать

- Привет, Амиго! Билаабо сегодня расскажет тебе о самом интересном методе при работе с нитями – это метод sleep. Метод sleep объявлен как статический метод класса Thread, т.е. он не привязан ни к какому объекту. Цель этого метода, чтобы программа «заснула» на некоторое время. Вот как это работает:

КодОписание
public static void main(String[] args)
{
 Thread.sleep(2000);
}
Программа запустится.

Затем замрет на 2 секунды (2 000 миллисекунд)

Затем завершится.

Единственный параметр метода sleep – это время. Время задается в тысячных долях секунды (миллисекундах). Как только нить вызывает это метод, она засыпает на указанное количество миллисекунд.

- А где это лучше всего использовать?

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

КодОписание
public static void main(String[] args)
{
 while (true)
 {
  Thread.sleep(500);
  System.out.println("Tik");
 }
}
Программа будет работать вечно – условие продолжения цикла никогда не нарушится.

Вот что программа делает в цикле:
а) поспать полсекунды
б) вывести на экран текст «Tik»

Т.е. дважды в секунду будет выполняться некоторое действие.

- Ух ты, теперь это уже интересно.

- Рад, что тебе понравилось, мой друг Амиго!

- А если я хочу, чтобы какое-то действие выполнялась 100 раз в секунду, что нужно делать?

- Если действие должно выполняться 100 раз в секунду, а в секунде 1000 миллисекунд, значит, действие должно выполняться один раз в 10 миллисекунд.

Если твое действие занимает 2 миллисекунды, то нужно добавить к нему паузу длинной 8 миллисекунд. Вместе они будут выполняться как раз за 10 миллисекунд. И за секунду – как раз 100 раз.

Если же твое действие выполняется почти мгновенно, то поставь паузу (sleep) длиной 10 миллисекунд. Тогда в секунду будет тоже около 100 раз.

- Спасибо, Билаабо.

7. Задачи на sleep

- Привет, Амиго!

Задачи
1. Часы

1. Разберись, что делает программа.
2. Реализуйте логику метода printTikTak:
2.1. Через первые полсекунды должна выводиться в консоль фраза: Tik.
2.2. Через вторые полсекунды должна выводиться в консоль фраза: Tak.
2. Stopwatch (Секундомер)

1. Разберись, что делает программа.
2. Реализуй логику метода doSeveralSteps так, чтобы учитывалась скорость бегуна.
2.1. Метод getSpeed() в классе Runner показывает, сколько шагов в секунду делает бегун.
Нужно, чтобы бегун действительно делал заданное количество шагов в секунду.
Если Иванов делает 4 шага в секунду, то за 2 секунды он сделает 8 шагов.
Если Петров делает 2 шага в секунду, то за 2 секунды он сделает 4 шага.
2.2. Метод sleep в классе Thread принимает параметр типа long.
3. Big Ben clock

1. Разберись, что делает программа.
2. Реализуй логику метода printTime так, чтобы каждую секунду выдавалось время начиная с установленного в конструкторе
Пример:
В г. Лондон сейчас 23:59:58!
В г. Лондон сейчас 23:59:59!
В г. Лондон сейчас полночь!
В г. Лондон сейчас 0:0:1!
4. Обратный отсчет

1. Разберись, что делает программа.
2. Реализуй логику метода printCountdown так, чтобы каждые полсекунды выводился объект
из переменной list в обратном порядке - от переданного индекса до нуля.
Пример: Передан индекс 3

Пример вывода в консоль:
Строка 2
Строка 1
Строка 0
5. Аэропорт

1. Разберись, что делает программа.
2. Исправь метод takingOff(взлет) - сейчас он работает оооочень долго. Взлет должен занимать 100 миллисекунд.
3. Реализуй метод waiting по аналогии с методом takingOff. Время ожидания не должно превышать время взлета.

8. Наша версия остановки потока

- Привет, Амиго! Вот интересный вопрос, с которым ты уже столкнулся или столкнёшься в ближайшее время. А как остановить запущенную нить?

Допустим, пользователь отдал программе команду «загрузить файл из интернета». Главная нить создала для этого задания отдельную дочернюю нить, и передала ей объект, метод run которого содержит все необходимые действия для скачивания файла.

А тут пользователь – раз и передумал. Не хочет он качать этот файл. Как отменить задание и остановить нить?

- Да, как?

- Никак. Это и есть самый общий и самый правильный ответ. Нить остановить нельзя, она может остановиться только сама.

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

- И как это лучше всего сделать?

- Можно завести какую-нибудь переменную, например типа boolean. Если она true – нить работает. Если же она стала false – нить должна завершиться. Например, так:

КодОписание
class Clock implements Runnable
{
 public void run()
 {
  while (true)
  {
   Thread.sleep(1000);
   System.out.println("Tik");

  if (!ClockManager.isClockRun)
   return;

  }
 }
}
Класс Clock (часы) будет вечно писать в консоль раз в секунду слово «Tik»

Если переменная ClockManager.isClockRun равна false – метод run завершится.
class ClockManager
{
 public static boolean isClockRun = true;
public static void main(String[] args)
{
  Clock clock = new Clock();
  Thread clockThread = new Thread(clock);
  clockThread.start();


  Thread.sleep(10000);
  isClockRun = false;
}

}
Главная нить, запускает дочернюю нить – часы, которая должна работать вечно.

Ждет 10 секунд и подает часам сигнал на завершение.

Главная нить завершает свою работу.

Нить часов завершает свою работу.

- А если у нас несколько нитей, что тогда?

- Тогда лучше завести такую переменную для каждой нити. Удобнее всего будет добавить ее прямо в класс. Можно добавить туда переменную boolean isRun. Хотя лучше добавить переменную boolean isCancel, которая будет принимать значение true, если задание отменено.

КодОписание
class Clock implements Runnable
{
 private boolean isCancel = false;

 public void cancel()
 {
  this.isCancel = true;
 }

 public void run()
 {
  while (!isCancel)
  {
   Thread.sleep(1000);
   System.out.println("Tik");
  }
 }
}
Класс Clock (часы) будет писать в консоль раз в секунду слово «Тик», пока переменная isCancel равна false.

Когда переменная isCancel станет равной true, метод run завершится.
public static void main(String[] args)
{
 Clock clock = new Clock();
 Thread clockThread = new Thread(clock);
 clockThread.start();


 Thread.sleep(10000);
 clock.cancel();
}
Главная нить, запускает дочернюю нить – часы, которая должна работать вечно.

Ждет 10 секунд и отменяет задание, вызовом метода cancel.

Главная нить завершает свою работу.

Нить часов завершает свою работу.

- Буду знать, спасибо, Элли.

9. interrupt, IsInterrupted, interrupted exception

- Привет, Амиго! Согласись, Элли хорошо придумала с этим Cancel?

- Ага.

- На самом деле нечто подобное существует в классе Thread. Только переменная называется не isCancel, а isInterrupt, и метод остановки, соответственно, не cancel(), а interrupt().

- Да?

- Ага. Вот смотри:

КодОписание
class Clock implements Runnable
{
 public void run()
 {
  Thread current = Thread.currentThread();

  while (!current.isInterrupted())
  {
   Thread.sleep(1000);
   System.out.println("Tik");
  }
 }
}
Т.к. много нитей могут вызвать метод run одного объекта, то объект Clock в своем методе run получает объект вызвавшей его нити («текущей нити»).

Класс Clock (часы) будет писать в консоль раз в секунду слово «Tik», пока переменная isInterrupt текущей нити равна false.

Когда переменная isInterrupt станет равной true, метод run завершится.
public static void main(String[] args)
{
 Clock clock = new Clock();
 Thread clockThread = new Thread(clock);
 clockThread.start();


 Thread.sleep(10000);
 clockThread.interrupt();
}
Главная нить, запускает дочернюю нить – часы, которая должна работать вечно.

Ждет 10 секунд и отменяет задание, вызовом метода interrupt.

Главная нить завершает свою работу.

Нить часов завершает свою работу.

Более того, в методе sleep, который так любят использовать для организации вечного цикла в методе run, есть автоматическая проверка переменной isInterrupt. Если нить вызовет метод sleep, то этот метод сначала проверит, а не установлена ли для текущей (вызвавшей его нити) переменная isInterrupt в true. И если установлена, то метод не будет спать, а выкинет исключение InterruptedException.

- А зачем выкидывать исключение? Не лучше ли тоже просто в цикле вместо isCancel подставить isInterrupted()?

- Во-первых, не всегда в методе run есть цикл. Метод может состоять просто из двух десятков вызовов других методов. Тогда перед вызовом каждого придется добавлять проверку isInterrupted.

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

В-третьих, выкидывание исключения – это не замена проверке isInterrupted, а скорее удобное дополнение. Выкинутое исключение позволяет быстро раскрутить стек вызовов до самого run.

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

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

- Серьёзные аргументы.

- И, наконец, последнее: ты можешь в своем методе run вызывать чужой код, к которому у тебя нет доступа (исходников и/или прав их менять). Он может не иметь проверок на isInterrupted, а также перехватывать с помощью try…catch(Exception e) все возникшие исключения.

Никто не гарантирует, что нить можно остановить. Она может остановиться только сама.

10. Задача на interrupt

- Привет, Амиго!

Задачи
1. Считаем секунды

1. Напиши реализацию метода run в нити Stopwatch (секундомер).
2. Stopwatch должен посчитать количество секунд, которое прошло от создания нити до ввода строки.
3. Выведи количество секунд в консоль.
2. Отсчет на гонках

1. Разберись, что делает программа.
2. Реализуй логику метода run так, чтобы каждую секунду через пробел
выдавался отсчет начиная с countSeconds до 1, а потом слово [Марш!] (см примеры).
3. Если нить работает 3.5 секунды или более, прерви ее методом interrupt и внутри нити выведи в консоль слово [Прервано!].
Пример для countSeconds=4 : [4 3 2 1 Прервано!]
4. Если нить работает менее 3.5 секунд, она должна завершиться сама.
Пример для countSeconds=3 : [3 2 1 Марш!]
PS: метод sleep выбрасывает InterruptedException.
3. Снова interrupt

Создай нить TestThread.
В методе main создай экземпляр нити, запусти, а потом прерви ее используя метод interrupt().
4. А без interrupt слабо?

Разберись, как работает программа.
Сделай так, чтобы в методе ourInterruptMethod можно было сделать так, чтобы нить TestThread завершилась сама.
Нельзя использовать метод interrupt.
5. Один для всех, все - для одного

1. Разберись, как работает программа.
1.1. Обрати внимание, что объект Water - один для всех нитей.

2. Реализуй метод ourInterruptMethod, чтобы он прерывал все нити из threads.
3. В методе run исправь значения переменных:
3.1. isCurrentThreadInterrupted - должна равняться значению метода isInterrupted у текущей нити.
3.2. threadName - должна равняться значению метода getName (реализовано в классе Thread) у текущей нити.

11. Ссылка на вики, потоки

- Как тебе новая тема? Правда, интересно! То-то же. Если возникли некоторые вопросы, то есть хорошая статья, которая может дать на них ответы.

Ссылка на вики, потоки

12. Хулио

- Привет, Амиго! Чувствую себя любителем сериалов из девяностых. Это так здорово. Ладно, хватит уже трепаться, давай смотреть:

Оригинал видео на YouTube

13. Домашние и бонусные задания

- Здорово, боец!

- Здравия желаю, товарищ капитан!

- У меня для тебя шикарная новость. Вот тебе задания для закрепления полученных навыков. Выполняй их каждый день, и твои навыки будут расти с неимоверной скоростью. Они специально разработаны для выполнения их в Intellij IDEA.

Дополнительные задания для выполнения в Intellij Idea
1. Thread.currentThread - всегда возвращает текущую нить

1. В методе printMsg присвой переменной t текущую нить.
2. В методе printMsg после всех действий поставь задержку в 1 миллисекунду.
2. Последовательные выполнения нитей

1. В методе run после всех действий поставь задержку в 10 миллисекунд. Выведи "Нить прервана", если нить будет прервана.
2. Сделай так, чтобы все нити выполнялись последовательно: сначала для нити №1 отсчет с COUNT до 1, потом для нити №2 с COUNT до 1 и т.д.

Пример:
#1: 4
#1: 3
...
#1: 1
#2: 4
...
3. Рекурсивное создание нитей

1. Измени класс GenerateThread так, чтобы он стал нитью.
2. Создай конструктор GenerateThread, который должен:
2.1. Вызвать конструктор суперкласса с параметром String - номером созданной нити. Используй countCreatedThreads.
2.2. Запустить текущую нить.
2.3. Номер первой нити должен начинается с 1.
3. Переопредели метод toString, для этого внутри GenerateThread нажми Alt+Insert -> Override Methods. Начни печатать toString.
3.1. Метод toString должен возвращать № текущей нити и слово " created". Используй getName(). Пример: [8 created]
4. Пока количество созданных нитей меньше Solution.count метод run должен:
4.1. создать новую нить типа GenerateThread.
4.2. Вывести в консоль созданную в пункте 4.1. нить.
5. В итоге должно быть выведено в консоль 15 строк.
4. Последовательные выполнения нитей

1. Разберись, что делает программа.
2. Сделай так, чтоб программа сначала выводила результат нити, а когда нить завершится - продолжила метод main.

Пример выходных данных:
inside MyThread 0
inside MyThread 1
...
inside MyThread 9
inside main 0
inside main 1
...
inside main 9
5. Взаимная блокировка

1. Разберись, как работает программа.
2. Не меняя классы T1 и T2 сделай так, чтобы они завершились, не обязательно успешно.
3. метод sleep не использовать.
6. Создание по образцу

Разберись, как работает программа.
По образу и подобию CountDownRunnable создай нить CountUpRunnable, которая выводит значения в нормальном порядке – от 1 до number
7. Поиграем?

Три человека играют в игру. Каждый игрок (Gamer) характеризуется двумя параметрами: фамилией (name) и количеством действий в секунду (rating).
Нужно вывести в консоль ход игры и определить победителя и проигравших.
Итак...
1. Разберись, что делает программа.
1.1. List<String> steps хранит последовательность действий, которое каждый игрок выполняет от 0 до последнего.
1.2. isWinnerFound показывает, найден победитель или нет.
1.3. метод sleep выбрасывает InterruptedException и принимает параметр типа long.
1.4. Игроки играют независимо друг от друга.

2. Реализуйте логику метода run так, чтобы для каждого игрока:
2.1. За 1 секунду через равные интервалы времени выводились в консоль действия, описанные в steps. Количество выведенных действий должно равняться rating.
2.2. Любой текст должен начинаться с фамилии игрока (метод getName()), потом следовать двоеточие, а затем сам текст. Пример: [Ivanov:Начало игры].
2.3. Когда игрок выполнит все действия из steps, то он считается победителем. Выведите [getName() + ":победитель!"].
2.4. Когда найден победитель, то игра останавливается, и остальные игроки считаются побежденными. Выведите для них [getName() + ":проиграл"].
8. Кто первый встал - того и тапки

1. Разберись, что делает программа.
1.1. Каждая нить должна читать с консоли слова. Используйте готовый static BufferedReader reader.
1.2. Используй static byte countReadStrings, чтобы посчитать, сколько слов уже считано с консоли всеми нитями.

2. Реализуйте логику метода run:
2.1. Пока нить не прервана (!isInterrupted) читайте с консоли слова и добавляйте их в поле List<String> result.
2.2. Используй countReadStrings для подсчета уже считанных с консоли слов.
9. Только по-очереди!

1. В классе Solution создать нить public static Read3Strings унаследовавшись от Thread.
2. В методе run реализовать чтение с консоли трех строк.
3. Три подряд введенных строки должны считываться в одной нити и объединяться в одну строку через пробел.
4. В методе main вывести результат для каждой нити.
5. Используйте join
Пример входных данных:
a
b
c
d
e
f

Выходные данные:
a b c
d e f
10. Последовательный вывод файлов

1. Разберись, что делает программа.
2. В статическом блоке считай 2 имени файла firstFileName и secondFileName.
3. Внутри класса Solution создай нить public static ReadFileThread, которая реализует интерфейс ReadFileInterface (Подумай, что больше подходит - Thread или Runnable).
3.1. Метод setFileName должен устанавливать имя файла, из которого будет читаться содержимое.
3.2. Метод getFileContent должен возвращать содержимое файла.
3.3. В методе run считай содержимое файла, закрой поток. Раздели пробелом строки файла.
4. Подумай, в каком месте нужно подождать окончания работы нити, чтобы обеспечить последовательный вывод файлов.
4.1. Для этого добавь вызов соответствующего метода.
Ожидаемый вывод:
[все тело первого файла]
[все тело второго файла]

- Те задания были для духов. Для дедушек я добавил бонусные задания повышенной сложности. Только для старослужащих.

1. Factory method pattern

Задача: 1. Внимательно посмотри, какие классы у тебя есть.
2. В отдельных файлах в пакете common создай классы JpgReader, PngReader, BmpReader, которые реализуют интерфейс ImageReader.
3. В отдельном файле в пакете bonus01 создай класс ImageReaderFactory с одним методом.
3.1. Подумай, как он должен называться.
3.2. Подумай, какие модификаторы должны быть у этого метода.
4. Этот метод должен:
4.1. для каждого значения из ImageTypes возвращать соответствующий Reader, например, для ImageTypes.JPG - JpgReader;
4.2. если передан неправильный параметр, то выбросить исключение IllegalArgumentException("Неизвестный тип картинки").
2. Клубок

Задача: 1. Создай 5 различных своих нитей c отличным от Thread типом:
1.1. нить 1 должна бесконечно выполняться;
1.2. нить 2 должна выводить "InterruptedException" при возникновении исключения InterruptedException;
1.3. нить 3 должна каждые полсекунды выводить "Ура";
1.4. нить 4 должна реализовать интерфейс Message, при вызове метода showWarning нить должна останавливаться;
1.5. нить 5 должна читать с консоли цифры пока не введено слово "N", а потом вывести в консоль сумму введенных цифр.
2. В статическом блоке добавь свои нити в List<Thread> threads в перечисленном порядке.
3. Нити не должны стартовать автоматически.

Подсказка: Нить 4 можно проверить методом isAlive()
3. Отдебажим все на свете

Задача: Разобраться, что делает програма.
Почитать про UncaughtExceptionHandler - это важно.
Еще раз внимательно посмотреть программу.
Разобраться - продебажить - почему наш OurUncaughtExceptionHandler не срабатывает.
Исправить ошибку, т.е. все должно работать. :)

Ожидаемый результат в произвольном порядке:
Нить 1: My exception message
Нить 2: My exception message