23 марта 2010 г.

Scala

Первое впечатление от Scala IDEA plugin: "как же медленно оно компилируется!" Натуральный С++ камбэк. Успеваю заметить штук 5 стадий компиляции. Все-таки javac, пересобирающий проект из 500 файлов за 20 секунд сильно развращает.

18 марта 2010 г.

Зачем synchronized вокруг wait/notify?

Еще один из вопросов, который мучил меня во время знакомства с java threading, и так и остался, в то время, не отвеченным. Зачем сановские инженеры спроектировали wait/notify так, что они обязательно требуют на входе захваченной блокировки? Причем на входе они ее требуют, но внутри себя они ее отпускают -- что-то очень хитрое стояло за таким решением, что-то, что я не мог понять.

Ответ оказался довольно прост. Другое поведение не имеет смысла, потому что не дает возможность реализовать то, для чего нужен wait/notify.

Зачем нужен wait? Точнее -- каков сценарий его использования? Если мы хотим просто приостановить поток -- есть Thread.sleep(). А wait нужен тогда, когда мы ждем какого-то события. Еще точнее, мы ждем выполнения какого-то условия, за которое отвечает какой-то другой поток. Но это означает, что у нашего потока с этим другим потоком есть общее, разделяемое состояние (shared state). Второй поток это состояние меняет, и в какой-то момент оно становится "подходящим" для нас, и мы хотим об этом узнать. И wait/notify это всего лишь инструмент, который дает нам такую возможность. Но если у нас есть разделяемое состояние -- нам просто необходима блокировка в обоих потоках, чтобы избежать проблем с data race и memory visibility. Сами-то по себе методы wait/notify можно организовать без требования блокировки -- но они будут бесполезны

На конкретном примере: вот как выглядит типичный код с wait/notify (представим, что секции synchronized не обязательны)
//общие переменные 
    boolean condition = false;
    final Object event = new Object();
    ...
    //первый поток
    while(!condition){
        event.wait();
    }
    ...
    //второй поток
    condition = true;
    event.notify();

Что помешает второму потоку вклиниться между строчками 7 и 8 -- когда первый уже решил, что он должен ждать, но еще не вызвал метод wait? В этом случае notify() вызванный вторым уйдет в пустоту (пока еще никто ничего не ждет), а wait(), вызванный первым никогда не пробудится -- некому больше будить (забудем пока про внезапные пробуждения). Другой вопрос -- кто гарантирует, что обновленное вторым потоком значение condition=true будет увидено первым? Никаких memory barrier-ов здесь нет, первый поток спокойно может закэшировать condition хоть в регистрах процессора, и быть свято уверенным, что оно все еще false. Еще можно вообразить разнообразные insruction reordering, в ходе которых компилятор может решить переставить condition = true после event.notify(), например.

Ок, договорились: синхронизация необходима. Но зачем так плотно привязывать wait/notify к synchronized? Спроектировали бы wait/notify независимо от синхронизации, и просто указывали бы в recommended practices что правильно писать так-то. Были бы очевидные бенефиты -- сейчас, например, старый-добрый Object.wait/notify работает только со старым-добрым synchronized, а новый Lock.lock()/unlock() только с новым же Condition.await/signal -- а между собой они не работают, что, в общем-то, странно. Казалось бы -- какая разница, каким методом обеспечивать синхронизацию разделяемого состояния между потоками?

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

Вот мы и приходим к той реализации, что имеем. Сначала нужно захватить монитор какого-нибудь объекта, чтобы обеспечить согласованность состояния condition. Потом можно вызвать wait(), и он должен захваченный монитор отпустить -- иначе как другой поток сможет изменять условие? Но wait нужно вызвать на том же объекте -- иначе как wait() узнает, какой именно монитор из 10 захваченных текущим потоком выше по стеку ему надо освободить? Перед notify нужно захватить тот же самый монитор, чтобы синхронизировать изменения в condition между потоками. Ну и очевидно, что notify() нужно вызывать на том же объекте, чтобы он знал, какой wait ему нужно будить. Такой вот расклад

Чем плохо synchronized(this)?

Несколько лет назад, бродя по исходникам JDK я задался вопросом -- почему там так часто встречается организация блокировки через
private final Object lock = new Object();
....

synchronized(lock){
...
}

