субота, 27 серпня 2011 р.

Lock та Condition

Минулого разу я розглянув Atomic-типи та методику їх застосування. Сьогодні я продовжу огляд класів з пакету java.util.concurrent. Отже Lock та Condition.

У JDK 7 три реалізації Lock’ів, але нас цікавить лише ReentrantLock, тому далі мова йде саме про цей клас. Інші два використовуються вкупі з ReadWriteLock, і з точки зору можливостей не дуже цікаві. ReadWriteLock буде темою однієї з наступних статей.

Чим корисні високорівневі локи?
Як відомо у Java кожен об’єкт є простеньким монітором. Більш точно Java-монітор є простим монітором Хоара з однією внутрішньою умовною змінною. З іншого боку - клас ReentrantLock, який виконує ту саму функцію. Я спочатку задавався питанням, навіщо вводити ще й високорівневу конструкцію, яка робить те саме, адже завжди можна ввести змінну типу Object і використати її для синхронізації. 

Але сенс у тому є - перевагаю локів є можливість умовного блокування tryLock(), можливість будувати повноцінні монітори з багатьма множинами очікування використовуючи інтерфейс Condition та багато інших зручностей.

По суті сам по собі лок - це аналог критичної секції, synchronized-блоку. Цього в принципі може вистачити, але в простих випадках краще вже користуватись блоками synchronized. Основний профіт високорівневих локів - у використання їх як моніторів, і для цього потрібно створити одну чи кілька умовних змінних.  

Крім методів диктованих інтерфейсом Lock, у класі є методи для отримання інформацїї про стан локу (кількість входів, кількість потоків у чергах для кожної умовної змінної, блокуючий потік, чесність тощо).


Умовні змінні
Для створення умовних змінних призначений метод newCondition. Умовні змінні призначенні для координації потоків по певній умови - звідси й назва (англ. conditional variables), тому гарною практикою є вибір імені змінної, що прямо пов’язує її з відповідною умовою. Для прикладу у коді ArrayBlockingQueue (Oracle JDK 7), умовні змінні називаються notEmpty та notFull для сповіщення про стан, коли внутрішній буфер відповідно має об’єкт у черзі на читання та має вільне місце на прийом нових об’єктів   

/** Main lock guarding all access */
final ReentrantLock lock;
/** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts */
private final Condition notFull;

Умовні змінні таким чином інкапсулють певну умову, що використовуюється для координації потоків. ArrayBlockingQueue є прикладом повноцінного монітора Хоара.  


Умовні змнні (інтерфейс Condition) мають методи, що повинні вам бути знайомі: await() - аналог Object#wait(), signal() та signalAll() - аналоги Object#notify() та Object#notifyAll(), але на відміну від останніх signal() та signalAll() мають послаблені вимоги до стану лока - реалізація може не вимагати захоплення монітора. Однак внутрішня реалізація все таки вимгає, щоб визиваючий код містився у критичній секції.

Крім того є три аналоги Object#wait(long):
  • Condition#await(long, TimeUnit) - потік чекатиме синхронізації вказану кількість одиниць часу у TimeUnit’ах
  • Condition#awaitNanos(long) - потік чекатиме синхронізації вказаний час у наносекундах 
  • Сondition#awaitUntil(Date) - потік чекатиме синхронізації до вказаної дати
Три забагато, бо TimeUnit підтримує наносекунди.
TimeUnit - взагалі дуже зручний утилітний клас для роботи з одиницями часу навіть у звичайних програмах. Дозволяє уникати магічних часових констант типу 24, 60, 3600 (інші рахувати впадло). Натомість пропонується наступне:
package part2;

import java.util.concurrent.TimeUnit;

public class TimeUnitDemo {

    public static void main(String[] args) throws InterruptedException {
        // без TimeUnit
        Thread.sleep(10*24*3600*1000);
        
        // з TimeUnit
        Thread.sleep(TimeUnit.DAYS.toMillis(10));
        
        // ще краще ;)
        TimeUnit.DAYS.sleep(10);
    }
}


І справжнє полегшення, - метод Condition#awaitUninterruptibly() - дозволяє відкласти обробку переривання потоку - виклику Thread#interrupt(), - і виконати її після завершення цього методу. Решта методів негайно кидають InterruptedException.  Готувати так:
 
package part2;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class UninterruptiblyWaitForTheEnd {

    public static void main(String[] args) {
        final Lock lock = new ReentrantLock();
        final Condition doomsDay = lock.newCondition();

        // Запускаєм счьотчік
        new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                try {
                    TimeUnit.DAYS.sleep(10);
                    doomsDay.signal();
                } catch (InterruptedException e) {
                    // піздєц отмєняєтся по тєхніческім прічінам
                } finally {
                    lock.unlock();
                }
            }
        }).start();

        // Хоч від JVM взагалі нема гарантій, 
        // що до цього моменту 10 секунд не вийшло 
        // і не наступив піздєц,
        // ми - наївні пацани, - сподіваємось, що це не так
        boolean theEndHasCame;
        lock.lock();
        try {
            do {
                doomsDay.awaitUninterruptibly();

                if (theEndHasCame = hasTheEndCome()) {
                    // написати у Твіттер, що настав кінець світу
                    System.exit(0); // місію виконано
                }

                if (Thread.interrupted()) {
                    // далі йде обробка виклику interrupt() 
                    // з матюками у лог на тему яка у нас важлива місія
                }
            } while (!theEndHasCame);
        } finally {
            lock.unlock();
        }
    }

    public static boolean hasTheEndCome() {
        // На жаль http://www.doomsdayclock.org/ не має REST-інтерфейсу
        return true;
    }
}


Взагалі тут варто звернути увагу на те, що звільнення локу винесено в блок finally - це по-перше. По-друге, я не забув про spurious wakeups, по-третє - пам’ятайте що Thread#interrupted() скидує стан переривання потоку. 


Чи можна користуватись новим блоком try?
На жаль у JDK 7 локи не можуть бути використані як ресурси нового блоку try і автоматично звільнені не будуть. Можна це обійти, написавши невелику обгортку чи просто наслідувати ReentrantLock для підтримки AutoCloseable, але в питаннях паралельних я не бачу в цьому нічого хорошого. Робити так чи ні - вирішіть самі.

Три слова про чесність (fairness)
Остання важлива особливість локів - це можливість створювати їх відносно чесними. Конструктор ReentrantLock'а має необов'язковий параметр fairness. Грубо кажучи, чесні локи дозволяють вхід до критичної секції потокам в порядку викликів lock(), більш точно - завжди перевага надається потоку який довше чекає. Звичайні локи не дають таких гарантій і за рахунок цього вони трохи ефективніші. Відносно чесними такі локи є тому, що операційна система не зобов'язана чесно розподіляти процесор між потоками - це залежить від ОС та параметрів ядра. Непогане дослідження з цифрами можна знайти у статті The fairness of java.util.concurrent.locks.ReentrantLock

* * *

У мене все про локи та умовні змінні, хоч кілька цікавих методів не висвітлено. Загалом ReentrantLock повинен задовольнити будь-які питання роботи з довільними техніками синхронізації. На черзі семафори та ReadWriteLock.


До зустрічі!

пʼятниця, 26 серпня 2011 р.

2000 pageviews! Yikes!


It's fantastic, because i don't know who was 2000th, it might have been Yandex.
It's not much, but as lazy as I am, I didn't hoped to make this many. As happy as I am now I just felt I should write something about this.

Time to review stats!
It was quite some time since I have started this blog, less than a year, and I still have some crowd to be proud: http://mantonov.blogspot.com/ and http://arhipov.blogspot.com/. Thanks, I'll continue even if it's just for you two!

Also special thanks to http://javenue.info/ who was just the right guy to pass SCJP with!

It was a coincidence but a good timing to switch to new domain, which looks much more human than previous. I still haven't paid much attention to SEO and design since start so this is a good thing to do in the next year.

New part of java.util.concurrent is being prepared with me googling every tiny detail about why and how, stay tuned, but you may be disappointed as I started it in Ukrainian. That's a try and I plan to translate it to Russian once done. I wish I had an app that'd write as I talk with some acceptable rate of errors, if you know any - please give me a shout!

Also I'd be thankful to anybody who suggests a good idea of the study-friendly but realistic app to illustrate the concurrency concepts. I have some ideas, but yours may be more exciting!

I was excited to know that our Institut of Cybernetics has a research on Ukrainian language recognition: http://uasoiro.org.ua/home/ It's pretty scary but you can navigates through frames - it looks quite promising. I hope to contact them next week, maybe they will be kind enough to open-source it, so I could port it on Mac and make pretty

Thats's it, gratz everyone and don't routine yourself! Ever!

пʼятниця, 12 серпня 2011 р.

Hucking Bot for Assembla

If you know Assembla, you may think it's good. I think it's good too, but its Chat tool totally sucks. It is one piece of that crap, you wonder about why it was born to this world at all - when the stuff like XMPP was available for years already. Their amazing efforts to clone Skype and GTalk functionality and frame it into the web page would deserve respect if it would at least just work. I heard they moved off XMPP in the past, I would like to know the reason why, because OpenTok they use now doesn't allow to users to connect, which means I can't connect to Assembla with iChat or anything else.

Chat has two convenient features though: it can be setup to hub every event on Assembla's space, and it is searchable. So once Dave (The Hacker at @buildabrand) discovered it was good to store conversations for sake of future generations, we had this pain of switching from Skype to Assembla and constant paste-and-copy from here to there.

It's about that time I had an idea of a bot to hang on a conversation and log everything from Skype to Assembla, and benefit from this just like IRC users do.

Today I've discovered another nice Assembla tool, called Webhook, which allows you to POST event notifications from space to any URL you want. This is the way out of the space! So I fired the IDE, got the project setup and started hacking the fucking Hucking Bot!

After few hours I had something... it's a mess... it can talk to me only... it's not configurable... and it runs in screen on an old laptop in a place far far away..., but it knows me, knows how to accept events from Assembla and send them to my GTalk account, and it knows how to work with Base64, just because I had a lot of encoding/decoding in the last weeks, and just can't stand the tools available on the web.

The picture is worth thousand words:




For XMPP I have used excellent Smack API, provided by IgniteRealtime, despite that artifacts from Maven were outdated a bit.

What else? Yay! Skype support coming soon!

UPD: it seems Skype has some very restrictive licensing for their SkypeKit product. Sad!

вівторок, 9 серпня 2011 р.

Atomic-типы

Давным-давно я обещал написать обзорный цикл по java.util.concurrent.*, да как-то и застряло. Встречайте... Atomic-типы!

Как известно, операции над переменными нельзя считать атомарными, если только это не присвоение примитивных типов (и то - с оговорками на разрядность).
Почему так, можно понять, например, изучив байт-код операций (см. например Как выглядит счетчик в байт-коде?).
Для упрощения программы в случае, когда синхронизация потоков осуществляется на основе значения некоторой переменной, например, счетчика, были добавлены типы из пакета java.util.concurrent.atomic.*, а именно:

AtomicInteger (тут все ясно)
AtomicLong (тут тоже)
AtomicBoolean (яснее некуда)
AtomicReference (атомная ссылка на экземпляр чего-либо, не имеет отношения к java.lang.ref.Reference)

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

Несмотря на отсутствие общего родителя (Serializable не в счет), у всех есть несколько общих методов, которые выполняются атомарно, а именно:

compareAndSet - сравнивает хранимое значение с указанным и если они совпалают, -устанавливает новое значеие

weakCompareAndSet - хитрый метод, похож по семантике на compareAndSet, но со странной оговоркой "May fail spuriously". О нем ниже.

getAndSet - изменяет текущее значение, и возвращает старое

get и set - соответственно, атомарные чтение и запись значений, по семантике аналогичны чтению и записи в volatile-переменную

lazySet - тоже весьма интересный метод, обещает когда-нибудь да установить значение в памяти.
Несмотря на описание в javadoc, что метод имеет семантику volatile-записи, он больше похож как раз на запись в не-volatile переменную. Другие потоки могут не сразу увидеть изменения - пока не наступит синхронизирующее событие (например, volatile-запись или выход из блока synchronized). Ниша этого метода - обнуление ссылок на обьекты, когда другие потоки не зависят от быстрого обновления ссылки, но вы хотите чтобы GC собрал обьект. В таком случае, эффективнее использовать lazySet(null), вместо set(null). Есть намеки на другие применения, но разбираться мы с этим будем как нибудь в другой раз.

У AtomicInteger и AtomicLong есть следующие пары методов, назначение которых вполне понятно из названий:

incrementAndGet  - аналог ++i
getAndIncrement - аналог i++
decrementAndGet и getAndDecrement - соответственно --i и i++
addAndGet и getAndAdd - атомарная аккумуляция, разница, что делается в первую очередь - определение, что возвращать, или изменение значение.

В целом, в методах xxxAndGet возвращается измененное значение, в методах getAndXxx - изначальное.

AtomicReference ничем не примечателен, кроме того, что параметризирован.

Наша теперь задача - понять, как это помогает потокам сохранить свою живость в жестоком многопоточном мире.

Атомарные типы все построены вокруг хитрой операции процессоров Intel и бывшей Sun, которая называется compare-and-swap. Операция берет три аргумента - адрес памяти, ожидаемое значение и новое значение, - сравнивет содержимое памяти по адресу с ожидаемым значением и если оны равны - записывает в память новое значение. Операция атомарна. Впервые такая операция появилась в мейнфреймах IBM/370, с помощью CAS можно реализовать синхронизацию без использования блокировок по следующей схеме - процессор читает некое значение в памяти, выполняет над ним действие в собственных регистрах, и выполняет CAS в ту же ячейнку, ожидая там старое значение. Если проверка закончилась неудачно, данные читаются снова, снова выполняется операция, и опять пробуется CAS. Таким образом операция всегда происходит над свежими данными и соблюдается свойство атомарности.

Метод compareAndSet есть мостиком к этой инструкции, а изучив исходный код JDK, мы увидим те самые циклы, например (заметьте - блокировки не используются):

    /**
     * Atomically increments by one the current value.
     *
     * @return the updated value
     */
    public final int incrementAndGet() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return next;
        }
    }

Именно таким образом следует работать с Atomic типами, если вы хотите определить свои операции над ними, например, деление. Кстати, в отличие от обычных оберточных типов, атомики не помечены как final, что как бы намекает...


Чем отличается compareAndSet от weakCompareAndSet?

Суть в том, что на архитектурах MIPS, ARM, PowerPC и Alpha используется другой подход к реализации неблокирующей синхронизации, известный как Load-link/Store-Conditional. Операция подобная CAS на таких процессорах выполняется с помощью цикла из двух инструкций загрузки LL и записи SC, подобно тому, как это было показано выше. По различным обстоятельствам, которые могут произойти между двумя инструкциями, запись может завершится с ошибкой. Некоторые алгоритмы не требуют такого цикла и могут быть выполнены эффективнее, даже если допустить спонтанные ошибки записи.
К сожалению, в исходниках JDK weakCompareAndSet реализован точно так же как и compareAndSet, однако подразумевается, что на некоторых архитектурах процессоров этот метод может работать быстрее, хотя и допускает непредсказуемые ошибки записи. Я попробую разобраться в теме в одной из следующих статей цикла.

На этом все, надеюсь вам было так же интересно, как и мне.


Эти ссылки были мне особенно полезны:

http://cs.oswego.edu/pipermail/concurrency-interest/2006-February/002276.html

http://cs.oswego.edu/pipermail/concurrency-interest/2006-May/002501.html

http://stackoverflow.com/questions/1468007/atomicinteger-lazyset-and-set

понеділок, 8 серпня 2011 р.

Jackson Pretty Printing

While playing with Jackson, I've found it's rather difficult to find a clue about how to provide visually attractive and human-readable JSON output.

Apparently it was rather simple to do, but not obvious. Here is the code (Colour is just an example POJO):

        
        Colour c = new Colour(22, 147, 165);
        ObjectMapper jsonMapper = new ObjectMapper();
        jsonMapper.configure(
              SerializationConfig.Feature.INDENT_OUTPUT, true);
        String dutchTeal = jsonMapper.writeValueAsString(c);

Полгода писал, чесслово

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

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

Не-Миф 1. Сертификаты нафик никому не нужны, кроме вас. 
Как не прискорбно, это действительно так. Пожалуй, единственное, что осталось в течении полугода - это уверенные знания и прокачанное ЧСВ (я все-таки это сделал! и сделал достаточно неплохо!).

Забавно, но как раз за пару дней до сдачи я менял Люксофт на Когнианс. Ясное дело, я всячески пытался подчеркнуть, что я вот-вот стану Сертифицированным. И знаете что? Ничего. И знаете что? А мне плевать, потому что я это сделал и я сделал это хорошо, и я понял, что это хорошо и стал перманентно доволен, тем, что я это сделал, и сделал это хорошо. Ммммм! :)

С другой стороны сказать, что сертификаты нужны только вам, тоже нельзя. Как ни крути, "сертификат" хочет каждый. Каждый человек стремится к признанию своей компетентности и в той или иной форме его получает. Сертификаты незаинтересованных (лично в вас персонально, интерес у них в этом есть) Oracle/IBM/Cisco/... - один из способов получить, чтото похожее на такое признание.

Не-Миф 2. После сдачи вы не станете профессионалом.
Я не ожидал, что у меня это сработает, и рок-звезду я из себя не строил. И это не сработало. Для меня не секрет, что такое украинские титулы в программировании, но все равно соприкосновение с реальностью происходит болезненно. О моем полугоде в Когниансе напишу, когда уволят, а сейчас просто скажу: профессионал - это не только первоклассные скиллы, а еще коммуникативные навыки, умение оценить задачу, поставить реальный срок ее выполнения, управление своим временем, чтобы уложится в этот срок, умение работать в команде на общий результат. Шесть месяцев назад у меня были только скиллы и желание расти. Сейчас я на грани увольнения, на мне клеймо двоечника - просто потому что у меня отсутствует остальное. Сегодня мне предстоит защищать свое доброе имя :) 

Но в общем и целом я получил то, что хотел. Скоро соберу плоды теперешних мук.

Ну и поскольку третьего не-мифа нету, расскажу про хитрый цикл for:

        int[] a = new int[]{1, 2, 3};
        int sum = 0;
        for (int i = -1, l = a.length; ++i < l; ) {
            sum += a[i];
        }        

При таком итерировании количество итераций вычисляется всего один раз.
На сегодня все!