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

Уровень 26

1. Comparator, сортировка коллекций

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

- Привет, Билаабо!

- Сегодня будет небольшая, но интересная и полезная тема – сортировки коллекций.

- Сортировка? Я что-то про это слышал.

- Давным-давно каждый программист обязан был уметь писать сортировку. Умел и писал. Но те времена канули в лету. Сегодня написание своей сортировки считается дурным тоном, как и написание всего, что уже было придумано.

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

- Ок.

- У вспомогательного класса Collections есть статический метод sort, который используется для сортировки коллекций, а если точнее – списков. Элементы в коллекциях Map и Set не имеют порядка/номера, значит, и сортировать там нечего.

- Да, я вспомнил, я когда-то уже использовал этот метод для сортировки списка чисел.

- Отлично. Но этот метод гораздо мощнее чем, кажется на первый взгляд. Он может сортировать не только числа, но и любые объекты, по любым критериям. И помогают ему в этом два интерфейса: Comparable и Comparator.

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

Давай я сначала покажу тебе пример, и все станет понятнее:

Пример
public class Woman implements Comparable<Woman>
{
 public int age;

 public Woman(int age) {
  this.age = age;
 }

 public int compareTo(Woman o)
 {
  return this.age - o.age;
 }
}
Пример использования:
public static void main(String[] args )
{
 ArrayList<Woman> women = new ArrayList<Woman>();
 women.add(new Woman(18));
 women.add(new Woman(21));
 women.add(new Woman(5));

 Collections.sort(women);
}

Чтобы объекты можно было сортировать, сначала нужно научиться их сравнивать. Для этого и используется Comparable. Интерфейс Comparable является generic’ом – т.е. типом с параметром. У него всего один generic-метод – compare(To). В этом методе и происходит сравнение переданного объекта и текущего (this). Т.е. надо переопределить этот метод в своем классе и сравнить в нем текущий объект (this) с переданным.

- А как работает compare? Я думал, что он будет возвращать true/false в зависимости от того – больше переданный объект или меньше.

- Тут все немного хитрее. Метод compare возвращает не true/false, а значение типа int. На самом деле так сделано для простоты.

Когда компьютеру нужно определить больше ли одно число, чем другое, он просто вычитает из первого числа второе, а потом смотрит, что получилось. Если 0 – числа равны, если получилось число меньше нуля, то второе число больше, а если результат больше нуля, то больше уже первое число.

Тут используется та же логика. Согласно спецификации метод compare должен вернуть ноль, если сравниваемые объекты равны. Если метод compare вернул число больше нуля, это значит, что наш (this) объект больше, чем переданный. Если метод compare вернул число меньше нуля, то объект this меньше чем переданный.

- Немного странно.

- Да, но если ты сравниваешь объекты просто по какому-то параметру-числу, то можешь просто вернуть разницу между ними – вычесть один из другого. Как это и сделано в примере выше.

 
public int compareTo(Woman o)
{
 return this.age - o.age;
}

- Вроде все понятно. Хотя может и не все. Но почти все.

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

Объект Woman содержит у тебя не только возраст, а еще целую кучу данных: имя, фамилию, рост, вес, количество детей, …

В таблице пользователей есть много колонок, и тут встает вопрос: а как сортировать пользователей по разным критериям? По весу, по возрасту, по фамилии?

- Гм. Действительно, часто вижу таблицы с сортировкой колонок. И как это сделать?

- А для этого есть второй интерфейс, о котором я хотел тебе сегодня рассказать – это интерфейс Comparator. И у него тоже есть метод compare, только он принимает не один параметр, а два. Вот как это работает:

Пример
public class Woman
{
 public int age;
 public int childrenCount;
 public int weight;
 public int height;
 public String name;

 public Woman(int age) {
  this.age = age;
 }
}
Пример использования:
public stati void main(String[] args )
{
 ArrayList<Woman> women = new ArrayList<Woman>();
 women.add(new Woman(18));
 women.add(new Woman(21));
 women.add(new Woman(5));

 Comparator<Woman> compareByHeight = new Comparator<Woman>() {
  public int compare(Woman o1, Woman o2) {

   return o1.height - o2.height;
  }
 };


 Collections.sort(women, compareByHeight);
}

