Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

IdBasedLocking

Anna | 2.06.2014 | нет комментариев

У Java хорошая помощь параллелизма (concurrency) и блокировки (locking) — допустимо, самая лучшая из тех, что предлагают современные языки. Помимо того, что в самом языке есть встроенная помощь синхронизации, существует целый ряд пригодных утилит на основе AQS framework. К ним относятся CountDownLatches, Barriers, Semaphores и прочие. Впрочем Зачастую встречается обстановка, не поддерживающаяся напрямую: когда нужно блокировать доступ не к определенному объекту, а к идее этого объекта.

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

   public void sendMessage(UserId from, UserId to, Message message) { 
        Mailbox sendersBox = getMailbox(from);
        Mailbox recipientsBox = getMailbox(to);
        min(sendersBox,recipientsBox).lock();
        max(sendersBox, recipientsBox).lock();
        sendersBox.addIncomingMessage(message);
        recipientsBox.addSentMessage(message);
        max(sendersBox, recipientsBox).lock();
        min(sendersBox,recipientsBox).lock();
    }

Код исходит из того, что для мэйлбокса у нас есть функция мин/макс, которая неизменно даёт однозначный итог: скажем, сопоставляет хэш-код, либо id владельца, либо что-то ещё. Это необходимо для того, Дабы при модификации 2-х ящиков неизменно ставить блокировку в идентичном порядке (вначале меньшую) и не допускать deadlock. Принимая это во внимание, код выглядит касательно безвредным, не так ли?

На самом деле, безопасность этого кода зависит от того, как, собственно, реализован способ getMailbox(). Если он может гарантировать возвращение одного и того же объекта (причем именно «того же», а не «такого же»), то мы в безопасности. К сожалению, такую гарантию фактически немыслимо реализовать. С иной стороны, мы, безусловно, могли бы поставить synchronized на каждый способ sendMessage(), но это убило бы нашу продуктивность на корню, так как мы могли бы единовременно отправлять только одно сообщение, несмотря на число потоков и процессоров в нашем распоряжении.

Вот пример того, как дозволено ненормально реализовать getMailbox():

  private Mailbox getMailbox(UserId id) {
        Mailbox mailbox = cache.get(id);
        if (mailbox == null) {
            mailbox = new Mailbox();
            cache.put(id, mailbox);
        }
        return mailbox;
    }

Видимо, что эта реализация небезопасна: она допускает создание одного и того же мэйлбокса по сути (то есть принадлежащего одному и тому же юзеру) из различных потоков единовременно. Это значит, что в какой-то момент в системе могут появится два различных ящика одного пользователя, и только Ктулху будет знать, какой из них выживет. Дозволено решить эту задачу, заблокировав создание нового мэйлбокса глобально, но это, вновь же, грабли с эффективностью. Дозволено начать развлекаться с ConcurrentMap и любыми там putIfAbsent. Но представьте, что мы не только создаём новые почтовые ящики, но и грузим существующие из базы данных. Тогда это будет значительно труднее верно синхронизировать (да и недопустить лишние запросы в базу было бы хорошо).

К счастью, IdBasedLocking решает именно эту задачу. Взамен блокирования определенного объекта“мэйлбокс”, мы блокируем концепт мэйлбокса, исходя из того, что у нас по одному ящику на пользователя:

    IdBasedLockManager<UserId> manager = new SafeIdBasedLockManager<UserId>();

    public void sendMessageSafe(UserId from, UserId to, Message message) {

        IdBasedLock<UserId> minLock = manager.obtainLock(min(from, to));
        IdBasedLock<UserId> maxLock = manager.obtainLock(max(from, to));
        try {
            minLock.lock();
            maxLock.lock();

            Mailbox sendersBox = getMailbox(from);
            Mailbox recipientsBox = getMailbox(to);
            sendersBox.addIncomingMessage(message);
            recipientsBox.addSentMessage(message);
        } finally {
            maxLock.unlock();
            minLock.unlock();

        }
    }

Пример может показаться неестественно усложненным из-за одновременной модификации 2-х объектов. IdBasedLocking отменно работает и с одним объектом, как показывает дальнейший пример счетчика.

Вначале — поломанный вариант:

   public void increaseCounterUnsafe(String id) {
        Counter c = counterCache.get(id);
        if (c == null) {
            c = new Counter();
            counterCache.put(id, c);
        }
        c.increase();
    }

А сейчас неопасная версия:

   public void increaseCounterSafely(String id) {
        IdBasedLock<String> lock = lockManager.obtainLock(id);
        lock.lock();
        try{
            Counter c = counterCache.get(id);
            if (c == null) {
                c = new Counter();
                counterCache.put(id, c);
            }
            c.increase();

        } finally {
            lock.unlock();
        }
    }

IdBasedLocking план на гитхабе.

Взять попользоваться дозволено maven-ом, gradle либо ivy, либо легко из central. Либо пробилдить на гитхабесамому.

Спасибо за внимание.

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB