Статьи

Изучение модели параллелизма Java с JRuby в водительском кресле

В наши дни темы снова в моде . Если бы вы были как я, инженер Java, работавший в Ruby пару лет назад, вы, вероятно, видели бы свою долю участия в JVM от нескольких людей, которые любили избивать все Java, продавая при этом свои наполовину драгоценные камни как шедевры программного обеспечения. Это не значит, что Java, особенно сообщество, любит чрезмерно проектировать дерьмо из всего, на что они могут рассчитывать, но (и это довольно большое НО) JVM является оригинальным программным обеспечением. Хорошо, если ты все еще слоняешься после этой напыщенной речи, я благодарю тебя за предоставленную мне эту снисходительность.

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

Почему JRuby?

Столько, сколько я ненавижу иррациональное избиение другого языка, я действительно полюбил работать с Руби. Это просто замечательно для того, чтобы «изложить свои идеи на бумаге» с минимальной церемонией. Хотя для многих эта церемония может показаться не такой уж важной, но на самом деле это так . Этот доклад о когнитивной психологии углубляется в детали того, как наш мозг обрабатывает информацию, и как код базовой платы мешает другим инженерам понять ваш код. Использование JRuby также дает непреднамеренное преимущество, заставляя меня сконцентрироваться только на понятиях параллелизма и параллелизма , не тратя много времени на все эти вещи Java, такие как конечные переменные или приватная статика.методы, среди других. Также будет гораздо интереснее сосредоточиться только на удивительных вещах пакетов параллелизма Java, пропуская при этом весь код, который является неотъемлемой частью работы с Java.

Параллелизм и параллелизм

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

Диаграмма параллелизма

Как видите, параллелизм не означает присед. В параллельном графике поток A запускается со времени 0-10, а затем планировщик ЦП планирует запуск потока B со времени 10-20, после чего он переключается обратно в поток A и так далее, и так далее. Таким образом, машина фактически не выполняет команды процессора одновременно . В параллельном графике машина выполняет инструкции в обоих потоках одновременно между временем 10 и временем 30. Все это предполагает, что у вас, конечно, многоядерный ЦП (если вы этого не сделаете, вы, вероятно, не хотите читать этот или любой другой пост в блоге о распараллеливании работы).

Поток безопасности

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

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

class Incrementer
  def increment
    @val ||= 0
    @val += 1
  end
end

incrementer = Incrementer.new
incrementer.increment

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

@val += 1

На самом деле это не атомарная операция, т. Е. Компьютерная операция, которая представляет собой ровно одну инструкцию. Скорее, это можно рассматривать как две отдельные операции:

temp = @val + 1   - (1)
    @val = temp       - (2)

 С небольшим неудачным временем строка (1) может завершиться потоком A, а затем поток B сможет запустить и повторно выполнить строку (1), в этом случае он читает устаревшее значение @val. Существует также инициализация переменной, которая не является потокобезопасной, но я оставлю это для дальнейшего обсуждения. Так как же сделать этот поток кода безопасным?

Синхронизация и блокировки

Один из самых простых способов сделать этот поток кода безопасным — требовать, чтобы всякий раз, когда данный поток планировался для выполнения планировщиком ЦП, он нуждался в блокировке . Если этого не произойдет, он должен ничего не делать и вернуться к ожиданию. В Java есть ключевое слово synchronized, которое позволяет потоку попытаться получить блокировку для объекта. Потокобезопасная версия инкремента с этим параметром выглядит следующим образом:

require 'java'

class Incrementer < java.lang.Object
  def increment
    self.synchronized do
      @val ||= 0
      @val += 1
    end
  end
end

incrementer = Incrementer.new
incrementer.increment

Вызов self.synchronized пытается получить блокировку на себя , которая в этом будет экземпляр инкрементора из инкрементора класса. Несколько вещей, чтобы отметить здесь:

- In order to be able to synchronize on an Object, that Object 
  should have a *java.lang.Object* object somewhere it's ancestry tree
- Ensure that all your threads synchronize on the same Object, or 
  they'll be getting/releasing different locks

Этот код на самом деле является причиной чрезмерной синхронизации, и чрезмерная синхронизация плоха в этом, она медленная. Чтобы дать вам представление о некоторых крутых Java-пакетах, которые мы можем использовать, вот измененная версия:

def increment
    self.synchronized do
      @val ||= java.util.concurrent.atomic.AtomicInteger.new
    end
    @val.incrementAndGet
  end

Здесь нам все еще нужно защищать раздел нашего кода, где мы инициализируем нашу переменную экземпляра. Но как только мы убедились, что у нас есть действительная ссылка, мы снимаем блокировку и вызываем метод incrementAndGet, предоставленный нам объектами Java AtomicInteger.

Вывод

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

Следующий

Совместное использование данных, видимость потоков, изменчивые переменные, потокобезопасная инициализация и неизменные объекты и как они качаются!

Источник: http://santosh-log.heroku.com/2011/12/28/exploring-javas-concurrent-packages-via-jruby/