При использовании интерфейса Comparator, логика сравнения пары объектов не прячется внутрь класса/объекта, а реализуется в отдельном классе.

- Т.е. я могу сделать несколько классов, реализующих интерфейс Comparator, но в каждом из них сравнивать разные параметры? В одном – weight, в другом – age, в третьем – height?

- Да, это очень просто и удобно.

Мы просто вызываем метод Collections.sort, передаем туда список объектов и еще специальный объект во втором параметре, который реализует интерфейс Comparator и говорит, как правильно сравнивать пары объектов в процессе сортировки.

- Гм. Вроде все понятно. Дай-ка я сам попробую. Допустим, мне нужно отсортировать пользователей по весу, это будет так:

Пример кода, пользователи сортируются по росту:
Comparator<Woman> compareByWeight = new Comparator<Woman>() {
 public int compare(Woman o1, Woman o2) {
  return o1.weight - o2.weight;
 }
};

Collections.sort(women, compareByWeight);

- Да, именно так.

- Отлично. А если я хочу отсортировать в обратном порядке?

- А подумать? Ответ очень простой!

- Придумал! Вот так:

Сортировка по возрастанию:
return o1.weight - o2.weight;
Сортировка по убыванию:
return o2.weight – o1.weight;

- Правильно. Молодец.

- А если я хочу сортировать по фамилии? Как сортировать строки, Билаабо?

- А у строк уже реализован метод compare, надо просто вызвать его:

Пример кода, пользователи сортируются по имени:
Comparator<Woman> compareByName = new Comparator<Woman>() {
 public int compare(Woman o1, Woman o2) {
  return o1.name.compareTo(o2.name);
 }
};

Collections.sort(women, compareByName);

- Это был отличный урок, Билаабо, спасибо тебе большое.

- И тебе спасибо, друг!

2. Задачи на сортировку и comparator

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

Задачи
1. Почитать в инете про медиану выборки

Реализовать логику метода sort, который должен сортировать данные в массиве по удаленности от его медианы
Вернуть отсортированный массив от минимального расстояния до максимального
Если удаленность одинаковая у нескольких чисел, то выводить их в порядке возрастания
2. Был бы ум - будет и успех

Солдаты всегда строились, строятся и будут строиться по убыванию роста.
Отсортируйте солдат, предварительно подумав, что лучше использовать при таких условиях - Comparable или Comparator.
3. Убежденному убеждать других не трудно

В таблице есть колонки, по которым можно сортировать.
Пользователь имеет возможность настроить под себя список колонок, которые будут сортироваться.
Напишите public static компаратор CustomizedComparator, который будет:
1. в конструкторе принимать список компараторов
2. сортировать данные в порядке, соответствующем последовательности компараторов.
Все переданные компараторы сортируют дженерик тип Т
В конструктор передается как минимум один компаратор

3. Разделяемые ресурсы, Конфликты, Проблема совместного доступа

- Привет, Амиго! Хочу тебе рассказать о совместном использовании ресурсов. Разными нитями, ясное дело.

Я все время говорю о проблемах при работе нескольких нитей и о том, как их решать. Это не значит, что использование нитей – это плохо. Нити – это очень мощный инструмент. Фактически, они позволяют увеличить скорость, и даже надежность работы твоей программы. Чем сложнее программа – тем больше в ней нитей и различных самостоятельных частей.

Разбиение программы на независимые (слабосвязанные) части очень выгодно.

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

Если же тебе нужно нарастить мощность программы, ты просто покупаешь двухпроцессорный сервер и пару крутых процессоров для него. В результате у него суммарно может быть до 32- ядер, и производительность твоей программы может вырасти в 2-20 раз. В зависимости от того, насколько действительно независимых частей она разбита.

Это одна из причин доминирования Java в секторе Enterprise-разработки. Если у компании есть сложная программа для внутренних нужд, которую пишут 20 разработчиков, то купить еще один сервер гораздо дешевле, чем ускорить программу в 2 раза.

