Управление непостоянством (volatility)

Указания по использованию volatile-переменных


Автор Брайан Гётц, 19 июня 2007 года. Оригинал: Managing Volatility

Volatile-переменные в Java можно назвать «synchronized-лайт»; для их использования нужно меньше кода, чем для synchronized-блоков, часто они выполняются быстрее, но при этом могут делать лишь часть из того, что делают synchronized. В этой статье представлено несколько паттернов эффективного использования volatile — и несколько предупреждений о том, где их использовать не надо.

У локов (locks) есть две основные черты: взаимное исключение (mutual exclusion, mutex) и видимость. Взаимное исключение означает, что лок может быть захвачен только одной нитью в отдельный момент времени, и это свойство можно использовать для реализации протоколов управления доступом к общедоступным ресурсам, так что только одна нить будет их использовать в отдельный момент времени. Видимость — вопрос более тонкий, ее задача обеспечить, что изменения, сделанные в общедоступных ресурсах до освобождения замка будут видимы следующей нити, захватившей этот замок. Если бы синхронизация не гарантировала видимость, нити могли бы получать устаревшие или неверные значения общедоступных переменных, что привело бы к целому ряду серьезных проблем.

Volatile-переменные

Volatile-переменные обладают свойствами видимости, присущими synchronized, но лишены их атомарности. Это означает, что нити автоматически будут использовать самые актуальные значения volatile-переменных. Их можно использовать для нитебезопасности (thread safety, чаще переводится как потокобезопасности), но в очень ограниченном наборе случаев: тех, что не вводят связи между несколькими переменными или между текущими и будущими значениями переменной. Таким образом, одной volatile недостаточно для реализации счетчика, мьютекса или любого класса, чьи неизменные части связаны с несколькими переменными (например, «start <=end»).

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

Условия правильного использования volatile

Заменить локи на volatile можно в ограниченном числе обстоятельств. Для нитебезопасности необходимо, чтобы выполнялись оба критерия:

  1. То, что записывается в переменную, не зависит от ее текущего значения.
  2. Переменная не участвует в инвариантах с другими переменными.

Проще говоря, эти условия означают, что корректные значения, которые могут быть записаны в volatile-переменную, не зависят от любого другого состояния программы, включая текущее состояние переменной. Первое условие исключает использование volatile-переменных как нитебезопасных счетчиков. Хотя инкремент (x++) выглядит как одна операция, в действительности это целая последовательность операций чтения-изменения-записи, которая должна выполняться атомарно, чего volatile не обеспечивает. Корректная операция требовала бы, чтобы значение x оставалось неизменным в течение всей операции, чего нельзя добиться с помощью volatile. (Однако если вам удастся обеспечить, что значение будет записываться только из одной нити, первое условие можно опустить).

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

@NotThreadSafe 
public class NumberRange {
    private int lower, upper;
 
    public int getLower() { return lower; }
    public int getUpper() { return upper; }
 
    public void setLower(int value) { 
        if (value > upper) 
            throw new IllegalArgumentException(...);
        lower = value;
    }
 
    public void setUpper(int value) { 
        if (value < lower) 
            throw new IllegalArgumentException(...);
        upper = value;
    }
}


Поскольку переменные состояния диапазона ограничены таким образом, будет недостаточным сделать поля lower и upper volatile, чтобы обеспечить нитебезопасность класса; по-прежнему будет нужна синхронизация. Иначе рано или поздно не повезет и две нити, выполнившие setLower() и setUpper() с неподходящими значениями могут привести диапазон в противоречивое состояние.

Например, если начальное значение (0, 5), нить A вызывает setLower(4), и в то же время нить B вызывает setUpper(3), эти перемежающиеся операции приведут к ошибке, хотя обе пройдут проверку, которая должна защищать инвариант. В итоге диапазон будет (4, 3) — неверные значения. Нам нужно сделать setLower() и setUpper() атомарными по отношению к другим операциям над диапазоном — и присвоение полям volatile этого не сделает.

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

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

Чрезвычайно трудно сделать точные всеобъемлющие заявления вида «X всегда быстрее, чем Y,» особенно когда речь идет о внутренних операциях виртуальной машины Java. (Например, JVM может полностью снять блокировку в некоторых ситуациях, что затрудняет абстрактное обсуждение затрат на volatile по отношению к синхронизации). Тем не менее, на большинстве современных процессорных архитектур затраты на чтение volatile мало отличаются от затрат на чтение обычных переменных. Затраты на запись volatile значительно больше, чем на запись обычных переменных, из-за ограждения памяти, необходимого для обеспечения видимости, но в целом дешевле, чем установка локов.

Паттерны для правильного использования volatile
Многие эксперты по параллелизму склонны избегать использования volatile-переменных вообще, потому что их труднее использовать правильно, чем локи. Однако существуют некоторые четко определенные паттерны, которые, если следовать им внимательно, могут безопасно использоваться в самых разных ситуациях.
Всегда учитывайте ограничения volatile — используйте только volatile, которые никак не зависят от всего остального в программе, и это должно не позволить вам залезть с этими паттернами на опасную территорию.
Паттерн №1: флаги состояния
Возможно, каноническое использование изменчивых переменных — это простые булевы флаги состояния, указывающие на то, что произошло важное одноразовое событие жизненного цикла, такое как завершение инициализации или запрос на завершение работы. Многие приложения включают конструкцию управления формы: «Пока мы не готовы выключиться, продолжаем работать», как показано в листинге 2:

volatile boolean shutdownRequested;
 
...
 
public void shutdown() { shutdownRequested = true; }
 
public void doWork() { 
    while (!shutdownRequested) { 
        // do stuff
    }
}


Вероятно, метод shutdown () будет вызываться откуда-то извне цикла — в другой нити — поэтому требуется синхронизация для обеспечения правильной видимости переменной shutdownRequested. (Он может быть вызван из слушателя JMX, слушателя действий в нити событий GUI, через RMI, через веб-службу и т. д.). Однако цикл с синхронизированными блоками будет гораздо громоздким, чем цикл с volatile-флагом состояния как в листинге 2. Поскольку volatile упрощает написание кода, а флаг состояния не зависит от какого-либо другого состояния программы, это пример хорошего использования volatile.

Для таких флагов статуса характерно то, что обычно существует только один переход состояния; флаг shutdownRequested переходит из false в true, а затем программа выключается. Этот паттерн можно расширить до флагов состояния, которые могут изменяться туда и обратно, но только если цикл перехода (от false до true to false) будет происходить без внешних вмешательств. В противном случае необходим какой-то атомарный механизм перехода, такой как атомарные переменные.

Паттерн № 2: одноразовая безопасная публикация

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

