• ,

Как пользоваться 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


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

Пример SynchronousQueue в Java - решение задачи Производитель Потребитель

Пример SynchronousQueue в Java — решение задачи Производитель Потребитель


SynchronousQueue – это специальный тип BlockingQueue, в котором каждая операция insert должна ждать соответствующую команду remove в другой нити, и наоборот.
Когда вы вызываете метод put() у SynchronousQueue, он блокируется до тех пор, пока другая нить не заберет этот элемент из него. Соответственно, если другая нить пытается удалить элемент из него, а элемента там нет, то эта нить блокируется до тех пор, пока другая нить не положит элемент в очередь. Можно представить SynchronousQueue как спортсмена (нить) бегущего с олимпийским факелом, он бежит с факелом (объектом который передается) и передает его другому спортсмену, ожидающему с другой стороны. Если вы обратите внимание на название, то поймете, что SynchronousQueue так назван не безосновательно, он передает данные синхронизированно в другую нить; он ждет пока кто-то заберет данные, вместо того чтобы просто положить их и завершиться (асинхронная операция). Если вы знакомы с CSP и Ada, то вы знаете что синхронизированные очереди похожи на встречу потоков. Они хорошо подходят для конструкций передачи управления, в которых объект запущенный в одной нити, должен синхронизироваться с объектом в другой нити, чтобы передать ему какую-то информацию, событие или задание. В ранее изученных учебниках по много-нитиевому программированию мы изучали как решить задачу производитель-потребитель, используя методы wait и notify, и BlockingQueue. Сейчас мы узнаем как применить производитель-потребитель паттерн используя SynchronousQueue. Этот класс дополнительно поддерживает честное поведение для упорядоченности ожидания нитей производителя и потребителя. По умолчанию, эта упорядоченность не гарантированена. Однако очереди, созданные с честными свойствами
делают гарантированным доступ для нитей в очередности FIFO (Firs In First Out – кто Первый Пришел, тот Первый Вышел).
Производитель потребитель используя SynchronousQueue в Java.


Как я говорил выше, нет ничего лучше, чем задача производителя-потребителя для понимания между-нитиевого взаимодействия в любом языке программирования. В этой проблеме одна нить выступает как производитель который производит события и задания, а другая нить выступает потребителем этого. Общий буфер используется для передачи данных от производителя к потребителю. Сложность решения этой задачи приходит в крайних случаях, например, когда производитель вынужден ждать т.к. буфер заполнен или потребитель вынужден ждать, т.к. буфер пуст. Это легко решалось, т.к. блокирующая очередь предоставляла не только буфер для хранения данных, но и управление потоком, блокируя нить вызывающую put() метод (Производитель) если буфер заполнен, и блокируя нить вызывающую take() метод (Потребитель) если буфер пуст. Сейчас мы решим эту же самую задачу используя SynchronousQueue, специальный вид параллельных коллекций с нулевой емкостью.
В следующем примере у нас есть две нити которые называются PRODUCER и CONSUMER (всегда давайте имена нитям, это очень хороший стиль много-нитиевого программирования).Первая нить размещает счет в игре, а вторая нить его потребляет. Счет в игре ничто иное как объект типа String. Но если вы запустите программу с другим типом, вы не заметите никакой разницы. Чтобы понять как SynchronousQueue работает, и как решать задачу производитель-потребитель вам нужно: либо запустить программу на отладку (debug) в среде Eclipse, либо просто запустить нить производителя закомментировав consumer.start(); если нить потребителя не запущена то нить производителя будет заблокирована на queue.put(event); если запущена, то вы не сможете видеть как производитель [PRODUCER] публикует событие :FOUR. Это происходит т.к. специфическое поведение SynchronousQueue, которое гарантирует, что нить размещающая данные будет заблокирована до тех пор пока другая нить не заберет эти данные, и наоборот. Вы можете протестировать оставшуюся часть кода закомментировав producer.start(); и запуская только нить потребителя.

import java.util.concurrent.SynchronousQueue; 
/** * Java Program to solve Producer Consumer problem using SynchronousQueue. A 
* call to put() will block until there is a corresponding thread to take() that 
* element. 
* * @author Javin Paul 
*/ 
public class SynchronousQueueDemo{ public static void main(String args[]) 
{ 
    final SynchronousQueue<String> queue = new SynchronousQueue<String>(); 
    Thread producer = new Thread("PRODUCER") 
    { 
        public void run() { 
            String event = "FOUR"; 
            try 
            { 
                queue.put(event); // thread will block here 
                System.out.printf("[%s] published event : %s %n", Thread .currentThread()
                    .getName(), event); 
           } catch (InterruptedException e) { 
                e.printStackTrace(); 
            } 
        }     
    };
 
    producer.start(); // starting publisher thread 

    Thread consumer = new Thread("CONSUMER") { 
        public void run() {
            try 
            { 
                String event = queue.take(); // thread will block here 
                System.out.printf("[%s] consumed event : %s %n", Thread .currentThread()
                    .getName(), event); 
            } catch (InterruptedException e) { 
                e.printStackTrace(); 
            } 
        }     
    }; 
    