- Так вот оказывается в чем дело.

- Но! Каждый раз, когда разработчик решает использовать еще одну нить, он решает одну проблему, а создает две. Слишком много нитей не увеличат производительность программы до бесконечности.

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

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

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

- Да, я помню, это делается с помощью ключевого слова synchronized.

- Да, именно.

- А если нити пишут в разные файлы?

- Формально – это разные объекты, но жесткий диск же один.

- Т.е. реально что-то распараллелить можно только внутри процессора?

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

- И что же делать? Как узнать – делать много нитей или нет?

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

- А если я не знаю?

- Тут есть два варианта:

а) поработать под началом такого специалиста

б) набить шишек самому

- Я – робот, у меня не бывает шишек – только вмятины.

- Ну, значит, набить вмятин.

- Ясно, спасибо. Ты прояснила некоторые вопросы, о которых я уже начал ломать голову.

4. Применение volatile

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

- Привет, Элли!

- Хочу рассказать тебе о модификаторе volatile. Знаешь, что это такое?

- Вроде что-то связанное с нитями. Не помню точно.

- Тогда слушай. Вот тебе немного технических деталей:

В компьютере есть два вида памяти – глобальная (обычная) и встроенная в процессор. Встроенная в процессор делится на регистры, затем кэш первого уровня (L1), кэш второго уровня (L2) и третьего уровня (L3).

Эти виды памяти отличаются по скорости работы. Самая быстрая и самая маленькая память – это регистры, затем идет кэш процессора (L1, L2, L3) и, наконец, глобальная память (самая медленная).

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

- А можно как-то управлять этим процессом?

- Практически никак – всю работу делает Java-машина, она очень интеллектуальная в плане оптимизации скорости работы.

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

- И что же делать?

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

 
public volatile int count = 0;

- О, вспомнил. Ты же уже про это рассказывала. Я же это уже знаю.

- Ага, знаешь. А вспомнил, только когда я рассказала.

- Э, ну забыл немного.

- Повторение – мать учения.

Вот тебе несколько новых фактов работы модификатора volatile. Модификатор volatile гарантирует только безопасное чтение/запись переменной, но не ее изменение.

- А в чем разница?

- Вот смотри. Как изменяется переменная:

КодЧто происходит на самом деле:Описание
count++ register = count;


register = register+1;


count = register;
Этап 1.
Значение переменной count копируется из глобальной памяти в регистровую память процессора.


Этап 2
Внутри процессора регистровая переменная увеличивается на 1.


Этап 3
Значение переменной копируется из процессора в глобальную память.

- Ого! Так что, изменение любой переменной происходит только в процессоре?

- Ага.

- И значения копируются туда-сюда: из памяти в процессор и обратно?

- Ага.

Так вот, модификатор volatile, гарантирует, что при обращении к переменной count она будет прочитана из памяти (этап 1). А если какая-то нить захочет присвоить ей новое значение, то оно обязательно окажется в глобальной памяти (этап 3).

Но Java-машина не гарантирует, что не будет переключения нитей между этапами 1 и 3.

- Т.е. увеличение переменной на 1 – это фактически три операции?

- Да.

- И если две нити одновременно захотят исполнить count++, то они могут помешать друг другу?

- Да, вот смотри:

Нить 1Нить 2Результат
register1 = count;
register1++;
count = register1;
register2 = count;
register2++;
count = register2;
register1 = count;
register2 = count;
register2++;
count = register2;

register1++;
count = register1;

- Т.е. обращаться к переменной можно, а изменять рискованно все равно?

- Ну, изменять можно, только осторожно ☺

- Это как же?

- synchronized наше все.

- Ясно.

5. Задачи по volatile

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

Задачи
1. Для того, чтобы усовершенствовать ум, надо больше размышлять, чем заучивать

Расставьте volatile там, где это необходимо.
2. Трудолюбие - душа всякого дела и залог благосостояния

Расставьте volatile там, где это необходимо.
3. Тот, кто любит труд, не нуждается в развлечениях

Расставьте volatile там, где это необходимо.

6. Мютекс и монитор

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

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

- Я готов слушать.

