Содержание


Руководство по работе с языком Scala для занятых разработчиков Java-приложений

Узнайте больше о параллелизме Scala

Узнайте о том, как акторы обеспечивают новый путь моделирования кода вашего приложения

Серия контента:

Этот контент является частью # из серии # статей: Руководство по работе с языком Scala для занятых разработчиков Java-приложений

Следите за выходом новых статей этой серии.

Этот контент является частью серии:Руководство по работе с языком Scala для занятых разработчиков Java-приложений

Следите за выходом новых статей этой серии.

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

Но поскольку я не привык впадать в отчаяние, я стал изучать вместе с вами некоторые элементы параллелизма Scala, начав с базового использования библиотек параллелизма Java непосредственно изнутри Scala, а затем перейдя к типу MailBox из Scala API. И хотя оба подхода можно с уверенностью назвать жизнеспособными, причина такой активной заинтересованности в Scala и параллелизме заключается не в этом.

На сцену выходят акторы Scala ...

Что такое «актор»?

По сути, реализация «акторов» — это подход, подразумевающий передачу сообщений между исполнительными элементами, называемыми акторами, с целью координирования работы (обратите внимание на намеренный отказ от использования таких слов, как «процесс», «поток» и «машина»). Если все это смутно напоминает вам механизм удаленного вызова процедур (RPC), то это, с одной стороны так и есть, а с другой – это не совсем так. Там, где вызов RPC (например, вызов RMI Java) блокирует вызывающую программу до того момента, пока сервер не завершит обработку и не отправит ответ (возвращаемое значение или исключение), метод обмена сообщениями умышленно не блокирует вызывающую программу, тем самым аккуратно предотвращая возможность взаимоблокировки.

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

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

Акторы Scala

По большому счету, работа с акторами не должна вызывать сложностей; самым простым действием здесь является создание актора с использованием метода actor класса Actor, как показано в листинге 1:

Листинг 1. И ... мотор!
import scala.actors._, Actor._

package com.tedneward.scalaexamples.scala.V4
{
  object Actor1
  {
    def main(args : Array[String]) =
    {
      val badActor =
        actor
        {
          receive
          {
            case msg => System.out.println(msg)
          }
        }
      
      badActor ! "Ну как, простофиля, тебе везет?"
    }
  }
}

Здесь одновременно происходит несколько событий.

Во-первых, мы импортируем библиотеку Scala Actors из одноименного пакета, а затем импортируем членов класса Actor непосредственно из этой библиотеки; данный второй шаг не является строго обязательным, поскольку мы могли бы пойти дальше и впоследствии использовать в коде Actor.actor вместо actor, но подобный ход событий создает впечатление того, что actor является встроенной конструкцией языка и (как считают некоторые) делает код удобочитаемым.

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

Напомним, что акторы используют для связи не методы, а сообщения. Возможно, покажется немного нелогичным, когда я скажу о том, что следующая строка, в которой используется !, фактически представляет собой метод (в реальности), который отправляет сообщение (образно говоря) для badActor. Далее, где-то глубоко в признаке Actor находится еще один из элементов MailBox, которые мы обсуждали в прошлый раз; метод ! принимает передаваемый параметр (в данном случае строку), записывает его в очередь сообщений и незамедлительно возвращает.

Как только сообщение доставлено актору, он обрабатывает его путем вызова метода receive, который совершает именно то, что подразумевает его название — он извлекает первое доступное сообщение из буфера и доставляет его в неявный блок сопоставления с образцом. Заметьте, что, поскольку вы не указали тип для сценария сопоставления с образцом, сопоставляться будет все, а сообщение привязывается к имени msg (которое необходимо вам для его вывода на печать).

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

Например, предположим, вы хотите, чтобы актор выполнил пару различных действий в ответ на отправленные сообщения; новая реализация могла бы выглядеть так, как показано в листинге 2:

Листинг 2. Эй, я режиссер!
  object Actor2
  {
    case class Speak(line : String);
    case class Gesture(bodyPart : String, action : String);
    case class NegotiateNewContract;
  
    def main(args : Array[String]) =
    {
      val badActor =
        actor
        {
          receive
          {
            case NegotiateNewContract =>
              System.out.println("Я не собираюсь заниматься этим меньше, чем за 1 миллион 
              долларов!")
            case Speak(line) =>
              System.out.println(line)
            case Gesture(bodyPart, action) =>
              System.out.println("(" + action + "s " + bodyPart + ")")
            case _ =>
              System.out.println("Что? Я буду в своем трейлере.")
          }
        }
      
      badActor ! NegotiateNewContract
      badActor ! Speak("Ну как, простофиля, тебе везет?")
      badActor ! Gesture("лицо", "гримассы")
      badActor ! Speak("Что ж, да?")
    }
  }

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

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

  • Разместите блок receive внутри почти бесконечного цикла.
  • Создайте новый класс сценария для указания момента завершения всей обработки.
Листинг 3. Теперь я стал намного лучшим режиссером!
  object Actor2
  {
    case class Speak(line : String);
    case class Gesture(bodyPart : String, action : String);
    case class NegotiateNewContract;
    case class ThatsAWrap;
  
    def main(args : Array[String]) =
    {
      val badActor =
        actor
        {
          var done = false
          while (! done)
          {
            receive
            {
              case NegotiateNewContract =>
                System.out.println("Я не собираюсь заниматься этим меньше, чем за 
                1 миллион долларов!")
              case Speak(line) =>
                System.out.println(line)
              case Gesture(bodyPart, action) =>
                System.out.println("(" + action + "s " + bodyPart + ")")
              case ThatsAWrap =>
                System.out.println("Отличной всем вечеринки! До скорого!")
                done = true
              case _ =>
                System.out.println("Что? Я буду в своем трейлере.")
            }
          }
        }
      
      badActor ! NegotiateNewContract
      badActor ! Speak("Ну как, простофиля, тебе везет?")
      badActor ! Gesture("лицо", "гримассы")
      badActor ! Speak("Что ж, да?")
      badActor ! ThatsAWrap
    }
  }

Тот, кто говорит, что постановка фильмов является сложным делом, точно не имел в составе исполнителей акторов Scala.

Действуя параллельно

Один аспект, не являющийся очевидным в данном коде, — это источник появления параллелизма (если он вообще присутствует). Таким источником могла бы с успехом быть одна из тех синхронных форм вызовов метода, о которых я вам уже рассказал, и вы не смогли бы найти различий. (С технической точки зрения вы могли бы прийти к заключению, что некоторый параллелизм прослеживается, начиная со второго примера, перед тем, как я задействовал почти бесконечный цикл, но это больше случайное, чем «железное» доказательство.)

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

Листинг 4. Я готов к съемке крупным планом, г-н ДеМилл
  object Actor3
  {
    case class Speak(line : String);
    case class Gesture(bodyPart : String, action : String);
    case class NegotiateNewContract;
    case class ThatsAWrap;
  
    def main(args : Array[String]) =
    {
      def ct =
        "Thread " + Thread.currentThread().getName() + ": "
      val badActor =
        actor
        {
          var done = false
          while (! done)
          {
            receive
            {
              case NegotiateNewContract =>
                System.out.println(ct + "Я не собираюсь заниматься этим меньше, чем за 
                1 миллион долларов!")
              case Speak(line) =>
                System.out.println(ct + line)
              case Gesture(bodyPart, action) =>
                System.out.println(ct + "(" + action + "s " + bodyPart + ")")
              case ThatsAWrap =>
                System.out.println(ct + "Отличной всем вечеринки! До скорого!")
                done = true
              case _ =>
                System.out.println(ct + "Что? Я буду в своем трейлере.")
            }
          }
        }
      
      System.out.println(ct + "Обсуждение...")
      badActor ! NegotiateNewContract
      System.out.println(ct + "Разговор...")
      badActor ! Speak("Ну как, простофиля, тебе везет?")
      System.out.println(ct + "Жестикуляция...")
      badActor ! Gesture("лицо", "гримассы")
      System.out.println(ct + "Возобновление разговора...")
      badActor ! Speak("Что ж, да?")
      System.out.println(ct + "Завершение")
      badActor ! ThatsAWrap
    }
  }

При рассмотрении этого нового примера становится вполне очевидно, что имеют место два потока:

  • Поток main (тот же, который запускает каждый метод main Java
  • Поток Thread-2, который был запущен библиотекой Scala Actors на более глубоком уровне

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

Однако привыкнуть к этой новой модели выполнения может оказаться немного затруднительно, хотя бы потому, что она являет собой совершенно иной способ видения параллелизма. Для примера рассмотрим модель «производитель/потребитель» из прошлой статьи. Там было представлено изрядное количество кода, в частности, в классе Drop, и у нас сложилась довольно ясная картина происходящего в процессе взаимодействия потоков друг с другом при наличии мониторов, требующих синхронизации всего процесса. Я снова приведу здесь код V3 из прошлой статьи для справки:

Листинг 5. ProdConSample, v3 (Scala)
package com.tedneward.scalaexamples.scala.V3
{
  import concurrent.MailBox
  import concurrent.ops._

  object ProdConSample
  {
    class Drop
    {
      private val m = new MailBox()
      
      private case class Empty()
      private case class Full(x : String)
      
      m send Empty()  // инициализация
      
      def put(msg : String) : Unit =
      {
        m receive
        {
          case Empty() =>
            m send Full(msg)
        }
      }
      
      def take() : String =
      {
        m receive
        {
          case Full(msg) =>
            m send Empty(); msg
        }
      }
    }
  
    def main(args : Array[String]) : Unit =
    {
      // Создание класса Drop
      val drop = new Drop()
      
      // Порождение производителя
      spawn
      {
        val importantInfo : Array[String] = Array(
          "Лошади едят овес",
          "Оленихи едят овес",
          "Ягнята едят плющ",
          "Дети тоже едят плющ"
        );
        
        importantInfo.foreach((msg) => drop.put(msg))
        drop.put("DONE")
      }
      
      // Порождение потребителя
      spawn
      {
        var message = drop.take()
        while (message != "ГОТОВО")
        {
          System.out.format("СООБЩЕНИЕ ПОЛУЧЕНО: %s%n", message)
          message = drop.take()
        }
      }
    }
  }
}

И хотя можно с интересом посмотреть на то, как Scala упростил части этого кода, на самом деле концептуально все это мало отличается от первоначальной версии Java. Но теперь давайте посмотрим, что может представлять собой модель «производитель/потребитель» на основе акторов, если мы упростим ее и оставим только самые существенные элементы:

Листинг 6. Дубль 1. И ... Мотор! Производи! Потребляй!
  object ProdConSample1
  {
    case class Message(msg : String)
    
    def main(args : Array[String]) : Unit =
    {
      val consumer =
        actor
        {
          var done = false
          while (! done)
          {
            receive
            {
              case msg =>
                System.out.println("Полученное сообщение! -> " + msg)
                done = (msg == "ГОТОВО")
            }
          }
        }
      
      consumer ! "Лошади едят овес"
      consumer ! "Оленихи едят овес"
      consumer ! "Ягнята едят плющ"
      consumer ! "Дети тоже едят плющ"
      consumer ! "ГОТОВО"      
    }
  }

Данная первая версия, безусловно, более выигрышна с точки зрения лаконичности, плюс в некоторых ситуациях выполняются все необходимые действия, но выполнение кода и его сравнение с более ранними версиями раскрывают важное отличие — версия на основе акторов представляет собой многоместный буфер обмена, а не однослотовую линию, с которой мы работали прежде. Некоторым это может показаться своего рода усовершенствованием, а не недостатком, но давайте сравним подобное с подобным — подойдем с другой стороны и создадим версию Drop на основе акторов, в которой каждый вызов put() уравновешен вызовом take().

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

Самым простым способом сделать это в библиотеке Scala Actors является использование метода !? вместо метода! (который осуществляет блокировку до получения подтверждения). (Если вам интересно — в реализации Scala Actors каждый Java-поток уже является актором, так что ответ поступает в буфер, который неявным образом связан с потоком main.) Это означает, что потребителю необходимо отправить своего рода подтверждение; он делает это при помощи метода reply, который неявным образом наследует (вместе с методом receive), как показано в листинге 7:

Листинг 7. Дубль 2 ... Мотор!
  object ProdConSample2
  {
    case class Message(msg : String)
    
    def main(args : Array[String]) : Unit =
    {
      val consumer =
        actor
        {
          var done = false
          while (! done)
          {
            receive
            {
              case msg =>
                System.out.println("Полученное сообщение! -> " + msg)
                done = (msg == "ГОТОВО")
                reply("ПОЛУЧЕНО")
            }
          }
        }
      
      System.out.println("Отправка ...")
      consumer !? "Лошади едят овес"
      System.out.println("Отправка ...")
      consumer !? "Оленихи едят овес"
      System.out.println("Отправка ...")
      consumer !? "Ягнята едят плющ"
      System.out.println("Отправка ...")
      consumer !? "Дети тоже едят плющ"
      System.out.println("Отправка ...")
      consumer !? "ГОТОВО"      
    }
  }

Или, если вы предпочитаете версию, использующую spawn для перемещения производителя в отдельный поток из main() (которая наиболее точно отражает оригинал), это могло бы выглядеть, как показано в листинге 8:

Листинг 8. Дубль 4 ... Мотор!
  object ProdConSampleUsingSpawn
  {
    import concurrent.ops._
  
    def main(args : Array[String]) : Unit =
    {
      // Порождение потребителя
      val consumer =
        actor
        {
          var done = false
          while (! done)
          {
            receive
            {
              case msg =>
                System.out.println("ПОЛУЧЕННОЕ СООБЩЕНИЕ: " + msg)
                done = (msg == "ГОТОВО")
                reply("ПОЛУЧЕНО")
            }
          }
        }
    
      // Порождение производителя
      spawn
      {
        val importantInfo : Array[String] = Array(
         "Лошади едят овес",
          "Оленихи едят овес",
          "Ягнята едят плющ",
          "Дети тоже едят плющ",
          "ГОТОВО"
        );
        
        importantInfo.foreach((msg) => consumer !? msg)
      }
    }
  }

С какой бы стороны вы ни взглянули, версия на основе акторов намного проще оригинальной… поскольку читатель может открыто хранить акторы и неявные буферы.

Это нетривиальный аспект. Модель акторов серьезно меняет все представление о параллелизме и безопасности потоков; оно меняется от модели, где мы фокусируем внимание на совместно используемых структурах данных (параллельном доступе к данным), к модели, где внимание фокусируется на структуре самого кода, работающего с данными (параллелизме задач) и разделяющего минимально возможное количество данных. Обратите на это внимание в примере «производитель/потребитель» в предыдущем коде. В рассматриваемых ранее примерах параллелизм был явным образом прописан вокруг класса Drop (ограниченный буфер). В данной версии, описываемой в настоящей статье, Drop даже и не появляется, и все внимание сконцентрировано на двух акторах (потоках) и их взаимодействии с использованием сообщений без разделения ресурсов.

Разумеется, использование акторов также позволяет строить конструкции, нацеленные на параллельную обработку данных; для этого необходимо лишь применить несколько иной подход. Рассмотрите простой объект «счетчика», использующий сообщения акторов для передачи операций «приращение» и «получение», как показано в листинге 9:

Листинг 9. Дубль 5 ... Считай!
 object CountingSample
  {
    case class Incr
    case class Value(sender : Actor)
    case class Lock(sender : Actor)
    case class UnLock(value : Int)
  
    class Counter extends Actor
    {
      override def act(): Unit = loop(0)
 
      def loop(value: int): Unit = {
        receive {
          case Incr()   => loop(value + 1)
          case Value(a) => a ! value; loop(value)
          case Lock(a)  => a ! value
                           receive { case UnLock(v) => loop(v) }
          case _        => loop(value)
        }
      }
    }
    
    def main(args : Array[String]) : Unit =
    {
      val counter = new Counter
      counter.start()
      counter ! Incr()
      counter ! Incr()
      counter ! Incr()
      counter ! Value(self)
      receive { case cvalue => Console.println(cvalue) }    
      counter ! Incr()
      counter ! Incr()
      counter ! Value(self)
      receive { case cvalue => Console.println(cvalue) }    
    }
  }

Или, для сохранения преемственности с примером «производитель/потребитель», ниже приводится листинг 10, представляющий собой версию Drop, использующую акторы внутренним образом (возможно, с целью позволить другим классам Java использовать Drop не заботясь о том, как осуществлять прямой вызов методов акторов):

Листинг 10. Drop, встречайте акторов
  object ActorDropSample
  {
    class Drop
    {
      private case class Put(x: String)
      private case object Take
      private case object Stop
  
      private val buffer =
        actor
        {
          var data = ""
          loop
          {
            react
            {
              case Put(x) if data == "" =>
                data = x; reply()
              case Take if data != "" =>
                val r = data; data = ""; reply(r)
              case Stop =>
                reply(); exit("остановлено")
            }
          }
        }
  
      def put(x: String) { buffer !? Put(x) }
      def take() : String = (buffer !? Take).asInstanceOf[String]
      def stop() { buffer !? Stop }
    }
    
    def main(args : Array[String]) : Unit =
    {
      import concurrent.ops._
    
      // Создание класса Drop

      val drop = new Drop()
      
      // Порождение производителя
      spawn
      {
        val importantInfo : Array[String] = Array(
         "Лошади едят овес",
          "Оленихи едят овес",
          "Ягнята едят плющ",
          "Дети тоже едят плющ"
        );
        
        importantInfo.foreach((msg) => { drop.put(msg) })
        drop.put("DONE")
      }
      
      // Порождение потребителя
      spawn
      {
        var message = drop.take()
        while (message != "DONE")
        {
          System.out.format("СООБЩЕНИЕ ПОЛУЧЕНО: %s%n", message)
          message = drop.take()
        }
        drop.stop()
      }
    }
  }

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

Еще об акторах.

В случаях, подобных работе внутри систем с высокой степенью масштабирования, подкрепление каждого актора Java-потоком будет сложной и затратной задачей, особенно если каждый актор будет тратить большую часть своего времени на ожидание, а не на обработку. В подобной ситуации подходящим решением может являться использование актора на основе событий; он эффективно размещается внутри замыкания, которое охватывает остальные действия актора. Иначе говоря, это блок кода (функция), который в данной ситуации не требует представления посредством состояния потока, регистров и т.п. Замыкание запускается, как только к актору поступает какое-либо сообщение (для этого, очевидно, необходим активный поток). Следовательно, замыкание принимает активный поток на период его активности, а затем либо завершает работу, либо снова переходит в состояние «ожидания», осуществляя обратный вызов самого себя и тем самым позволяя эффективно освободить поток для использования в других целях. (См. работы Холлера/Одерски (Haller/Odersky) в разделе Ресурсы.)

В рамках библиотеки Scala Actors указанные операции выполняются с помощью метода react вместо receive, как я уже показывал в данной статье. Ключом к использованию метода react является то, что формально react возвращаться не может, поэтому реализация внутри react требует повторного вызова блока кода, содержащего блок react. Вот здесь-то и пригодится конструкция loop, создающая почти бесконечный цикл (как видно из названия). Это означает, что реализация Drop в листинге 10 фактически может работать, просто заимствуя потоки вызывающей программы и тем самым сокращая количество потоков, необходимых для выполнения всех требуемых операций. (На практике я никогда не наблюдал подобного на тривиальных примерах, поэтому, как мне кажется, нам придется поверить разработчикам Scala на слово.)

В некоторых случаях можно выбрать наследование из основного признака Actor (в такой ситуации необходимо определить метод act, или класс останется абстрактным) с целью создания нового класса, который будет неявным образом действовать в качестве актора. С другой стороны, эта идея не находит одобрения в сообществе Scala; в целом обрисованный мной подход (с использованием метода actor из объекта Actor) — это предпочтительный способ создания нового актора.

Заключение

Поскольку программирование с использованием акторов требует несколько иного стиля, чем программирование с использованием «традиционных» объектов, при работе с акторами следует помнить о некоторых аспектах.

Во-первых, помните, что основные возможности акторов вытекают из их стиля, предполагающего передачу сообщений, в отличие от стиля блокировки-вызова, характерного для остального мира императивного программирования. (Примечательно то, что объектно-ориентированные языки, использующие процесс обмена сообщениями в качестве основного принципа, здесь не используются. Двумя из наиболее популярных подобных языков являются Objective-C и Smalltalk; в этой «компании» есть еще один новичок, Ioke, созданный коллегой из ThoughtWorker Олой Бини (Ola Bini).) При создании классов, прямо либо косвенно расширяющих Actor, следите за тем, чтобы все вызовы указанных объектов осуществлялись путем обмена сообщениями.

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

  • Код становится более простым для понимания (поскольку сообщение будет содержать всю информацию о состоянии, необходимую для обработки)
  • Снижается вероятность того, что актор будет обращаться к совместно используемому состоянию где-то еще, таким образом, уменьшается вероятность возникновения взаимоблокировки и прочих «ужасов» параллелизма

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

Любопытно, что если вы знакомы с API Java Message Service (JMS), то обнаружите много знакомого в моих рекомендациях: в конце концов, стиль акторов, предусматривающий передачу сообщений — это, как и передача сообщений JMS, просто передача сообщений между объектами. Разница состоит в том, что сообщения JMS являются более масштабными и работают на уровне ярусов и процессов, а сообщения акторов имеют меньшие масштабы и функционируют на уровне объектов и потоков. Если вы разобрались с JMS, вам покорятся и акторы.

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

Для этой части достаточно. До скорой встречи и всего вам наилучшего!


Ресурсы для скачивания


Похожие темы

  • Оригинал статьи: The busy Java developer's guide to Scala: Dive deeper into Scala concurrency.
  • The busy Java developer's guide to Scala" («Руководство по работе с языком Scala для занятых разработчиков Java-приложений») (Тед Ньюард (Ted Neward), developerWorks): прочтите всю серию статей.
  • В статье "Event-Based Programming without Inversion of Control" («Программирование на основе событий без инверсии управления») (Федеральная политехническая школа Лозанны (Ecole Polytechnique Federale de Lausanne), авторы Филипп Холлер (Philipp Haller) и Мартин Одерски (Martin Odersky)) объясняется, почему так необходим параллелизм, и освещаются принципы, лежащие в основе библиотеки акторов Scala. В другой работе этих авторов "Actors That Unify Events and Threads," («Акторы, унифицирующие события и потоки») вашему вниманию предлагается более развернутая информация о том, как реализация акторов Scala скрывает разницу между потоковыми акторами и акторами на основе событий.
  • В книге Java Concurrency in PracticeПараллелизм Java на практике») (Addison-Wesley Professional, 2006 год) раскрываются как основы, так и темы продвинутого уровня, касающиеся параллелизма.
  • В книге Concurrent Programming in JavaПараллельное программирование на языке Java/») (Prentice Hall PTR, 1999 год) рассматривается широкий спектр вопросов параллелизма и взаимосовместимости и показывается, как можно добиться большего с помощью многопоточности в Java, с десятками шаблонов и рекомендаций по разработке.
  • "Functional programming in the Java language" («Функциональное программирование на языке Java») (Абхиджит Белапуркар (Abhijit Belapurkar), developerWorks, июль 2004 года): в этой статье раскрываются преимущества и области применения функционального программирования с точки зрения разработчика Java. .
  • "Scala by Example" («Scala в примерах») (Мартин Одерски (Martin Odersky), декабрь 2007 года): краткое введение в Scala с демонстрацией образцов кода (в формате PDF).
  • Programming in ScalaПрограммирование на языке Scala») (Мартин Одерски (Martin Odersky), Лекс Спун (Lex Spoon) и Билл Веннерс (Bill Venners); Artima, декабрь 2007 года): первая книга с вводной информацией о языке Scala.
  • Бьерн Страуструп (Bjarne Stroustrup): разработал и реализовал язык C++, который он описывает как «улучшенный C».
  • В книге Java Puzzlers: Traps, Pitfalls, and Corner CasesГоловоломки Java: ловушки, подвохи и скрытые камни преткновения») (Addison-Wesley Professional, июль 2005 года) особенности языка программирования Java раскрываются с помощью развлекательных и активизирующих мышление головоломок программирования.
  • Загрузите Scala: начните изучение с прочтения данной серии статей!
  • SUnit: часть стандартного дистрибутива Scala в пакете scala.testing.
static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Технология Java
ArticleID=853344
ArticleTitle=Руководство по работе с языком Scala для занятых разработчиков Java-приложений: Узнайте больше о параллелизме Scala
publish-date=12242012