хотя для этих целей легко можно использовать synchronized(this)? Я еще понимаю, если нужно несколько мониторов для разных наборов атомарных изменений (хотя, на мой взгляд, это часто первый звоночек что объекту нужна декомпозиция), но я часто видел такой код и когда монитор только один. Какое-то время это было для меня загадкой, пока у кого-то из гуру я не встретил ответа -- использование this как монитора синхронизации может нарушать инкапсуляцию вашего объекта. Монитор синхронизации -- это штука с состоянием: у него есть владелец (owner) который может меняться. Давая возможность клиентам работать с вашим объектом вы не можете запретить им работать с его монитором -- а это может менять его состояние, и нарушать те инварианты, на которые вы рассчитывали, проектируя класс.

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

Но вот вчера у меня, наконец, сложился явный и четкий пример. Итак, код:
public class Runner {

private Thread owner = null;

public synchronized void run( final Callable task ) throws Exception {
    owner = Thread.currentThread();
    try {
        task.call();
    } finally {
        owner = null;
    }
}

public synchronized void check() {
    final boolean invariant = ( owner == null ) || ( owner == Thread.currentThread() );
    if ( !invariant ) {
       //can this code be executed?
       throw new AssertionError( "Lock is broken: " + owner + " is owner, but " + Thread.currentThread() + " is here!" );
    }
}

}

на первый взгляд кажется, что код в строках 17-18 никогда не может быть выполнен. Оба метода синхронизированы, никакой посторонний поток не может влезть, пока текущий поток внутри run(Callable). Однако, сломать этот объект крайне просто:
final Runner runner = new Runner();

final Callable task = new Callable() {
    public Object call() throws Exception {
        runner.wait( 2000 );
        return null;
    }
};

final Thread thread = new Thread( "runner" ) {
    public void run() {
        try {
            runner.run( task );
        } catch ( Exception e ) {
            throw new RuntimeException( e );
        }
    }
};

thread.start();

//ensure thread started
Thread.yield();
Thread.sleep( 100 );
//check the invariant
runner.check();

object.wait() должен вызываться внутри synchronized(object), и, на время ожидания он отпускает монитор. То есть пока поток thread ждет 2 секунды на мониторе runner этот монитор свободен, несмотря на то, что выше по стеку есть synchronized(runner). И в эти 2 секунды любой другой поток может захватить этот монитор -- что мы и делаем, демонстрируя нарушение инварианта класса.

Какой отсюда вывод? Вывод такой: если вы используете callback интерфейсы -- т.е. если ваш код выполняет внутри себя какой-то другой код, пришедший "со стороны" -- вы должны делать это либо вне синхронизации, либо использовать монитор синхронизации, до которого клиентский код не сможет добраться, вроде private final Object lock = new Object()

На данный момент я не вижу других способов (кроме callback), как "открытая" синхронизация с помощью synchronized(this) может нарушить инкапсуляцию. Если класс колбэки не использует -- можно использовать синхронизированные методы спокойно.

17 марта 2010 г.

Чем отличается synchronized метод от synchronized(this) блока?

Аттрибут synchronized -- это флаг на методе. Компилятор не будет вставлять на выходе и выходе из метода инструкции захвата и освобождения монитора, как в случае с synchronized(this) блоком; вместо этого уже JVM, на стадии выполнения кода фиксирует наличие этого флага, и автоматически выполняет требуемый захват и освобождение монитора. То есть все различие -- флаг в описании метода вместо двух байт-кодов в его теле. Возникает вопрос: стоит ли беспокоиться из-за двух инструкций (инструкций байт-кода, не процессора!) Чаще всего -- нет. Но представьте себе, например, что JIT-компилятор использует количество байт-кодов в теле метода как одну из метрик для принятия решения о его инлайнировании? Что, кстати, почти наверняка так и есть...

Вольный перевод отсюда (сноска в конце страницы)

Ну так и в java коде синхронизированный метод выглядит заметно компактнее, чем метод, все тело которого обернуто в синхронизированный блок. Может, разработчики языка тем самым хотели на что-то намекнуть?

Синхронизация

Читаю серию статей от Neil Coffey по синхронизации и вообще многопоточному программированию в java. Неожиданно много вещей, которых я не помню или не знаю. Все-таки вещи, которые мало используешь плохо запоминаются -- я смутно помню, что почти все где-то когда-то встречал, но в общую картину у меня в голове все тонкости так и не собрались -- не было достаточно крупных и сложных задач по многопоточности в моей практике.

В ближайшее время постараюсь уложить все у себя в голове в единую картинку, и написать сюда пару статей. Особенно хочется разобраться с volatile и Thread.interrupt(). Заодно, может быть, пока буду готовить статьи -- и сам, наконец, запомню :)

12 марта 2010 г.

Serialization libraries

Наткнулся на интересный "проект" по сравнению производительности различных способов/библиотек сериализации/десериализации в джава. Comparing varius aspects of Serialization libraries on the JVM platform Мало того, что лишний раз подивился на разницу в скорости стандартной сериализации с Externalizable, так еще и узнал о куче интересных библиотек. Например, Kryo выглядит вполне подходящей заменой стандартному механизму -- быстрее, нагляднее, архитектурно изящнее (механизмы сериализации настраиваются отдельно от сериализуемых объектов -- в отличие от стандартного метода, где объект в любом случае сам задает свой метод сериализации, и он может быть только один). А JSON Marshaller я собираюсь рассмотреть на место XStream в DataGuard -- все равно XStream-ский json даже после тщательной доработки напильником периодически генерирует ересь. Более того, в json marshaller есть и замена для org.json.* -- по их словам она более быстрая и более удобная.

11 марта 2010 г.

Со-процедуры (coroutines)

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

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

Зачем это нужно? Ну во многих случаях это сильно упрощает программу. Например, я знаю веб-фреймворк для джавы , построенный на продолжениях (сontinuations, частный случай), где весь цикл взаимодействия с браузером может быть реализован в прямом смысле циклом -- for/while -- внутри одного метода (точнее, фреймворк написан на джаве, но код веб-приложения под него пишется на javascript/rhino). Когда нужно отправить данные пользователю ваш код просто вызывает что-то вроде var userResponse = postToUser(htmlPage); -- выполнение вашего кода приостанавливается на вызове postToUser, страница отправляется пользователю, он с ней что-то делает, результат отправляется на сервер -- и ваш код его получает в переменную userResponse, продолжая выполнение дальше. Очень изящно, гораздо проще, чем сервлеты.

Другой вариант -- всем известные генераторы, как замена итераторам.

В общем, штука удобная и интересная. К тому же, если верить автору, еще и достаточно быстрая. Если она в самом деле будет включена в jdk1.7 -- будет приятно.


Источник: http://classparser.blogspot.com/ via Levin Matveev blog

Syntax highlighter

Нашел себе подсветку синтаксиса для скриптов в блоге.

В теле поста пишем
<pre class="brush: java">
public static void main( final String[] args){
final int count = args.length;
System.out.println("count: "+count);
}
</pre>




и в результате получаем:
public static void main( final String[] args){
    final int count = args.length;
    System.out.println("count: "+count);
}


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

Подробности здесь: Awesome syntax highlighting

Numbers everyone should know

Numbers everyone (developer) should know (от разработчиков google)

* L1 cache reference 0.5 ns
* Branch mispredict 5 ns
* L2 cache reference 7 ns
* Mutex lock/unlock 100 ns
* Main memory reference 100 ns
* Compress 1K bytes with Zippy 10,000 ns
* Send 2K bytes over 1 Gbps network 20,000 ns
* Read 1 MB sequentially from memory 250,000 ns
* Round trip within same datacenter 500,000 ns
* Disk seek 10,000,000 ns
* Read 1 MB sequentially from network 10,000,000 ns
* Read 1 MB sequentially from disk 30,000,000 ns
* Send packet CA->Netherlands->CA 150,000,000 ns

9 марта 2010 г.

Smack XMPP

На выходных игрался с XMPP. Недавно на хабре был анонс простенькой текстовой игры через jabber: snow@talk2play.ru Мне поднадоело играть в нее самому, захотелось это дело автоматизировать. В итоге, после 3-х дней отладки мой бот более-менее устойчиво набирает очки. Нет ничего приятнее, чем смотреть, как кто-то делает твою работу...

Джабберовский XMPP протокол (и его реализация в Smack) произвел хорошее впечатление. Простой и расширяемый. В будущих проектах собираюсь попробовать предоставлять network interface через него -- на пару к обычному HTTP.