- Мютекс – это специальный объект для синхронизации нитей/процессов. Он может принимать два состояния – занят и свободен. Если упростить, то мютекс – это boolean-переменная, которая принимает два значения: занят(true) и свободен(false).

Когда нить хочет монопольно владеть некоторым объектом, она помечает его мютекс занятым, а когда закончила работу с ним – помечает его мютекс свободным.

- Т.е. мютекс – это как табличка на двери – занят/свободен?

- Да. И такой мютекс прикреплен к каждому объекту в Java. Прямой доступ к мютексу есть только у Java-машины. От программиста он скрыт.

- А как же тогда его использовать?

- А работать с мютексом в Java можно посредством монитора.

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

В Java монитор реализован с помощью ключевого слова synchronized.

Когда ты пишешь блок synchronized, то компилятор Java заменяет его тремя кусками кода:

1) В начале блока synchronized добавляется код, который отмечает мютекс как занятый.

2) В конце блока synchronized добавляется код, который отмечает мютекс как свободный.

3) Перед блоком synchronized добавляется код, который смотрит, если мютекс занят – то нить должна ждать его освобождения.

Вот как это примерно работает:

КодКак это работаетОписание
synchronized(object)
{

 object.doJob();

}
while (object.mutex)
 Thread.sleep(1);


object.mutex = true;

object.doJob();

object.mutex = false;
Нить спит, пока мютекс занят
(выйдем из цикла, когда мютекс освободится)


Помечаем мютекс занятым

Выполняем doJob();

Помечаем мютекс свободным

На самом деле логика там другая и сложнее, но это уже детали.

- А можно подробности?

- Пока не научишься правильно этим пользоваться – в детали лезть нет смысла.

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

- Не очень. Просто все быстро забывается…

- Чем меньше практики, тем меньше пользы и от теории.

Вот уровню к 40 научишься всем этим пользоваться, и я тебе объясню, как все устроено на самом деле. А пока - учись просто правильно всем пользоваться. Ясно?

- Да, спасибо, Элли.

7. Применение synchronized

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

- Да тут я, тут.

- Сегодня я расскажу тебе о практическом применении synchronized.

Когда в программе много объектов и нитей, то часто случаются ситуации, когда с одним объектом одновременно работает несколько нитей. Нити при этом мешают друг другу.

- Да, я это уже знаю.

- Так вот, допустим, у тебя есть объект, к которому обращаются несколько нитей. Чтобы с ним не было проблем можно сделать две вещи.

Первая – все места работы с данным объектом обернуть в блоки synchronized. Но такой подход может не сработать, если какой-то программист в своем коде будет обращаться к объекту напрямую, без блока synchronized.

Поэтому чаще всего применяется второй подход – объект делают нитебезопасным (thread-safe). Т.е. механизм synchronized встраивают в сам объект – объявляют его методы synchronized и/или оборачивают код внутри методов в блоки synchronized.

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

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

- Безопасен?

- Безопасен (thread-safe) - это безопасен при вызове из разных нитей.

Вот тебе несколько примеров. Допустим, у тебя есть объект String, к которому происходит обращение из разных нитей. Как ты уже должен был запомнить, String (и все другие примитивные типы) является immutable. А это значит, что после создания объект не изменяется. А значит, и «сломать» такой объект нельзя. Все immutable объекты являются thread-safe.

- Уже легче.

- Теперь, допустим, тебе нужна изменяемая строка.

- Ага, помню. Есть две вида таких строк – StringBuffer и StringBuilder. StringBuffer – это аналог StringBuilder, но у него все методы синхронизированы. Он тоже является thread-safe?

- Да. Если тебе нужно обращаться к объекту StringBuilder из разных нитей, то нужно заменить его на StringBuffer, иначе рано или поздно нити будут менять его одновременно и «поломают».

- А если объект, к которому происходит обращение из разных нитей, – это объект моего собственного класса? Мне тоже надо добавлять к его методам synchronized?

- Да. Лучше всего руководствоваться таким правилом – все объекты, к которым происходит обращение из разных нитей, должны быть thread-safe.

