Использование ограниченных ресурсов

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

Из-за того, что это единственный объект, вам никогда не придется думать о проблеме использования одного и того же ресурса разными объектами в одно и то же время, подобно тому, как два человека пытаются припарковаться в одном и том же месте, или пройти через дверь в одно и то же время, или даже говорить в одно и то же время.

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

Неправильный доступ к ресурсам

//: c13:AlwaysEven.java

// Demonstrating thread collision over resources by

// reading an object in an unstable intermediate state.

 

class Watcher extends Thread {

        AlwaysEven ae;

        public Watcher(AlwaysEven x) {

                ae = x; start();

        }

        public void run() {

                        while(true) {

                        int val = ae.getValue();

                        if(val % 2 != 0) {

                                        System.out.println(val);

                                        System.exit(0);

                        }

                        }

        }

}

public class AlwaysEven {

          private int i = 0;

          public void next() { i++;     i++; }

          public int getValue() {  return i; }

         

          public static void main(String[] args) {

                    final AlwaysEven ae = new AlwaysEven();

                    new Watcher(ae);

                    while(true)

                               ae.next();

          }

} ///:~

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

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

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

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

Предотвращение подобных коллизий решается просто установкой блокировки на ресурс в момент использования.

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

Например, если переднее сиденье в машине представить как ограниченный ресурс, то ребенок с криком "Dibs" может занять это место.

 

Как Java получает доступ к ресурсам

В Java есть встроенная поддержка предотвращения коллизий при использовании одного типа ресурсов - объектов в памяти.

 

Поскольку элементы данных класса объявляются как private и доступ к этой области памяти возможен только посредством методов, то можно избежать коллизий объявив эти методы как synchronized.

 

Одновременно только один процесс может вызвать synchronized метод для определенного объекта (хотя этот процесс может вызывать более одного синхронизированного метода объекта).

 

Ниже приведены простые synchronized методы:

 

      synchronized void f() { /* ... */ }

      synchronized void g(){ /* ... */ }


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

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

В выше приведенном примере, если f() вызвана для объекта, то g() не может быть вызвана для того же объекта до тех, пока f() не завершится и не снимет блокировку.

Таким образом, это единственная (в смысле одна - Прим. пер.) блокировка, используемая всеми synchronized методами отдельного объекта и эта блокировка предотвращает возможность записи в память более чем одному методу в одно и тоже время (т.е. более одного процесса в одно и то же время).

Запомните, если вы хотите защитить какие-либо ресурсы от одновременного доступа со стороны нескольких процессов, можно сделать это разрешив доступ к этому ресурсу только через synchronized методы.

MUTEX (mutual exclusion, взаимное исключение)

 

Эффективность синхронизации

 

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

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

Таким образом, если вы знаете, что данный метод не вызовет проблем в доступе к ресурсам уместнее избегать ключевого слова synchronized.

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

 

 

LÕIME OLEKUSKEEM

 

 

LÕIMEDE BLOKEERUMINE JA DEBLOKEERUMINE

Blokeerumise põhjus

      Deblokeerumine

 

Sisend-väljund operatsiooni algus

·         Operatsioon on lõppenud

·         IOException

sleep( millisekundeid )

wait( millisekundeid )

  • Timeout
  • InterruptedException

wait()

  • notify(), notifyAll()
  • InterruptedException

Sünkroniseeritud objekt on „lukus”

      Objekt vabaneb lukustusest

 

join()

  • Lõim, kellega join-iti, läheb olekusse Dead
  • InterruptedException

 

Ожидание и уведомление

Очень важно понять, как sleep(), так и suspend()  не освобождают блокировку во время своего вызова.  Вы должны знать об этом когда работает с блокировками.

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

Существуют две формы wait().

Первая принимает аргумент в миллисекундах, что имеет то же значение как и в sleep(): остановку на это время. Различие в том, что в wait() блокировка объекта освобождается и вы можете выйти из wait() с помощью notify() так же как и после истечения времени.

Вторая форма без передачи параметров означает, что wait() будет выполняться до тех пор пока не будет вызвано notify() и не остановится автоматически по истечению времени.

Один, довольно уникальный аспект wait( ) и notify( ) в том, что оба метода являются частью базового класса Object, а не частью Thread, как sleep( ), suspend( ) и resume( ).

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

В результате можно поместить wait() в любой syncronized метод, в зависимости от того, будет ли какой-либо процесс выполнять именно данный класс.

Фактически, единственное применение для wait() быть вызванным из synchronized метода или блокировки.

Если вызвать wait() или notify() в необъявленном как synchronuzed методе, то программа будет прекрасно компилироваться, но когда вы ее запустите, то получите IllegalMonitorStateException с каким-то не сразу понятным сообщением "current thread not owner" (текущий процесс не владелец).

Запомните, что sleep(), suspend() и resume() могут быть вызваны из не-syncronized методов, поскольку они не управляют блокировкой.

Вы можете вызвать wait() или notify() только для вашей собственной блокировки. Еще раз, вы сможете скомпилировать код, который пытается использовать неверную блокировку, но это приведет вас к тому самому IllegalMonitorStateException сообщению как и прежде.

Также ни чего не получиться с чужой блокировкой, но можно попросить другой объект выполнить операцию с его собственной блокировкой. Таким образом одна из попыток заключается в создании syncronized метода, который вызывает notify() для своего собственного объекта.

Однако возможен вызов notify() из syncronized блока:

synchronized(wn2) {
  wn2.notify();
}

 

 Этот  метод имеет блокировку на объект wn2 и с этого момента он совершенно спокойно может вызвать notify() для wn2 и не получить IllegalMonitorStateException.

Упражнение.  Задача о читателях-писателях

 

Семафор

 

 

Мертвая блокировка  (deadlock)

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

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

Можно утверждать, что это не происходит слишком часто, но когда это произойдет с вашим кодом, то будет очень сложно обнаружить ошибку.

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

 

Проблема обедающих философов