    consumer.start(); // starting consumer thread 

    } 
} 

Output: 
[CONSUMER] consumed event : FOUR 
[PRODUCER] published event : FOUR 

Если вы внимательно изучите что программа выводит, то заметите что порядок вывода обратный. Выглядит как будто нить [CONSUMER] забрала данные еще до того как нить [PRODUCER] произвела их. Это произошло из-за того, что по умолчанию SynchronousQueue не гарантирует очередности. Но у нее есть правила честности, которые устанавливают доступ к нитям в порядке FIFO. Вы можете включать эти правила передавая true в перегруженный конструктор SynchronousQueue например таким образом:
new SynchronousQueue(boolean fair).

Что надо запомнить про SynchronousQueue в Java.

Тут несколько важных свойств этого специального типа блокирующейся очереди в Java. Очень полезно передавать данные из одной нити в другую синхронизированно. Эта очередь не имеет объемаи заблокированна до тех пор пока ее не освободит другая нить.
  1. SynchronousQueue блокируется, и до тех пор пока одна нить не будет готова взять данные, другая будет пытаться положить данные.
  2. SynchronousQueue не имеет объема. То есть в ней не содержатся данные.
  3. SynchronousQueue используется для реализации стратегии очередности прямой передачи управления, где нить передает управление ожидающей нити, или создает новую если это разрешено, иначе управление не передается.
  4. Эта очередь не пропускает null-данные. Попытка добавить null элемент кинет NullPointerException.
  5. Если использовать другие методы из Collection (например contains), SynchronousQueue ведет себя как пустая коллекция.
  6. Вы не сможете использовать метод peek у SynchronousQueue, потому что элемент существует только тогда когда вы пытаетесь его удалить; так же вы не сможете вставлять элементы (используя любой метод) пока другая нить не пытается его удалить.
  7. Вы не сможете использовать iterator для SynchronousQueue, т.к. в ней нет элементов.
  8. SynchronousQueue может создаваться с честными правилами, когда гарантируется доступ к нитям в порядке FIFO.

Пожалуй это все о SynchronousQueue в Java. Мы рассмотрели некоторые особенные возможности этой много-нитиевой коллекции, и научились решать классическую задачу производитель-потребитель используя SynchronousQueue в Java. Между прочим называть ее Очередью не совсем верно, т.к. у она не содержит элементов. Вызов метода put() не завершится до тех пор пока другая нить не вызовет метод take(). Правильнее представлять ее как место встречи нитей, где они делятся объектом. Другими словами, это утилита по синхронизированной передаче объектов в Java, возможно более безопасная альтернатива методу с использованием wait и notify.
  • ,

Параллелизм в Java. Учебник – потокобезопасные конструкции.

После рассмотрения основных рисков при работе параллельных программ (таких как атомарность или видимость), мы посмотрим на некоторые конструкции классов, которые помогут нам предотвратить вышеупомянутые ошибки. Некоторые из этих конструкций создают потокобезопасные объекты, что позволяет нам безопасно делиться ими между потоками. В качестве примера мы рассмотрим неизменяемые и stateless объекты. Другие представления будут предотвращать модифицирование данных разными потоками, таких как локальные переменные потока.
  • ,

Особенности Java 8 – максимальное руководство (часть 2)

Вторая часть перевода статьи Java 8 Features – The ULTIMATE Guide. Первая часть тут (ссылка может поменяться).

  • ,

Особенности Java 8 – максимальное руководство (часть 1)

Первая часть перевода статьи Java 8 Features – The ULTIMATE Guide. Вторая часть тут (ссылка может поменяться).

От редакции: статья опубликована в то время как Java 8 была доступна общественности и все указывает на то, что это действительно major-версия.
Здесь мы в изобилии предоставили руководства Java Code Geeks, такие как Играем с Java 8 – Лямбды и Параллелизм, Java 8 руководство по API даты и времени: LocalDateTime и Абстрактный Класс против Интерфейса в эру Java 8.

Мы также ссылаемся на 15 необходимых к прочтению руководств по Java 8 из других источников. Конечно мы рассматриваем некоторые из недостатков, например, Темная сторона Java 8.

Итак, пришло время собрать все основные особенности Java 8 в одном месте для вашего удобства. Наслаждайтесь!

Параллельные операции над массивами в Java 8 - перевод

перевод статьи
//Parallel Array Operations in Java 8
//By Eric Bruno, March 25, 2014
//www.drdobbs.com/jvm/parallel-array-operations-in-java-8/240166287
//Eric Bruno работает в финансовом секторе и ведёт блоги для сайта Dr. Dobb's.


Новый релиз Java упрощает параллельное взаимодействие с массивами — что приводит к значительно улучшенной производительности с минимумом написания кода.

Сейчас, Oracle выпускает Java SE 8 — что является огромным шагом вперёд в плане языка. Одна из важных деталей этого релиза — улучшенная concurrency(параллельность), часть которой появляется в базовом классе java.util.Arrays. В этот класс добавлены новые методы, которые я и буду описывать в этой статье. Некоторые из этих них используются в другой новой фиче JDK8 — в lambda(лямбдах). Но перейдем к делу.

Arrays.paralellSort()

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