- Ясно. Не думал, что все так серьезно. Спасибо, Элли.

- Пожалуйста. Надеюсь, эти советы помогут тебе, когда Диего даст тебе пару своих легких задач ☺

8. Сложные задачи на synchronized

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

Задачи
1. Вежливость - это искусственно созданное хорошее настроение

В классе Solution создайте public static класс IntegerHolder.
IntegerHolder должен быть для типа int, быть нитебезопасным и изменяемым.
В этом классе должны быть два public метода get и set
2. Мудрый человек думает раз, прежде чем два раза сказать

Все методы класса Solution должны быть потоково-безопасными.
Сделайте так, чтобы оба метода могли выполняться одновременно двумя различными трэдами.
synchronized(this) для этого не подходит, используйте другой объект для лока.
3. Распределение элементов по корзинам с собственным локом

В синхронизированных блоках используйте нужный лок.

9. Канкаренси, BlockingQueues (java7)

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

- Привет, Ким!

- Я тебе сегодня расскажу про «канкаренси» - Concurrency.

Concurrency – это библиотека классов в Java, в которой собрали специальные классы, оптимизированные для работы из нескольких нитей. Эта тема очень интересная и обширная. Но сегодня мы просто познакомимся с ней. Эти классы собраны в пакете java.util.concurrent. Я расскажу про пару интересных классов.

Атомарные типы.

Ты уже знаешь, что даже операция count++ не является потокобезопасной (thread-safe). При увеличении переменной на 1 реально происходит три операции, в результате чего может возникнуть конфликт при одновременном изменении этой переменной.

- Ага, Элли рассказывала это немного раньше:

Нить 1Нить 2Результат
register1 = count;
register1++;
count = register1;
register2 = count;
register2++;
count = register2;
register1 = count;
register2 = count;
register2++;
count = register2;

register1++;
count = register1;

- Именно. Тогда в Java были добавлены типы данных, которые выполняют такие операции неразрывно – атомарно. (Атом - неделимый).

Так в Java появились типы AtomicInteger, AtomicBoolean, AtomicDouble и т.д.

Вот, допустим нам нужно сделать класс «счетчик»:

Пример
class Counter
{
 private int c = 0;

 public void increment()
 {
  c++;
 }

 public void decrement()
 {
  c--;
 }

 public int value()
 {
  return c;
 }
}

Как бы ты сделал его объекты thread-safe?

- Да, сделал бы все методы synchronized и все:

Пример
class synchronized Counter
{
 private int c = 0;

 public synchronized void increment()
 {
  c++;
 }

 public synchronized void decrement()
 {
  c--;
 }

 public synchronized int value()
 {
  return c;
 }
}

- Отличная работа. А вот, как бы он выглядел с использованием атомарных типов:

Пример
class AtomicCounter
{
 private AtomicInteger c = new AtomicInteger(0);

 public void increment()
 {
  c.incrementAndGet();
 }

 public void decrement()
 {
  c.decrementAndGet();
 }

 public int value()
 {
  return c.get();
 }
}

И твой и мой классы работают одинаково, но класс с AtomicInteger работает быстрее.

- Т.е. разница небольшая?

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

- Согласен, в это есть смысл.

- Кстати, а ты заметил, что атомарные типы – не immutable? AtomicInteger, в отличии от просто Integer, содержит методы по изменению своего внутреннего состояния.

- Понятно, прямо как String и StringBuffer.

- Да, что-то типа того.

Нитебезопасные (иногда называют - потокобезопасные) коллекции.

В качестве такой коллекции я хотела бы привести ConcurrentHashMap. Как сделать HashMap thread-safe?

- Сделать все его методы synchronized?

- Да, но представь теперь, что у тебя есть один такой SynchronizedHashMap, а к нему обращаются десятки нитей. И сто раз в секунду в этот map добавляется новая запись, при этом весь объект блокируется для чтения и записи.

- Да, но это же стандартный подход. Что тут можно сделать?

- Разработчики Java придумали несколько крутых штук.