Один из способов безопасной публикации объекта состоит в том, чтобы сделать ссылку на объект volatile. В листинге 3 показан пример, где во время запуска фоновый поток загружает некие данные из базы данных. Другой код, когда может попытаться использовать эти данные, проверяет, был ли он опубликован, прежде чем пытаться его использовать.

public class BackgroundFloobleLoader {
    public volatile Flooble theFlooble;
 
    public void initInBackground() {
        // делаем много всякого
        theFlooble = new Flooble();  // единственная запись в theFlooble
    }
}
 
public class SomeOtherClass {
    public void doWork() {
        while (true) { 
            // чё-то там делаем...
            // используем theFolooble, но только если она готова
            if (floobleLoader.theFlooble != null) 
                doSomething(floobleLoader.theFlooble);
        }
    }
}


Если бы ссылка на theFlooble не была volatile, код в doWork() рисковал бы увидеть частично сконструированный Flooble при попытке обратиться по theFlooble.

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

Паттерн № 3: независимые наблюдения

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

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

public class UserManager {
    public volatile String lastUser;
 
    public boolean authenticate(String user, String password) {
        boolean valid = passwordIsValid(user, password);
        if (valid) {
            User u = new User();
            activeUsers.add(u);
            lastUser = user;
        }
        return valid;
    }
}


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

Паттерн № 4: паттерн «volatile bean»

Паттерн “volatile bean” применим во фреймворках, использующих JavaBeans как “glorified structs”. В паттерне “volatile bean” JavaBean используется как контейнер для группы независимых свойств с геттерами и/или сеттерами. Обоснованием необходимости паттерна “volatile bean” является то, что многие фреймворки предоставляют контейнеры для изменяемых держателей данных (например, HttpSession), но объекты, помещенные в эти контейнеры, должны быть нитебезопасными.

В патттерне volatile bean все элементы данных JavaBean являются volatile, а геттеры и сеттеры должны быть тривиальными — они не должны содержать никакой логики, кроме получения или установки соответствующего свойства. Кроме того, для членов данных, которые являются объектными ссылками, упомянутые объекты должны быть эффективно неизменными. (Это запрещает наличие полей-ссылок на массивы, так как когда ссылка массива объявлена volatile, только эта ссылка, а не сами элементы, имеет свойство volatile.) Как и в любой volatile-переменной, не может быть никаких инвариантов или ограничений, связанных с свойствами JavaBeans. Пример JavaBean, написанного по паттерну “volatile bean”, показан в листинге 5:

@ThreadSafe
public class Person {
    private volatile String firstName;
    private volatile String lastName;
    private volatile int age;
 
    public String getFirstName() { return firstName; }
    public String getLastName() { return lastName; }
    public int getAge() { return age; }
 
    public void setFirstName(String firstName) { 
        this.firstName = firstName;
    }
 
    public void setLastName(String lastName) { 
        this.lastName = lastName;
    }
 
    public void setAge(int age) { 
        this.age = age;
    }
}


Более сложные volatile-паттерны

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

Более продвинутые паттерны использования volatile могут быть чрезвычайно хрупкими. Крайне важно, чтобы ваши предположения были тщательно задокументированы, а эти паттерны сильно инкапсулироваными, потому что даже мельчайшие изменения могут сломать ваш код! Кроме того, учитывая, что основной причиной для более сложных вариантов использования volatile является производительность, убедитесь, что у вас действительно есть выраженная потребность в предполагаемом усилении производительности, прежде чем применять их. Эти паттерны являются компромиссами, которые жертвуют читабельностью или легкостью поддержки ради возможного повышения производительности — если вам не требуется повышение производительности (или вы не можете доказать, что вам это нужно, с помощью строгой программы измерения), то это, вероятно, плохая сделка, потому что вы отказываетесь от чего-то ценного и получаете что-то меньшее взамен.

Паттерн № 5: дешевый лок чтения-записи

Сейчас вы должны уже хорошо понимать, что volatile слишком слаба для реализации счетчика. Поскольку ++ x по факту сокращение трех операций (чтение, добавление, хранение), при неудачном стечении обстоятельств вы потеряете обновленное значение, если несколько потоков попытаются одновременно увеличить volatile-счетчик.

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

@ThreadSafe
public class CheesyCounter {
    // Employs the cheap read-write lock trick
    // All mutative operations MUST be done with the 'this' lock held
    @GuardedBy("this") private volatile int value;
 
    public int getValue() { return value; }
 
    public synchronized int increment() {
        return value++;
    }
}


Причина, по которой этот метод называется «дешевым локом чтения-записи», заключается в том, что вы используете разные механизмы синхронизации для чтения и записи. Поскольку операции записи в этом случае нарушают первое условие использования volatile, вы не можете использовать volatile для безопасной реализации счетчика — вы должны использовать блокировку. Однако вы можете использовать volatile для обеспечения видимости текущего значения при чтении, поэтому вы используете блокировку для всех операций изменения и volatile для операций read-only. Если лок позволяет только одной нити за раз получать доступ к значению, volatile-чтения допускают более одного, поэтому, когда вы используете volatile для защиты чтения, вы получаете более высокий уровень обмена, чем если бы вы использовали блокировку для всего кода: и чтения, и записи. Однако имейте в виду хрупкость этого паттерна: с двумя конкурирующими механизмами синхронизации он может стать очень сложным, если вы выйдете за пределы самого базового приложения этого паттерна.

Резюме

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

Руководство по управлению памятью Java (и спасению вашего кода)

Примечание переводчика: желание перевести заметку появилось ранним июньским утром после прочтения ее в полусонном состоянии в вагоне метро. Целевая аудитория: люди, делающие первые шаги в мире Java и по роду своего основного технического бэкграунда или желания сильно жаждут забраться под капот Java и изучить все «электродинамические» процессы. Уверен, что для прочитавших это будет отправной точкой путешествия в мир настройки JVM и GC. Попутного ветра!
Оригинал статьи лежит здесь


Будучи разработчиком, вы проводите бесчисленные часы за вычищением багов из Java-приложения и достижением производительности в тех местах где нужно. Во время тестирования замечаете, что приложение постепенно работает медленней, и в конце полностью заваливается или просто демонстрирует плохую производительность. В конечном счете признаете, что происходят утечки памяти. Garbage collector Java делает всё что может, чтобы справиться с этими утечками. Но есть только много вещей, которые можно выполнить, когда сталкиваетесь с ситуациями подобными этим. Вам нужны способы идентификации вызовов утечек памяти, выявления причин и понимания роли Java garbage collector'а влияющего на общую производительность приложения.

Задачи окружающего мира решаемые Java