5 марта 2010 г.

Запуск внешних программ

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

На первый взгляд, все достаточно просто -- для простых случаев есть Runtime.exec(), если нужно настроить параметры среды для запуска -- есть ProcessBuilder. В любом случае получаем объект Process, у которого вызываем Process.waitFor(), чтобы дождаться завершения -- и, вроде бы, все?

К сожалению, ничего подобного. Несмотря на то, что API выглядит просто и очевидно, корректное его использование совсем не просто, и не очевидно. Какие конкретно подводные камни нас ждут?

Главный из них -- потоки ввода-вывода (IO streams). У порождаемого процесса нет терминала, к которому он привязан, его stdin, stdout, stderr выдаются порождающему процессу -- то есть, нам. Причем обрабатывать их -- наша обязанность. Потоки, созданные ОС имеют ограниченный размер буфера. Если, к примеру, буфер stdout для запущенного процесса заполнен, со стороны java никто его не читает (==не освобождает) а процесс настойчиво хочет что-то вывести -- то процесс просто окажется заблокирован на IO, и будет ждать, пока stdout кто-нибудь освободит. Если мы не предусмотрели в java код, читающий process.getInputStream() -- получается стандартный дедлок: мы ждем завершения процесса, процесс ждет нас.

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

Конкретный пример: в обычном, штатном режиме работы внешний процесс выдает одну-единственную строчку "Ок" и завершается. Строчка вполне влезает в буфер, поэтому код

final Process p = Runtime.getRuntime().exec( "my-script.bat" );  
final int retCode = p.waitFor();  

работает корректно. Но наступает день Х, когда звезды складываются неудачно. И процесс завершается с ошибкой. И, как и положено уважающей себя программе, старается эту ошибку максимально подробно описать. И пытается вывести в stdout простыню текста, превышающую размер буфера. Вуаля -- процесс ждет на выводе, java-программа -- на process.waitFor()

На мой взгляд -- это пример плохо спроектированного API. Простая вещь -- запустить внешний процесс не заморачиваясь с его выводом -- делается весьма нетривиально. Более того, из самого API это никак не следует. Да, в документации к Process это прописано, но я считаю, что хороший API это такой, использование которого, по крайней мере для простых задач, очевидно без документации. Можно было бы дополнить контракт, например, так: "если клиент не запросил process.getInputStream()/process.getErrorStream() до вызова process.waitFor() -- stdout/stderr внешнего процесса автоматически перенаправляются вникуда".

Но наши друзья из Sun этого не сделали, так что приходится отдуваться самим: ProcessRunner

Что делает: берет сконфигурированный ProcessBuilder, создает внешний процесс, запускает асинхронно "помпы", прокачивающие его потоки ввода-вывода либо в пустоту (если пользователь ничего не задал) либо из/в заранее заданные потоки. Метод ProcessRunner.execute() блокируется пока либо процесс не завершится, либо пока не будет вызван ProcessRunner.interrupt(). Пример использования:
final ProcessBuilder pb = new ProcessBuilder("my-script.bat");  
final ExecutorService pool = Executors.newFixedThreadPool(3); // нужно минимум 3 свободных потока в пуле 
final ProcessRunner pwd = new ProcessRunner( "run", pb,  pool );  

pwd.execute();  

final int retCode = pwd.getReturnCode();  
...
pool.shutdown();


В этом примере ввод-вывод my-script.bat будет просто выброшен. Другой пример:
final ProcessBuilder pb = ...;  
final ProcessRunner pwd = new ProcessRunner( "run", pb, POOL );  

final ByteArrayOutputStream out = new ByteArrayOutputStream();  
final ByteArrayOutputStream err = new ByteArrayOutputStream();  
pwd.setOutputStream( out );  
pwd.setErrorStream( err );  

pwd.execute();  

assertEquals( 0, pwd.getReturnCode() );  
final byte[] output = out.toByteArray();  
final byte[] errors = err.toByteArray();  

Здесь stdout/stderr будут считаны в предоставленные нами потоки. Обратите внимание, что если флаг ProcessBuilder.redirectErrorStream() выставлен в true, то stderr будет слит с stdout, и errors будет пуст.

Больше примеров использования можно посмотреть в тестах ProcessRunnerTest