Во-первых, они хранят данные в ConcurrentHashMap не одним куском, а разбивая их на порции - «корзины». И когда кто-то меняет данные в ConcurrentHashMap, то блокируется не весь объект, а только одна корзина, к которой происходит доступ. Т.е. на самом деле объект могут одновременно менять много нитей.

Во-вторых, помнишь проблему, что нельзя одновременно идти по элементам списка/мэпа и менять его? Такой код кинет исключение:

Нельзя одновременно идти в цикле по элементам коллекции и менять ее
HashMap<String, Integer> map = new HashMap<String, Integer>();

for (String key: map.keySet())
{
 if (map.get(key)==0)
  map.remove(key);
}

А в ConcurrentHashMap это можно:

Нельзя одновременно идти в цикле по элементам коллекции и менять ее
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<String, Integer>();

for (String key: map.keySet())
{
 if (map.get(key)==0)
  map.remove(key);
}

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

- Ясно. Спасибо, Ким. Действительно, очень интересные классы. Надеюсь, когда-нибудь я тоже буду виртуозно ими владеть.

10. Задачи на канкаренси

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

Задачи
1. Весь мир играет комедию

Почитать про java.util.concurrent.locks.Lock на http://docs.oracle.com/ (там все есть в джавадоках!)
Написать реализацию метода someMethod:
1. попытаться захватить лок
1.1. если лок занят, то вызвать метод ifLockIsBusy
1.2. если лок свободен, то:
1.2.1 вызвать метод ifLockIsFree
1.2.2 отпустить лок при любых условиях, даже если ifLockIsFree будет кидать исключение
2. Мир скучен для скучных людей

Разберитесь с BlockingQueue
По образу и подобию класса Producer создайте класс Consumer, который будет выводить данные из BlockingQueue в консоль
3. Мир не меняется, меняемся мы

Разберитесь с ConcurrentHashMap
В отдельном файле создайте класс Producer, который будет:
1. каждые полсекунды выводить на консоль, с новой строки начиная с 1 фразу [Some text for i]

пример: "Some text for 1"

2. при возникновении исключения выводить в консоль [[TREAD_NAME] thread was terminated]

пример: "[thread-1] thread was terminated"

11. Учимся гуглить. (Как отсортировать по строке)

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

Продолжаем наши уроки – учимся гуглить.

Вот тебе несколько заданий:

 Задания на поиск в интернете:
1 Какие методы есть у класса Collections?
2 Какие методы есть у класса Arrays?
3 Как называется сортировка, которая используется при вызове Collections.sort()?
4 Что такое mutex?
5 Что такое монитор?
6 Какие есть атомарные типы?
7 Какие классы есть в канкаренси?
8 Что такое "happens-before"?
9 Что такое «барьер» в канкаренси?
10 Нагуглить 5 различных классов из канкаренси.

12. Профессор дает доп. материал

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

Вот тебе дополнительный материал по теме.

Ссылка на дополнительный материал

13. Хулио

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

- Привет , Хулио.

- Я тут нашел новую серию «Звёздных войн». Эпизод называется «С3P0 в далёкой галактике DBCP». Садись смотреть.

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

14. Вопросы к собеседованию по этой теме

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

 Вопросы к собеседованиям
1 Как пользоваться интерфейсом Comparable?
2 Как пользоваться интерфейсом Comparator?
3 Какие методы есть у класса Collections?
4 Какие методы есть у класса Arrays?
5 Как называется сортировка, которая используется при вызове Collections.sort()?
6 Что такое канкаренси?
7 Какие классы из «канкаренси» ты знаешь?
8 Как устроен класс ConcurrentHashMap?
9 Что такое класс Lock?
10 Что такое итератор?
11 Что такое mutex?
12 Что такое монитор?

15. Большая задача

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

- Здравия желаю, капитан Бобров.

- Хорошо учишся. Молодец, боец!

У тебя сегодня новая секретная миссия - осваивать финансы.

- Ура!!! Я уже придумал, на что я их потрачу!!!

- Так, не расслабляться! Осваивать финансы – это писать новую секретную
программу "Банкомат".

Отправляйся к нашему секретному агенту Intellij IDEA, там получишь все инструкции.

- Будет сделано, товарищ капитан!