Прим. автора поста: Перевод местами сделан не совсем гладко. В попытке сгладить шероховатости, были сделаны вольные дополнения, которые нисколько не портят смысл статьи. Оригинал статьи вот здесь
Целевая аудитория статьи — люди, которые колеблются пуститься в плавание в мире Java.


Редакция dzone.com побеседовала с 11 профессионалами в бизнесе, плотно работающих большую часть, если не больше, свой карьеры в экосистеме Java. Спросили их о задачах в реальном мире, которые решаются Java.
  • ,

Double brace инициализация

1. Double brace инициализация


Перевод статьи What is Double Brace Initialization in Java? Anti Pattern Example (опубликованной в октябре 2015 года).

Инициализация с использованием двойных фигурных скобок (далее по тексту — Double brace инициализация) – Java средство для создания коллекций таких как list, set и map одновременно с их объявлением.
Когда вам необходимы списки с фиксированными элементами, такие как перечень поддерживаемых продуктов или денежных единиц, то объявление списка одновременно с его инициализацией улучшает читабельность кода. Именно поэтому Double brace инициализация набирает популярность, так как иных стандартных способов создания с одновременной инициализацией в коде для коллекций нет.
К сожалению, в отличие от других языков программирования Java не поддерживает коллекции литералов. В соответствие с данным ограничением, создание неизменяемого списка (unmodifiableList) даже с небольшим количеством элементов вынуждает нас писать много строк кода в виде повторяющихся вызовов add() для добавления нужных элементов с завершающим обертыванием (wrapping):
List<Integer> list = new ArrayList<>();
list.add(2);
list.add(3);
list.add(5);
list.add(7);        
List<Integer> unmodifiableList = Collections.unmodifiableList(list);


Это излишне избыточное описание, которое можо упростить. Заполним статические списки удобным для нас способом, а именно:
непосредственно в статических блоках при инициализации, в чем нам и поможет Double brace инициализация, позволяющая нам записать все в одну строку:
List<Integer> list = Collections.unmodifiableList(new ArrayList<Integer>() {{
        add(2);
        add(3);
        add(5);
}});


Аналогично Double brace инициализация поможет нам заполнить значениями и HashMap:
Map<Integer, String> intToString = new HashMap<Integer, String>(){{
         put(1, "one");
         put(2, "two");
         put(3, "three");         
 }};


Все это так элегантно выглядит, однако имеет свои недостатки, которые и делают Double brace инициализацию анти-шаблоном. Рассмотрим их далее в следующей главе.

Плюсы и минусы Double brace инициализации.


