Статьи

Первый взгляд на поддержку параллелизма в Commons Lang 3.0

В первой части серии я рассказал о некоторых новых и улучшенных функциях Commons Lang 3.0. В этой статье я расскажу о параллельных утилитах, предоставляемых Commons Lang 3.0. Был добавлен новый пакет org.apache.commons.lang3.concurrent, который предоставляет классы поддержки для многопоточного программирования и использует классы Java 5 java.util.concurrent. *.

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

  1. LazyInitializer

    Этот класс обеспечивает полностью функциональную реализацию идиомы двойной проверки для поля экземпляра, как было предложено Джошуа Блохом в «Эффективной Java», 2-е издание, пункт 71. Это необходимо в тех случаях, когда объект должен быть инициализирован только тогда, когда он необходимо, потому что создание объекта стоит дорого или потребление памяти или системных ресурсов значительно. В этих случаях ленивая инициализация может помочь уменьшить количество ресурсов, используемых программой. Например, предположим, у нас есть пользователь, у которого есть список вложений. Мы не хотим, чтобы вложения загружались, пока они нам действительно не нужны, поэтому мы должны лениво инициализировать пользовательские вложения.

    publi class UserAttachmentInitializer extends
    LazyInitializer<UserAttachments> {

    @Override
    protected UserAttachments initialize() throws ConcurrentException {
    List<File> attachments = loadAllAttachments();
    return new UserAttachments(attachments);
    }

    /**
    * Loads all attachments
    *
    * @return
    */
    private List<File> loadAllAttachments() {
    return new ArrayList<File>();
    }

    }

    Чтобы применить отложенную инициализацию, мы должны создать подкласс абстрактного класса LazyInitializer и переопределить его метод инициализации, который возвращает UserAttachments. Доступ к объекту данных осуществляется через метод get (). Итак, код, который хочет получить экземпляр UserAttachments, будет выглядеть просто так:

    UserAttachments userAttachments = new UserAttachmentInitializer().get();

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

  2. AtomicInitializer

    Это еще один способ выполнить отложенную инициализацию объектов с помощью AtomicReference . Этот подход будет более эффективным, чем LazyInitializer, если число параллельных потоков невелико, поскольку не требует синхронизации. Недостатком является то, что инициализация может выполняться несколько раз, если несколько потоков одновременно получают доступ к инициализатору. Однако гарантируется, что инициализатор всегда создает один и тот же объект и будет возвращен.

    public class MyAtomicInitializer extends AtomicInitializer<MyObject> {

    @Override
    protected MyObject initialize() throws ConcurrentException {
    MyObject myObject = new MyObject("shekhar");
    return myObject;
    }

    }

    Клиент получит доступ так же, как указано в LazyInitializer.

  3. BackgroundInitializer

    BackgroundInitializer инициализирует объект в фоновой задаче. Это тонкая оболочка вокруг объекта java.util.concurrent.Future, которая использует ExecutorService для запуска фоновой задачи, выполняющей инициализацию. Этот инициализатор обычно используется в тех случаях, когда при запуске приложения приходится выполнять дорогостоящую инициализацию, например, чтение файла конфигурации, подключение к внешней службе или базе данных и т. Д. BackgroundInitializer — это абстрактный класс, поэтому мы должны переопределить его абстрактный метод инициализации.

    public static void main(String[] args) {
    ConnectionBackgroundInitializer initializer = new ConnectionBackgroundInitializer();
    ExecutorService exec = Executors.newSingleThreadExecutor();
    initializer.setExternalExecutor(exec);
    initializer.start();

    try {
    Connection connection = initializer.get();
    } catch (ConcurrentException e) {
    e.printStackTrace();
    } finally {
    exec.shutdown();
    }

    }

    Сначала создается объект BackgroundInitializer, а затем вызывается метод start. Вызов метода start запускает фоновую обработку, и приложение может продолжить работу над другими вещами. Когда ему нужен доступ к объекту, созданному BackgroundInitializer, он вызывает его метод get (). Если инициализация уже завершена, get () немедленно возвращает объект результата. В противном случае он блокируется, пока объект результата не будет полностью построен. Как показано в приведенном выше примере, вы можете передать ExecutorService перед вызовом метода start (), и он будет использоваться для создания фоновой задачи. Если вы не предоставляете какой-либо ExecutorService, то BackgroundInitializer создает временный ExecutorService и убивает его после завершения инициализации. В приведенном выше примере, потому что мы предоставляем наш собственный ExecutorService,мы должны убить это сами.

  4. CallableBackgroundInitializer

    Это конкретная специализированная реализация BackgroundInitializer, которая принимает Callable в своем конструкторе. Callable похож на java.lang.Runnable, но может вернуть результат и выдать проверенное исключение. CallableBackgroundInitializer заставляет Callable работать в фоновой задаче, а его метод initialize возвращает результат обернутого Callable. Использование этого класса похоже на работу BackgroundInitializer.

  5. MultiBackgroundInitializer

    Это специализированный BackgroundInitializer, который может работать с несколькими задачами фоновой инициализации. Это полезно для приложений, которые должны выполнять несколько задач инициализации, которые могут работать параллельно (т.е. которые не зависят друг от друга).

    public void testMultiBackgroundInitializer() throws ConcurrentException {
    MultiBackgroundInitializer initializer = new MultiBackgroundInitializer();
    MyBackgroundInitializer firstInitializer = new MyBackgroundInitializer();
    MyBackgroundInitializer secondInitializer = new MyBackgroundInitializer();
    String first = "First Initializer";
    initializer.addInitializer(first, firstInitializer);
    String second = "Second Initializer";
    initializer.addInitializer(second, secondInitializer);
    initializer.start();
    MultiBackgroundInitializerResults multiBackgroundInitializerResults = initializer
    .get();
    assertNotNull(multiBackgroundInitializerResults);
    BackgroundInitializer<?> firstBackgroundInitializer = multiBackgroundInitializerResults
    .getInitializer(first);
    BackgroundInitializer<?> secondBackgroundInitializer = multiBackgroundInitializerResults
    .getInitializer(second);
    assertEquals(Integer.valueOf(1),
    ((Integer) firstBackgroundInitializer.get()));
    assertEquals(Integer.valueOf(1),
    ((Integer) secondBackgroundInitializer.get()));
    }

    /**
    * A concrete implementation of {@code BackgroundInitializer} used for
    * defining background tasks for {@code MultiBackgroundInitializer}.
    */
    private static class MyBackgroundInitializer extends
    BackgroundInitializer<Integer> {

    volatile int initializeCalls;

    @Override
    protected Integer initialize() throws Exception {
    initializeCalls++;

    return initializeCalls;
    }
    }

Это охватывает большую часть функциональности, которая предоставляется в Commons Lang 3.0. пакет параллелизма.