Double brace инициализация использует создание внутреннего анонимного класса (anonymous inner class). Что поначалу внешне скрыто, однако Double brace инициализация создает класс с дальнейшей инициализацией его экземпляра, каждый раз как вы применяете ее. Помимо этого используется скрытая ссылка на этот закрытый класс, что может привести нас к возможным утечкам памяти. Вы также не сможете использовать оператор привидения для generic-ов (diamond operator < >), так как мы не можем обратиться внутрь анонимного класса.
(От переводчика: Еще раз более подробно:
после первой { создается внутренний анонимный класс,
после второй { происходит инициализация при создании экземпляра класса при которой мы имеем доступ к полям и методам внешнего (по отношению к анонимному) класса.)


Плюсы:
  1. Уменьшение строк в коде
  2. Создание и инициализация в одном выражении.
Минусы:
  1. Скрытое от вас создание анонимного класса.
  2. Что стоит нам дополнительных расходов для его экземпляра каждый раз при использовании.
  3. Каждый раз создается скрытая ссылка на него, которая возможно приведет к утечкам памяти.

Вердикт:
Из-за вышеуказанных минусов и существованию альтернатив Double brace инициализация рассматривается как анти-шаблон в мире Java.
Спасите котенка

Альтернативы Double brace инициализации в Java.


Хорошая новость состоит в том, что имеются иные способы достижения тех же самых целей в Java. Мы может реализовать в одной строке кода создание и инициализацию значениями ArrayList используя Copy constructor из Collection класса как показано ниже:
List<Integer> list = Collections.unmodifiableList(new ArrayList<>(Arrays.asList(2, 3, 5)));


Arrays.asList() возвратит нам список фиксированной длины переданный в ArrayList copy constructor. Помните про различие между списками фиксированной длины возвращаемыми из Arrays.asList() и Collections.unmodifiableList():
Вы не можете добавлять или удалять элементы ArrayList-а, однако вы можете изменить элемент по индексу с использованием set(), чего вам не удастся сделать со списком, возвращенным Collections.unmodifiableList().
Если вы хотите получить небольшой список – это лучший способ, хотя он будет менее прозрачным для Set и других коллекций, так придется создать List перед созданием Set-а. Но это все же лучше, нежели Double brace инициализация, так как в данном случае не создается лишний внутренний анонимный класс при каждом подобном использовании.

Если вы работаете под Java 8 у вас есть еще один альтернативный способ. JDK 8 Stream API поможет вам создавать небольшие коллекции комбинируя вывод Stream Factory методы в коллекцию List:
List<String> list = Collections.unmodifiableList(Stream.of("abc", "bcd", "cde").collect(toList()));


Для Set вы можете использовать Collectors.toSet() метод вместо Collectors.toList(), как показано ниже:
Set<String> set = Collections.unmodifiableSet(Stream.of("abc", "bcd", "cde").collect(toSet()));


Кстати, Stream collect методы не гарантируют, что сгенерированные ими коллекции защищены от изменений. В Java 8 коллекции, которые они вернули (такие как — ArrayList, HashSet, and HashMap ), вполне себе обычные ( мы можем менять их), но это данный факт возможно будет исправлен в будущих релизах JDK.

Это все на данный момент о Double brace инициализации в Java. Этот шаблон приемлим для тестов и демонстраций, но не достаточно хорош для использования в промышленной эксплуатации (production). Из-за присущих ему минусов, Double brace инициализация стал анти-шаблоном в наши дни, особенно учитывая доступные альтернативы. Сам я все еще использую данную конструкцию для инициализации static map-ов и все. Для List я предпочитаю создавать Collections комбинируя с созданием Array.asList в его конструкторе. А если использую Java 8 – конструкцию с использованием Stream API и collect().

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

Рекомендуемая литература:
Если вы хотите побольше узнать о шаблонах и лучших практиках вы должны прочитать книгу «Эффективное программирование» Джошуа Блоха, ни одна книга не может занять ее место.
А если вы уже поднаторели в Java и ищете книгу по шаблонам проектирования, юморной стиль изложения которой интересно и легко читать – обратите внимание на «Нead First. Шаблоны проектирования».
От переводчика: Я умышленно привел ссылку на оригинальную книгу Блоха, так как перевод ее на русский неудачен (например, Builder=конструктор).
  • ,

Объектно-ориентированное программирование (перевод статьи)

Автор: Mohit Kanwar
9 ноября, 2015. Java Zone.
Оригинал статьи

От переводчика:
К сожалению, у меня нет сколько-нибудь значительного опыта перевода с английского, хотя я и довольно много читаю на английском. Но выяснилось, что читать и переводить – разные вещи. Также, к сожалению, у меня нет значительного опыта в программировании (недавно только сделал простейшее веб-приложение на Spring MVC и Hibernate). Поэтому перевод получился значительно хуже, чем мог бы быть. Я взял на себя смелость несколько подкорректировать примеры кода, которые даются в статье, так как они не соответствуют соглашениям по наименованию (code conventions) в Java. Возможно, не стоило переводить название некоторых паттернов (такой перевод мало что дает для понимания), но я посчитал, что это меньшее зло. Отдельно стоит сказать о «высокой сцепленности» как переводе «high cohesion». Согласен, не самый лучший перевод. Но «сильная связность» — это «high coupling» (другое важное понятие), а «когерентность» здесь вряд ли подойдет. Я открыт для критики и с признательностью приму замечания по статье в любой форме.


Объектно-ориентированное программирование – это стиль программирования, в котором программа состоит из компонентов, соответствующих объектам реального мира

Любой реальный объект имеет какие-то свойства (которые могут изменяться или нет с течением времени) и поведение (которое может меняться или нет в зависимости от других условий). Например, карандаш – это объект реального мира, который имеет следующие свойства:
  • Он красный (это не меняется с течением времени).
  • Он 10 сантиметров в длину сейчас (это может меняться, если карандаш заточить).
И он имеет следующее поведение:
  • Он оставляет след, если его использовать правильно.
  • След может отличаться в зависимости от давления (зависит от внешних факторов).
  • Его длина сокращается, если его затачивать (постоянное поведение).
Как в этом примере, объекты реального мира могут иметь много свойств, но при написании программ мы принимаем во внимание только необходимые свойства.
Объектно-ориентированное программирование имеет свои преимущества. Например, оно облегчает установление связи между объектом реального мира и программой так, как это ожидается. Это реально помогает по мере того, как приложение растет и множество объектов взаимодействуют друг с другом. Это помогает в распределении ответственности внутри объективного мира, позволяя сфокусироваться на продумывании приложения.
Другая важная особенность, ассоциируемая с ООП (Объектно-ориентированным программированием), — это классификация объектов. Так как мир (реальный/виртуальный) полон объектов, сложно управлять по отдельности. Нам нужен способ классификации этих объектов, который поможет нам связать различные объекты и их свойства, например, черный карандаш. Он был бы неотличим (тот же самый?), если бы использовался в предыдущем примере, но это другой объект. Но так как это оба карандаши, они принадлежат одному и тому же классу «Карандаш». Тогда как ручка, которая очень похожа на карандаш, принадлежит к другому классу. Тем не менее, ручка и карандаш оба являются «Пишущими инструментами».

Объектно-ориентированное программирование имеет следующие принципы:

Абстракция
Абстракция определяется как характерная черта (quality) взаимодействия с идеями, а не событиями или, другими словами, свобода от репрезентирующих качеств. Это позволяет программистам сфокусироваться на том, что программировать, а не как. Абстракцию можно рассматривать в качестве соглашения, посредством которого мы предоставляем функциональность. Детали реализации могут быть скрыты, если использовать этот концепт.
Например, если нам нужен класс, который пишет, то мы должны быть уверены, что обладает методов «писать»
abstract class writer {
	write ();
}

Что мы сделали? Мы разработали класс высокого уровня, являющийся абстрактными, иными словами, он знает, что за функциональность нам нужна, но как ее реализовать – это за пределами видимости (out of scope) данного класса.
Это дает много преимуществ:
  • Мы раскрываем минимум информации необходимой внешним сущностям, это позволяет сосредоточиться на продумывании программы (this enable focused thinking), избежать путаницы и не давать непреднамеренных обещаний.
  • Мы оставляем место для улучшений в будущем, которые были бы невозможны, если детали реализации были раскрыты.

Наследование
«Наследование» в общеупотребительном английском означает «приобретать и передавать дальше». Это слова существует в нашей культуре очень давно. Предки приобретали землю с помощью тяжелой работы и передавали ее своим детям, даже природа благоволит наследованию. Все свойства тела, например, рост, цвет кожи/глаз/волос и т.д. зависят от генов, наследуем нами от наших родителей. Наследование предотвращает изобретение колеса заново и ускоряет прогресс. То же самое и в ООП. Мы создаем родительский класс с несколькими базовыми свойствами/поведением. Все классы, наследуемые от этого родителя, будут содержать такие же свойства/поведение, что и их родитель. Тем не менее, наследуемые классы могут получить больше свойств/поведения или изменить реализацию поведения.
class WritingInstrument {
	colour;
	write() {
}
}
class Pen (child of parent) {
	inkcolour;
}

В примере сверху класс-родитель (WritingInstrument) имеет свойство «цвет» и поведение «писать». Когда класс-наследник (ручка) объявляется, повторное объявление свойства «цвет» и поведения «писать» не требуется. Они присутствуют в классе «ручка» в силу наследования. Однако класс-наследник может объявить собственные дополнительные свойства/поведение.
Как мы можем использовать это на практике? Мы, разработчики, очень ленивы. Мы не хотим печатать что-то снова и снова. Существование множества копий одного и того же кода не приветствуется в силу следующих соображений:
  • Чем меньше копий кода, тем легче его сопровождать.
  • Если нет множества копий кода, то изменение в одном месте становится видимым везде.
  • Чем меньше кода – тем меньше ошибок.
  • Если один код используется во многих местах, то так достигается обобщение.
  • Мы фокусируемся на написании кода.
  • Мы фокусируемся на тестах.
Наследование в Java достигается с помощью ключевых слов «extends» и «implements».
class WritingInstrument {
}
class Pen extends WritingInstrument {
}


Полиморфизм
Слова «полиморфизм» произошло от двух слов:
«Поли», т.е. «множество» / «больше, чем один»
«морф», т.е. «форма»
Буквально, слово «полиморфизм» отсылает к способности объектов вести себя различным образом в зависимости от условий.
В программировании полиморфизм может быть воплощен в нескольких местах:
  • Классы
  • Методы
  • Операторы
Все, перечисленное выше, может вести себя различным образом в зависимости от условий, возможно, от контекста, в которых они используются. Это полезно, так как клиенту (программисту, использующему ваши библиотеки) не нужно знать множество тонкостей, и желаемая функциональность реализуется посредством отбора необходимой информации из контекста.
Class WritingObject {
	wrire() {
	// пишем, используя стандартные (по дефолту) цвета
	}
}

class Pencil extends WritingObject {
	write() {
	// пишем, используя серый цвет, написанный текст можно стереть
	}
}

class Pen extends WritingObject {
	write() {
	// пишем, используя голубой цвет, написанный текст нельзя стереть
	}
}

class Main {
	main() {
		WritingObject wr = new WritingObject();
		wr.write(); // первый вызов
		WritingObject wr = new Pen();
		wr.write(); // второй вызов
		WritingObject wr2 = new Pencil();
		wr2.write(); // третий вызов 
	}
}

В примере выше имеет реализация по умолчанию в WritingObject, которая расширена/переопределена классами-наследниками перо и ручка. Метод write() вызван три раза в классе Main. Каждый раз вызывается различная реализация в зависимости от того, у какого объекта вызывается этот метод. В данном случае метод write() имеет множество типов поведения, так как он полиморфичен.

Инкапсуляция
Инкапсуляция определяется как сбор связанных данный/функциональностей в одном модуле (unit). Это помогает в облегчении доступа/модификации данных. Например, если нам нужно напечатать все свойства, которыми данный пользователь обладает, мы имеем следующие опции:
printUserProperties(userName, userId, firstname, lastname, email, phone, … … ….)

Мы создали метод, который принимает все свойства и печатает их друг за другом. С увеличением количества элементов в списке пропадет возможность идентифицировать корректные поля, а добавление/удаление одного поля изменит сигнатуру метода. Поэтому нам нужно заменить всех пользователей этого метода, если даже недавно добавленные поля им не нужны. Чтобы сделать код более читаемым и упростить будущие модификации проще, мы инкапсулируем свойства в классе и превращаем его в коллективный объект (collective object)
class User {
	userName
	userId
	firstname
	lastname
email
	phone
	..
	..
	..
}
printUserProperties(user) {}

Объект – это система (software bundle) переменных и связанных методов. Вы можете представить объекты реального мира, используя объекты программы. Вы можете представить реальных собак в анимационной программе или реальный велосипед как программный объект внутри велотренажера.
В ООП класс – расширяемый шаблон (program-code-template) для создания объектов, обеспечения их начальным состоянием (переменные) и реализацией поведения (функции, методы).
Аббревиатура SOLID была введена Michael Feather’ом для «первых пяти принципов», названных так Robert C. Martin’ом в начале 2000-х. Целью принципов, реализуемых совместно, является увеличение вероятности того, что программист создаст систему, которую легко будет поддерживать и расширять. Принципы SOLID – ориентиры в разработке программ, которые необходимы для удаления «протухшего» кода посредством рефакторинга, в результате которого код должен стать легко читаемым и расширяемым. Это часть стратегии agile and adaptive programming (гибкого и адаптирующегося программирования).

Принцип единой ответственности (Single Responsibility Principle)
В ООП принцип единой ответственности гласит, что каждый класс должен быть ответственен за одну часть функциональности, обеспечиваемой программой, и что ответственность должна быть полностью инкапсулирована этим классом. Вся его функциональность должна быть тесно связана с этой ответственностью.

Принцип открытости/закрытости (Open/Closed Principle)
В ООП принцип открытости/закрытости гласит «сущности программного обеспечения (классы, модули, методы и т.д.) должны быть открыты для расширения, но закрыты для изменения». Иными словами, сущность должна позволять расширять ее поведение без изменения исходного кода.

Принцип подстановки Лисковой (Liskov Substitution Principle)
Возможность подстановки (Substituability) – это принцип в ООП. Он гласит, что если S в компьютерной программе является подтипом T, то объекты типа T должны быть такими, чтобы их можно было заменить объектами типа S (т.е. объекта типа S могут заменить объекты типа T) без изменения каких-либо требуемых свойств программы (точность, выполнение задачи и т.д.).

Принцип разделения интерфейса (Interface Segregation Principle)
Принцип разделения интерфейса гласит, что программист-клиент не должен быть принужден зависеть от методов, которые он не использует. Согласно этому принципу нужно разделять большие интерфейсы на маленькие и более специфичные, чтобы программист-клиент знал только о методах, которые ему интересны. Целью принципа разделения интерфейса является сохранение системы в несвязанном состоянии (system decoupled), что облегчит рефакторинг, внесение изменений и повторное развертывание (redeploy).

Принцип инверсии зависимостей (Dependency Inversion Principle)
В ООП принцип инверсии зависимости означает специфическую форму несвязности программных модулей. При следовании этому принципу стандартные отношения зависимости, установленные от модулей высокого уровня, формирующих архитектуру приложения (policy-setting) к зависимым модулям низкого уровня инвертированы (обращены), поэтому измененные модули высокого уровня становятся независимы от деталей реализации модулей низкого уровня. Этот принцип утверждает:
  • Модули высокого уровня не должны зависеть от модулей низкого уровня. Модули обоих типов должны зависеть от абстракций.
  • Абстракции не должны зависеть от деталей реализации. Детали должны зависеть от абстракций.
Принцип обращает (inverts) путь, согласно которому люди могут думать об объектно-ориентированном дизайне, утверждая, что объекты высокого и низкого уровней должны зависеть от одних и тех же абстракций.

Принципы GRASP

Паттерны (принципы), используемые для решения общих задач по назначению обязанностей классам и объектам (General Responsibility Assignment Software Patterns (GRASP)) содержат руководства (guidelines) по назначению ответственности классам и объектам в объектно-ориентированном дизайне.

Контроллер (Controller)
Паттерн Контроллер назначает ответственность за взаимодействие с системными событиями классов без графического интерфейса, которые представляют всю систему или use case scenario (сценарии вариантов использования).
Контроллер:
  • Это не взаимодействующий напрямую с пользователем объект, ответственный за получение и реакцию на системные события.
  • Должен использоваться так, чтобы иметь дело со всем системными событиями одного (или множества взаимосвязанных) use cases.
  • Это первый объект за графическим интерфейсом, который контролирует системные операции.
  • Он не должен делать работу сам, его задача – контроль над потоком событий.

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

Высокая сцепленность (High Cohesion)
Высокая сцепленность – оценочный паттерн, целью применения которого является сохранение объектов в таком состоянии, чтобы они были нацелены на выполнение одной четкой задачи, легко управляемы и понимаемы. Высокая сцепленность обычно используется для поддержки слабой связности (Low Coupling). Высокая связность означает, что ответственность данного элемента четко обозначена (strongly related and highly focused). Разбиение программы на классы и подсистемы – пример действий, что повышает сцепленность свойств системы. Слабая сцепленность, наоборот, — ситуация, в которой элемент имеет слишком много несвязанных задач. Элементы со слабой сцепленностью обычно отличаются тем, что их сложно понять, сложно повторно использовать, поддерживать и изменять.

Окольный путь (Indirection)
Паттерн Окольный путь поддерживает слабую связность (и возможность повторного использования) между двумя элементами, назначая ответственность за взаимодействие между ними промежуточному объекту. Примером является введение контроллера для посредничества между данными (моделью) и их отображением (представлением) в паттерне Модель-Представление-Контроллер (MVC).

Информационный эксперт (Information Expert)
Информационный эксперт (также Эксперт или принцип Эксперта) – принцип, используемый для определения того, кому делегировать ответственность. Ответственность включает методы, вычисляемые поля и т.д. При использовании данного принципа при назначении ответственности главным подходом является следующая последовательность действий: анализ ответственности, определение информации, которая нужна для ее исполнения, наконец, установление того, где эта информация находится. Использование принципа Информационный эксперт приводит к назначению ответственности классу, который имеет наибольшее количество информации для ее исполнения.

Слабая связность (Low Coupling)
Слабая связность – оценочный паттерн, который указывает, как назначать ответственность: слабая зависимость между классами, изменение одного должно иметь минимальные последствия для другого, максимальная возможность повторного использования.

Полиморфизм (Polymorphism)
В соответствии с полиморфизмом изменения поведения основывается на типе объекта, на который ссылается переменная (responsibility of defining the variation of behaviors based on type is assigned to the types for which this variation happens). Это достигается использованием полиморфных операций.

Защищенные изменения (Protected Variations)
Паттерн Защищенные изменения защищает элементы от изменений других элементов (объектов, систем, подсистем) путем обертывания центра нестабильности (the focus of instability) интерфейсом и использования полиморфизма для создания различных реализаций данного интерфейса.

Чистое конструирование (Pure Fabrication)
Чистое конструирование предполагает класс, не представляющий концепт в проблемной области (the problem domain) и созданный специально для достижения слабой связности, высокой сцепленности и, следовательно, максимального потенциала повторного использования (решение, предлагаемое паттерном Информационный эксперт этого не обеспечивает). Такая класс обычно называется “Service” в Предметно-ориентированном дизайне (Domain-driven design).

Критика

Исследования Potok’a и др. показали отсутствие существенных различий между ООП и процедурными подходами.
Критическое сравнение ООП с другими технологиями, в особенности реляционными, затруднено в силу отсутствия определения ООП, которое было бы строгим и широко принятым (Christopher J. Date)
В сравнении с другими языками (LISP диалекты, функциональные языка и т.д.) ООП языки не имеют уникального преимущества и навязывают ненужную сложность. (Lawrence Krubner)
Я нахожу объектно-ориентированное программирование технически неосновательным. Оно пытается разложить мир на части в терминах интерфейсов, которые изменяются в пределах одного типа. Чтобы иметь дело с реальными проблемами, вам нужны многосортные алгебры — семейства интерфейсов, которые простираются на многие типы. Я нахожу объектно-ориентированное программирование философски нездоровым. Оно утверждает, что всё является объектом. Даже если это так, это не очень интересно: сказать, что всё является объектом — значит, не сказать вообще ничего. (Александр Степанов)
Популярность ООП среди больших компаний связана с «большими (и часто меняющимися) группами посредственных программистов». Дисциплина, навязываемая ООП, предотвращает нанесение программистом «слишком большого вреда». (Paul Graham)
Объектно-ориентированное программирование ставит существительные первыми и самыми главными. Зачем идти на такие крайние меры и ставить одну часть речи на пьедестал? Почему один концепт получает преимущество над другим? Это невозможно, чтобы ООП внезапно сделало глаголы менее важными для нашего мышления. Это странным образом перекошенная перспектива. (Steve Yegge)
Rick Hickey, создатель Clojure, описывал объектные системы как крайне упрощенные модели реального мира. Он подчеркивал неспособность ООП моделирования времени правильно, что создает огромные проблемы, когда в программах большое распространение получает многопоточность.
Eric S. Raymond, Unix-программист и сторонник программного обеспечения с открытым кодом, был критически настроен в отношении заявления, что ООП является «Единственно верным решением», и писал, что ООП поощряет многослойные программы, что препятствует понятности (transparency). В качестве противоположного подхода Raymond приводил пример Unix и С.

Ссылки

By Margaret Rouse @ WhatIs.com
Wikipedia! (Русский вариант)
inheritance is polymorphism
SOLID (Object Oriented Design) (Русский вариант)
Single Responsibility PrincipleArguments against OOPS (Русский вариант)
What is OOPS (without the hype)

Перевод: Варыгин Д.В.
  • ,

Проектирование Классов и Интерфейсов (Перевод статьи)

Исходная статья: www.javacodegeeks.com/2015/09/how-to-design-classes-and-interfaces.html

Эта статья является частью нашего Курса Академии под названием «Продвинутая Java».
Этот курс призван помочь вам наиболее эффективно использовать Java. В нем обсуждаются передовые темы, такие как создание объекта, взаимосовместимость(параллелизм), преобразование в последовательную форму, отражение и многое другое. Это будет вашим путеводителем в вашем путешествии к мастерству в Java. Убедись сам!


Содержание

  1. Введение
  2. Интерфейсы
  3. Интерфейс-маркеры
  4. Функциональные интерфейсы, статические методы и методы по умолчанию
  5. Абстрактные классы
  6. Неизменяемые (постоянные) классы
  7. Анонимные классы
  8. Видимость
  9. Наследование
  10. Множественное наследование
  11. Наследование и композиция
  12. Инкапсуляция
  13. Final классы и методы
  14. Что дальше
  15. Скачать исходный код

  • ,

Как пользоваться CopyOnWriteArraySet в Java с примером (перевод)

CopyOnWriteArraySet это младший брат класса CopyOnWriteArrayList. Это специализированный набор классов, добавленных в JDK 1.5 вместе с их более популярным двоюродным братом ConcurrentHashMap. Они являются частью concurrent collection framework и расположены в пакете java.util.concurrent.

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

Одна из самых важных вещей, которую стоит знать о CopyOnWriteArraySet это то, что он реализован при помощи CopyOnWriteArrayList. Это означает, что CopyOnWriteArraySet также разделяет все основные свойства CopyOnWriteArrayList. Еще одна важная вещь, которую стоит запомнить: итераторы этого класса коллекции не поддерживают операцию remove(). Попытка удалить элемент во время итерирования приведет к выбросу UnsupportedOperationException. Это сделано чтобы гарантировать скорость во время обхода. Обход этой реализации Set, используя итератор, осуществляется достаточно быстро, и во время него исключены вмешательства других потоков. В своей работе итераторы опираются на моментальный снимок массива, который был сделан во время создания итератора.

Короче говоря, используйте CopyOnWriteArraySet если set достаточно мал, чтобы копировать его при добавлении, задании значения или удалении объектов, и основной целью является чтение обновляемых от случая к случаю данных. Кроме того, если вы хотите удалить элементы во время итерации, не используйте эту реализацию, потому что ее итератор не поддерживает remove(), и бросает java.lang.UnsupportedOperationException, как показано ниже:


[RAJ] Event received : FOUR 
Exception in thread "main" java.lang.UnsupportedOperationException
    at java.util.concurrent.CopyOnWriteArrayList$COWIterator.remove(Unknown Source)
    at Publisher.notifySubs(HelloHP.java:43)
    at HelloHP.main(HelloHP.java:23)


CopyOnWriteArraySet пример на Java

Вот готовая программа на Java показывающая, как использовать CopyOnWriteArraySet. В нашем примере мы использовали шаблон проектирования «Издатель-подписчик» (англ. publisher-subscriber pattern), чтобы продемонстрировать его использование. Большинство подписчиков подписаны во время пуска приложения и главной задачей издателя является их перебор и уведомление о каких-либо обновлениях. Время от времени может случаться добавление и удаление подписчика. Так как нам нужен быстрый обход, CopyOnWriteArraySet является хорошим выбором, особенно в многопоточной среде, где один поток может добавить абонента, а другой поток обрабатывает обновления.


import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * Java program to demonstrate how to use CopyOnWriteArraySet in Java. Remember,
 * CopyOnWriteArraySet doesn't support remove() operation.
 *
 * @author Javin Paul
 */
public class CopyOnWriteArraySetDemo{

    public static void main(String args[]) {
        Publisher cricNext = new Publisher();

        SubScriber raj = new SubScriber("RAJ");
        SubScriber adom = new SubScriber("ADOM");

        cricNext.addSubscriber(raj);
        cricNext.addSubscriber(adom);

        cricNext.notifySubs("FOUR");
        cricNext.notifySubs("SIX");

    }

}

class Publisher {

    private CopyOnWriteArraySet setOfSubs = new CopyOnWriteArraySet();

    public void addSubscriber(SubScriber sub) {
        setOfSubs.add(sub);
    }

    public void notifySubs(String score) {
        Iterator itr = setOfSubs.iterator();
        while (itr.hasNext()) {
            SubScriber sub = itr.next();
            sub.receive(score);

            //itr.remove(); // not allowed, throws UnsupportedOperationException
        }
    }
}

class SubScriber {

    private String _name;

    public SubScriber(String name) {
        this._name = name;
    }

    public void receive(String score) {
        System.out.printf("[%s] Event received : %s %n", _name, score);
    }
}


Output:
[RAJ] Event received : FOUR 
[ADOM] Event received : FOUR 
[RAJ] Event received : SIX
[ADOM]Event received : SIX


Что нужно запомнить

CopyOnWriteArraySet реализует интерфейсы Collection и Set, а также, добавленный в JDK 1.5, вместе с другой специальной реализацией Set'а, EnumSet. Это также Set, который использует внутренний CopyOnWriteArrayList для всех своих операций. Таким образом, он разделяет те же основные свойства этого класса. Так как это не SortedSet, порядок элементов не гарантируется в течение итерации.

1. CopyOnWriteArraySet лучше всего подходит для приложений, в которых:
  • Размеры Set'ов, как правило остаются небольшими.
  • Операции read-only значительно превосходят операции, изменяющие объекты.
  • Вы должны предотвратить помехи между потоками во время обхода Set'а.

2. Еще одним преимуществом CopyOnWriteArraySet является потокобезопасность. Эта коллекция поддерживает параллелизм.
3. Мутативные операции (добавление, изменение, удаление и т.д.) являются дорогостоящими, так как они, как правило, требуют копирования всего базового массива.
4. Итераторы не поддерживают мутативную операцию удаления.
5. Обход используя итератор достаточно быстр и во время него исключены вмешательства других потоков. В своей работе итераторы опираются на моментальный снимок массива, который был сделан во время создания итератора.

На этом все об использовании CopyOnWriteArraySet в Java. Как я сказал, он является младшим братом CopyOnWriteArrayList. Так что если вы понимаете хотя бы один из них, то сможете использовать и другой. Единственное отличие в том, что один является List'ом а другой Set'ом, таким образом это влечет за собой унаследование всех отличий между этими структурами данных в Java. Например, в List важен порядок расположения элементов, и он может содержать дубликаты. В то время как Set является неупорядоченным, но не позволяет дублирование объектов.

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

Оригинал статьи
  • ,

Основы Параллелизма: взаимоблокировки и мониторы объектов (раздел 3) (перевод статьи)

Исходная статья: www.javacodegeeks.com/2015/09/concurrency-fundamentals-deadlocks-and-object-monitors.html
Автор: Martin Mois

Первые две части перевода здесь.

Содержание

1. Живучесть
 1.1 Взаимоблокировка
 1.2 Голодание
2. Мониторы объектов совместно с wait() и notify()
 2.1 Вложенные синхронизированные блоки совместно с wait() и notify()
 2.2 Условия в синхронизированных блоках
3. Проектирование для многонитевости
 3.1 Неизменяемый объект
 3.2 Проектирование API
 3.3 Локальное хранилище нити

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

3.1 Неизменный объект
Одним из правил проектирования, являющихся очень важным в данном контексте, является Неизменяемость. Если вы распределяете экземпляры объектов, например, между различными нитями, то должны уделить внимание тому, чтобы две нити не изменяли один и тот же объект одновременно. В подобных ситуациях легко управляться с немодифицируемыми объектами, поскольку вы не можете их изменить. Вы всегда должны создавать новый экземпляр, когда хотите изменить данные. Базовый класс java.lang.String — пример неизменяемого класса. Вы получаете новый экземпляр всякий раз, когда хотите изменить строку:

String str = "abc";

String substr = str.substring(1);


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

Далее приведён список правил, которые необходимо применять, чтобы сделать класс неизменяемым:
  • ,

Основы Параллелизма: взаимоблокировки и мониторы объектов (разделы 1, 2) (перевод статьи)

Исходная статья: www.javacodegeeks.com/2015/09/concurrency-fundamentals-deadlocks-and-object-monitors.html
Автор: Martin Mois

Эта статься — часть нашего курса Основы Параллелизма в Java.

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


Содержание

1. Живучесть
 1.1 Взаимоблокировка
 1.2 Голодание
2. Мониторы объектов совместно с wait() и notify()
 2.1 Вложенные синхронизированные блоки совместно с wait() и notify()
 2.2 Условия в синхронизированных блоках
3. Проектирование для многонитевости
 3.1 Неизменяемый объект
 3.2 Проектирование API
 3.3 Локальное хранилище нити

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

1.1 Взаимная блокировка
Термин взаимоблокировка хорошо известен разработчикам ПО и даже большинство обычных пользователей используют его время от времени, хотя и не всегда в правильном смысле. Строго говоря, этот термин означает, что каждая из двух (или больше) нитей ждут от другой нити, чтобы она освободила заблокированный ею ресурс, в то время как первая сам заблокировала ресурс, доступа к которому ждёт вторая:


Thread 1: locks resource A, waits for resource B

Thread 2: locks resource B, waits for resource A


Для лучшего понимания проблемы взглянем на следующий код:
  • ,

Java. Справочное руководство для интервью - Часть 1. (Перевод статьи)

Исходная статья находится по адресу:
http://www.javacodegeeks.com/2014/02/java-interview-reference-guide-part-1.html

Опубликовал: Nitin Kumar, в Core Java, 3 февраля 2014 г

Java. Объектно-Ориентированная концепция.


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

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

В Java, класс — это документация, шаблон или прототип, который определяет общее поведение объектов того же вида. Экземпляр является реализацией определенного класса, и все экземпляры класса имеют аналогичные свойства, как представлено в описании класса. Например, вы можете определить класс, который называется Дом, с числом комнат, как атрибутом, и создать объекты: дом с 2 комнатами, дом с 3 комнатами и т.д.

Преимущества:

Ниже перечислено несколько преимуществ разработки объектно-ориентированного программного обеспечения:

  • Меньше затрат на обслуживание, главным образом благодаря модульности.
  • Код легче повторно использовать за счет таких функций, как наследование, что как следствие ускоряет разработку.
  • Повышается надежность и гибкость кода.
  • Код легче понять, это обусловлено моделированием реального мира.
  • Лучшая абстракция на объектном уровне.
  • Уменьшение сложности перехода от одной стадии разработки к другой.

Существуют четыре основных понятия ООП:

  • Инкапсуляция
  • Наследование
  • Полиморфизм
  • Абстракция

Инкапсуляция:

Инкапсуляция представляет собой правила для других объектов, которые указывают, какие элементы скрыты, а какие открыты для доступа другим объектам. В Java мы используем модификатор доступа private для того, чтобы скрыть метод и ограничить доступ к переменной из внешней среды. Java также предусматривает различные модификаторы доступа, такие как public, который устанавливается по-умолчанию, protected и private, которые используются для ограничения видимости на различных уровнях, но конечная цель инкапсуляции — это скрыть те элементы, которые не должны изменяться. На практике лучше всего, когда класс имеет только одну причину для изменения, и инкапсуляция реализует принципы проектирования этой «одной причины».

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

Преимущества:
  • ,

15 вопросов для собеседования разработчиков, относительно Enum в Джаве (с ответами!)

Перечисление (Enum) было введено в Java 5 и с тех пор оно стало весьма популярно среди разработчиков Java. Оно широко используется в различных Java-приложениях. Поскольку Enum в Java намного универсальнее, чем в C или C ++, оно также представляет множество интересных вариантов использования, пару из которых, вы видели в статье 10 способов использовать Enum в Java.

Но, несмотря на такую популярность, многие Java-программисты по-прежнему не знают о функциональности, предоставляемой Enum и тонкостей использования Enum в Java коде.

Как отправить письмо из Java-приложения (с примером)

Перевод статьи: How to Send Email from Java Program with Example

Отправка электронного сообщения из Java-приложения это распространенное требование. Не имеет значения работаешь ли ты над core Java-приложением, веб-приложением или enterprise Java — приложением, тебе может понадобится отправить письмо для оповещения персонала из тех.поддержки об ошибках, или просто отправить письмо пользователям при регистрации, восстановить пароль или попросить их подтвердить адрес электронной почты после регистрации. Есть очень много сценариев(случаев), когда тебе нужна возможность отправить электронные письма из Java-приложения. В готовых приложениях у тебя уже есть модуль или библиотека, работающая со всеми функциями для отправки электронных писем, например, возможность отправлять вложения, изображения, включая подписи и форматированный текст в электронных письмах, НО если тебе нужно написать что-то с нуля, тогда Java Mail API это прекрасный вариант. В этой статье мы научимся как отправлять электронные письма из Java-приложения, используя почтовый API(javax.mail). Перед написанием кода ты должен знать некоторые основы работы электронной почты, например, тебе нужен SMTP(простой протокол передачи почты) сервер. Если ты запускаешь свое Java-приложение под Linux, тогда ты должен знать, что SMTP демон(прим. «компьютерная программа в системах класса UNIX, запускаемая самой системой и работающая в фоновом режиме без прямого взаимодействия с пользователем») использует порт 25. Ты можешь использовать любой почтовый сервер для отправки сообщений применяя Java, включая общедоступные почтовые сервера, такие как GMail, Yahoo или любой другой поставщик услуг, все что тебе нужно это детали их SMTP сервера, например, имя хоста, порт, параметры соединения и т.д. Так же ты можешь использовать SSL(англ. secure sockets layer — уровень защищённых сокетов), TLS(англ. Transport Layer Security — безопасность транспортного уровня) для безопасного соединения и отправки писем, но этот пример мы сделали простым и cфокусировались на минимальной логике для отправки письма из Java-приложения. В дальнейших статьях мы научимся как отправлять письмо используя вложения, как отправлять форматированное с помощью HTML письмо, как прикрепить изображения в письмо, как использовать SSL аутентификацию для соединения с сервером GMail и отправки писем и т.д. А сейчас, давай поймем(разберем) этот простой пример Java Mail API.

Пример Java-кода для отправки электронного сообщения



Для отправки письма из Java-программы тебе потребуются Java Mail API и Java Activation Framework(JAF); если более точно, то тебе понадобятся mail-1.4.5.jar, smtp-1.4.4.jar, и activation-1.1.jar. Тебе нужно скачать эти JAR файлы и включить их в твой Classpath для запуска этой программы. В качестве альтернативы ты можешь использовать Maven для управления зависимостями и включить все зависимости там. Как только у тебя будут все эти JAR файлы, просто следуй шагам ниже для создания и отправки электронного письма c помощью Java.

  1. Создай объект Session вызывая Session.getDefaultInstance(properties), где properties — содержит все важные свойства, например, имя хоста SMTP сервера.
  2. Создай объект MimeMessage передавая ему объект Session, полученный на предыдущем шаге. У нас есть набор различных свойств в этом объекте, таких как получатель письма, тема, тело сообщения, вложения и т.д.
  3. Используй javax.mail.Transport для отправки письма вызывая статический метод send(email), где email может быть объект MimeMessage.