HandlerSocket -- это расширение (плагин) для MySQL, позволяющий делать простые CRUD запросы непосредственно к движку хранения данных MySQL, обходя слой SQL. По словам автора, производительность такого решения в случае, когда данные БД влезают в память выше, чем производительность memcached. При том, что мы сохраняем возможность работать с теми же данными через SQL.
Здесь краткое введение в тему.
А здесь -- драйвер для java. Осталось только найти повод его где-нибудь попользовать
21 декабря 2010 г.
11 октября 2010 г.
Range check elimination
Узнал приятную новость -- оказывается в OpenJDK JIT умеет делать (пока что простейшие) array range check elimination (пруф). Другими словами -- в простых циклах по массивам вида
если компилятор может доказать, что счетчик цикла не выходит за границы массива -- runtime проверки индекса при доступе к элементам массива будут убраны. А это означает, что скорость такого доступа уже точно ничем дополнительным, по сравнению с С, не ограничена
Пока я, правда, не понял, используется ли это в какой-нибудь release версии JDK.
for(int i=0;i<arr.length;i++){ arr[i] = ...; }
если компилятор может доказать, что счетчик цикла не выходит за границы массива -- runtime проверки индекса при доступе к элементам массива будут убраны. А это означает, что скорость такого доступа уже точно ничем дополнительным, по сравнению с С, не ограничена
Пока я, правда, не понял, используется ли это в какой-нибудь release версии JDK.
28 сентября 2010 г.
Импорт классов из default package
C удивлением обнаружил, что, оказывается, классы из дефолтного пакета (которые в корне иерархии) нельзя импортировать и использовать в классах из любых других пакетов! Т.е. использовать можно, но только через reflection -- Class.forName(".."), etc. Более того, оказывается когда-то в java это было разрешено -- специальной конструкцией
import Unfinished;
(а я даже не знал, что такая есть...). О, сколько нам открытий чудных...
15 сентября 2010 г.
Развернутый связный список
Стыдно признаться, но я никогда еще в своей практике не использовал LinkedList. Не находилось оказии -- всегда оказывалось, что ArrayList либо в принципе подходит лучше, либо алгоритм можно так перестроить, что будет подходить лучше, либо в принципе он хуже, но на потребных размерах выигрывает. Последовательный доступ, большие накладные расходы памяти на хранение указателей в узлах, недружественность к кэшу и векторным инструкциям -- на мой взгляд, этого достаточно, чтобы 10 раз подумать, прежде чем его использовать. Тем не менее, никуда не деться -- вставка в случайное место для ArrayList дороже, чем для Linked уже начиная где-то с 500-1000 элементов (ну еще есть многопоточные приложения, где недружественность к кэшу становится преимуществом -- меньше конкуренция разных вычислительных ядер за строки кэша).
Не далее как вчера я обдумывал эту мысль, и пришел к выводу, что если бы мне нужен был быстрый список без (быстрого) случайного доступа но со всеми остальными плюшками, я бы попробовал сделать что-то вроде смеси array и linked -- узлы с массивами по нескольку элементов, так, чтобы узел вместе со всей служебной информацией влезал, скажем, в cache line. Получаем сразу и меньшие накладные расходы памяти -- одна пара указателей не на один, а сразу на несколько элементов. И лучшую локальность данных, и поле применимости для векторых инструкций. Все плюшки, кроме быстрого случайного доступа.
А сегодня обнаруживаю, что идею украли прямо из головы. И даже обозвать уже успели, и в википедии записали. Называется развернутый связный список (unrolled linked list). Неплохая статья на MSDN по этой структуре данных. Автор провел небольшое исследование производительности, и нашел, что развернутый список обходится (последовательно) примерно в 2.5 раза медленнее массива (обычный связный -- в 12 раз медленнее). На мой взгляд -- вполне разумный компромисс.
Еще вчера же, когда я дочитывал про B-trees, мне так же пришло в голову, что обычные бинарные диревья в нынешних условиях многоуровневых кэшей и векторных инструкций тоже очень не в тему. Всякие TreeMap разумнее было бы делать на базе B-trees, нежели на основе красно-черных бинарных деревьев. Ведь в конце-концов B-trees и были придуманы для ситуаций, когда операция сравнения элементов гораздо (на порядки) дешевле операции загрузки очередной страницы дерева. Некогда имелась в виду загрузка с диска/ленты, но нынче в роли "медленного хранилища" вполне подходит основная оперативная память. При разнице в скоростях доступа к кэшу и оперативке в несколько порядков -- куда уж медленнее. Вместо того, чтобы делать одно сравнение, и переходить к следующему узлу -- с высокой вероятностью кэш-промаха -- было бы оптимальнее сделать десяток-другой сравнений сразу.
Не далее как вчера я обдумывал эту мысль, и пришел к выводу, что если бы мне нужен был быстрый список без (быстрого) случайного доступа но со всеми остальными плюшками, я бы попробовал сделать что-то вроде смеси array и linked -- узлы с массивами по нескольку элементов, так, чтобы узел вместе со всей служебной информацией влезал, скажем, в cache line. Получаем сразу и меньшие накладные расходы памяти -- одна пара указателей не на один, а сразу на несколько элементов. И лучшую локальность данных, и поле применимости для векторых инструкций. Все плюшки, кроме быстрого случайного доступа.
А сегодня обнаруживаю, что идею украли прямо из головы. И даже обозвать уже успели, и в википедии записали. Называется развернутый связный список (unrolled linked list). Неплохая статья на MSDN по этой структуре данных. Автор провел небольшое исследование производительности, и нашел, что развернутый список обходится (последовательно) примерно в 2.5 раза медленнее массива (обычный связный -- в 12 раз медленнее). На мой взгляд -- вполне разумный компромисс.
Еще вчера же, когда я дочитывал про B-trees, мне так же пришло в голову, что обычные бинарные диревья в нынешних условиях многоуровневых кэшей и векторных инструкций тоже очень не в тему. Всякие TreeMap разумнее было бы делать на базе B-trees, нежели на основе красно-черных бинарных деревьев. Ведь в конце-концов B-trees и были придуманы для ситуаций, когда операция сравнения элементов гораздо (на порядки) дешевле операции загрузки очередной страницы дерева. Некогда имелась в виду загрузка с диска/ленты, но нынче в роли "медленного хранилища" вполне подходит основная оперативная память. При разнице в скоростях доступа к кэшу и оперативке в несколько порядков -- куда уж медленнее. Вместо того, чтобы делать одно сравнение, и переходить к следующему узлу -- с высокой вероятностью кэш-промаха -- было бы оптимальнее сделать десяток-другой сравнений сразу.
5 сентября 2010 г.
Потестировал на днях свою реализацию lock-free BufferedQueue. Неожиданно, но версия с блокировками всухую обходит CAS. Пока не могу понять, в чем дело -- грешу на ConcurrentLinkedQueue
Оказывается, с атомиками все тоже не так-то просто. Они, конечно, дешевле блокировок, но создают ничуть не меньший траффик на межпроцессорной (межядерной) шине. В определенных условиях это может сильно ухудшать производительность.
Оказывается, с атомиками все тоже не так-то просто. Они, конечно, дешевле блокировок, но создают ничуть не меньший траффик на межпроцессорной (межядерной) шине. В определенных условиях это может сильно ухудшать производительность.
1 сентября 2010 г.
Shared exceptions
Доктор Хейнс Кабуц в очередной рассылке пишет про интересную вещь -- оказывается, если в коде часто выбрасываются RuntimeException типа NPE, или ArrayIndexOutOfBoundsException, то JIT в серверном режиме их оптимизирует -- начиная с какого-то момента он перестанет создавать новый экземпляр исключения на каждый случай, а начнет каждый раз бросать один и тот же экземпляр, с пустым stack trace! Я припоминаю, что в самом деле видел такие исключения без стека вызовов -- но как-то не подумал, с чем это может быть связано, списал на баг протоколирования. Оказывается эта оптимизация выполняется аж с 2002 года -- хотя я до сих пор нигде не встречал ее описания.
27 августа 2010 г.
Immutable vs Mutable объекты
Когда я узнал, что final поля "гарантированно видимы любому потоку" после завершения инициализации, я подумал, что ведь это чего-то да стоит. В том смысле, что создание immutable объекта с final полями должно быть дороже (как минимум, в многопоточном окружении, чтобы JIT не смог это оптимизировать). Т.е. как минимум нужен же мембар по завершении конструктора. Сегодня попытался это проверить -- объявил два идентичных класса, у одного из которых все поля final, и создавал их в 150 потоков параллельно. Черта с два -- одинаково. Даже наоборот, есть слабое преимущество у immutable класса, но я бы не назвал его статистически значимым -- где-то 0.5%, слишком мало, чтобы принимать во внимание в случае микробенчмарка.
P.S. Постфактум это становится довольно очевидным. Семантика final полей обеспечивается StoreStore барьером между инициализацией полей и публикацией ссылки на созданный объект:
Но на x86, где я тестировал, StoreStore барьер не нужен -- тамошняя "железная" модель памяти (TSO -- total store order) достаточно строгая сама по себе.
Более того, по словам DL в его кулинарной книге StoreStore барьеры, даже если они и требуются на какой-то архитектуре, обычно одни из самых дешевых. Так что, с высокой степенью вероятности, их влияние на производительность даже там, где они есть (и требуются) будет теряться на фоне, например, затрат на выделение памяти
P.S. Постфактум это становится довольно очевидным. Семантика final полей обеспечивается StoreStore барьером между инициализацией полей и публикацией ссылки на созданный объект:
localRef = alloc(...); localRef.field1 = ...; localRef.field2 = ...; .... StoreStore; globallyAvailableRef = localRef;
Но на x86, где я тестировал, StoreStore барьер не нужен -- тамошняя "железная" модель памяти (TSO -- total store order) достаточно строгая сама по себе.
Более того, по словам DL в его кулинарной книге StoreStore барьеры, даже если они и требуются на какой-то архитектуре, обычно одни из самых дешевых. Так что, с высокой степенью вероятности, их влияние на производительность даже там, где они есть (и требуются) будет теряться на фоне, например, затрат на выделение памяти
Производительность
Тестировал, ради интереса, пиковую производительность разных методов доступа к разделяемому состоянию в многопоточном окружении. Участники соревнования -- sunchronized, ReentrantLock, AtomicInteger, volatile+AtomicIntegerFieldUpdater.
Результаты, в целом, предсказуемы -- атомики в 2-3 раза быстрее блокировок. Две интересные вещи:
ReentrantLock опять обогнал synchronized! Заметно -- на четверть почти. Как-то я уже обнаруживал этот феномен. До сих пор не могу понять, в чем здесь цимус -- Сановцы забили на оптимизацию "старого" способа синхронизации? -- но на ус придется намотать :)
AtomicInteger немного обогнал volatile+AtomicIntegerFieldUpdater. Чуть-чуть -- процентов на 5 в среднем. Не то, чтобы неожиданно -- накладные расходы на reflection-based доступ -- но все же приятно видеть их вживую в цифрах. Разница есть, она измерима, но по величине явно не критична. Т.е. на AtomicIntegerFieldUpdater можно закладываться как на альтернативу AtomicInteger.
Результаты, в целом, предсказуемы -- атомики в 2-3 раза быстрее блокировок. Две интересные вещи:
ReentrantLock опять обогнал synchronized! Заметно -- на четверть почти. Как-то я уже обнаруживал этот феномен. До сих пор не могу понять, в чем здесь цимус -- Сановцы забили на оптимизацию "старого" способа синхронизации? -- но на ус придется намотать :)
AtomicInteger немного обогнал volatile+AtomicIntegerFieldUpdater. Чуть-чуть -- процентов на 5 в среднем. Не то, чтобы неожиданно -- накладные расходы на reflection-based доступ -- но все же приятно видеть их вживую в цифрах. Разница есть, она измерима, но по величине явно не критична. Т.е. на AtomicIntegerFieldUpdater можно закладываться как на альтернативу AtomicInteger.
22 августа 2010 г.
Lock-free BufferedQueue
Дали тестовое задание перед собеседованием -- написать буфферизированную очередь. Т.е. на вход метода
Первая моя версия использовала ReadWriteLock -- неэксклюзивным ReadLock-ом защищался append, эксклюзивным WriteLock-ом -- flush. Но потом я взмедитнул, и подумал, что блокировки там вообще лишние. И получилась у меня lock-free версия вот такой:
Это первый мой опыт в самостоятельном написании lock-free алгоритмов на CAS-примитивах, поэтому написал я этот код часа за два, а медитировал над ним дня 3. Но косяков так и не нашел -- все должно работать как задумано. Увлекательное это занятие -- обдумывать корректность таких алгоритмов, мозг работает явно на повышенных оборотах
append(T item)
из нескольких потоков сыплются некие объекты, и, раз в какой-то интервал времени, задаваемый при создании, накопившиеся объекты сбрасываются списком в заранее заданный callback. Причем, понятно, очередь должна иметь как можно большую пропускную способность, т.е. засинхронизировать метод append -- это не выход. Первая моя версия использовала ReadWriteLock -- неэксклюзивным ReadLock-ом защищался append, эксклюзивным WriteLock-ом -- flush. Но потом я взмедитнул, и подумал, что блокировки там вообще лишние. И получилась у меня lock-free версия вот такой:
public class LockFreeBufferedQueue<ItemType> { private final ICallback<Collection<ItemType>> callback; private final long delay; private final TimeUnit timeUnit; private final ScheduledExecutorService executor; private final CallableVoid> flushBufferTask = new Callable<Void>() { public Void call() { assert ( pending.get() > 0 ) : "Call without being scheduled"; flushBuffer(); return null; } }; private final AtomicInteger pending = new AtomicInteger( 0 ); /** * volatile чтобы обойтись без блокировки в методе isClosed() */ private volatile boolean closed = false; private volatile ScheduledFuture<Void> scheduledTask = null; /** * lock-free Queue implementation */ private final ConcurrentLinkedQueue<ItemType> queue = new ConcurrentLinkedQueue<ItemType>(); public LockFreeBufferedQueue( final ICallback<Collection<ItemType>> callback, final long delay, final TimeUnit timeUnit, final ScheduledExecutorService executor ) { this.callback = callback; this.delay = delay; this.timeUnit = timeUnit; this.executor = executor; } /** * Не специфицированно, что должен делать метод close(), поэтому мы делаем так: * * Уже добавленные задачи будут выполнены немедленно, в текущем потоке, добавление * новых приведет к IllegalStateException в методе append(). */ public void close() { if ( !closed ) { closed = true; if ( scheduledTask != null ) { if ( scheduledTask.cancel( false ) ) { flushBuffer(); } scheduledTask = null; } //todo but if executor is external? executor.shutdown(); } } /** * @throws IllegalStateException если очередь уже закрыта вызовом close() * @throws NullPointerException если item == null */ public void append( final ItemType item ) { if ( closed ) { throw new IllegalStateException( "Queue was closed" ); } queue.add( item ); if ( pending.getAndIncrement() == 0 ) { schedule(); } } private void flushBuffer() { final int size = pending.get(); assert ( !queue.isEmpty() ) : "Call with empty queue"; assert ( size > 0 ) : "Call with 0 pending"; final Collection<ItemType> buffer = new ArrayList<ItemType>( size ); for ( int i = 0; i < size; i++ ) { buffer.add( queue.poll() ); } if ( pending.getAndAdd( -size ) > size ) { schedule(); } //todo что делать с исключениями? callback.call( buffer ); } private void schedule() { scheduledTask = executor.schedule( flushBufferTask, delay, timeUnit ); } public boolean isClosed() { return closed; } } //============================= public interface ICallback<T> { public void call( final T arg ); }
Это первый мой опыт в самостоятельном написании lock-free алгоритмов на CAS-примитивах, поэтому написал я этот код часа за два, а медитировал над ним дня 3. Но косяков так и не нашел -- все должно работать как задумано. Увлекательное это занятие -- обдумывать корректность таких алгоритмов, мозг работает явно на повышенных оборотах
18 августа 2010 г.
Clipboard
Несколько дней уже разбираюсь с задачей экспорта картинки из Матконструктора в офисный пакет. Хочется картинку выдавать векторную, чтобы в, скажем, Word можно было ее потом масштабировать и редактировать как удобно. Собственно, экспортировать картинку в какой-нибудь векторный формат не проблема -- библиотека FreeHEP дает сразу полдюжины вариантов, от SVG до родного виндового EMF. Вопрос в том, как объяснить буферу обмена, что тот набор, казалось бы, случайных байт, что в нем лежит -- это не данные датчика случайных чисел, а-таки EMF.
Оказывается, сделать это все-таки можно. Похоже, первыми на решение набрели разработчики JFreeChart (во всяком случае, остальные источники ссылаются на них как на оригинал). Внимание, сейчас будет фокус:
(источник)
Если я все правильно понял, волшебное заклинание sfm.addUnencodedNativeForFlavor(EMF_FLAVOR, "ENHMETAFILE"); объясняет джаве, что данные, маркированные как EMF_FLAVOR при передаче в системный буфер обмена никак не надо обрабатывать, надо отдать их туда как есть (массивом байт), и снабдить описанием типа "ENHMETAFILE". Интересно, какие еще типы можно так передавать? Пока я нашел только вариант, где удалось передать MathML.
Второй способ придумал автор Java Vector Cut and Paste Library. Финт ушами -- формат rtf поддерживается джавой из коробки, в том числе его передача через Clipboard. А rtf может служить контейнером для таких форматов как MACPICT, EMF и WMF. Через одно место, но уже неплохо -- в частности, MACPICT по его словам понимается как на Windows так и на Mac OS X. Но rtf не воспринимается, например, PowerPoint-ом -- в отличие от первого способа.
Оказывается, сделать это все-таки можно. Похоже, первыми на решение набрели разработчики JFreeChart (во всяком случае, остальные источники ссылаются на них как на оригинал). Внимание, сейчас будет фокус:
//на самом деле mime-type здесь не важен, //можно использовать image/emf, application/emf, image/x-mgx-emf, etc public static final DataFlavor EMF_FLAVOR= new DataFlavor("image/x-emf", "Enhanced Meta File"); static { // EMF graphics clipboard format try { final SystemFlavorMap sfm = (SystemFlavorMap)SystemFlavorMap.getDefaultFlavorMap(); sfm.addUnencodedNativeForFlavor(EMF_FLAVOR, "ENHMETAFILE");//seems to be a key command!! } catch(Exception e) { e.printStackTrace(); } }
(источник)
Если я все правильно понял, волшебное заклинание sfm.addUnencodedNativeForFlavor(EMF_FLAVOR, "ENHMETAFILE"); объясняет джаве, что данные, маркированные как EMF_FLAVOR при передаче в системный буфер обмена никак не надо обрабатывать, надо отдать их туда как есть (массивом байт), и снабдить описанием типа "ENHMETAFILE". Интересно, какие еще типы можно так передавать? Пока я нашел только вариант, где удалось передать MathML.
Второй способ придумал автор Java Vector Cut and Paste Library. Финт ушами -- формат rtf поддерживается джавой из коробки, в том числе его передача через Clipboard. А rtf может служить контейнером для таких форматов как MACPICT, EMF и WMF. Через одно место, но уже неплохо -- в частности, MACPICT по его словам понимается как на Windows так и на Mac OS X. Но rtf не воспринимается, например, PowerPoint-ом -- в отличие от первого способа.
4 августа 2010 г.
MAC-адрес в java
Периодически обнаруживаю в JDK функциональность, которую я там не ожидал. Например, сегодня узнал, что можно получить MAC-адрес сетевой карты вызовом NetworkInterface.getHardwareAddress(). Причем судя по документации, метод еще с 1.4 тянется. А я почему-то был уверен, что хрен там, а не MAC из java. Приятная неожиданность.
28 июля 2010 г.
java + USB, java + bluetooth (JSR-80/82)
Сегодня был на собеседовании. Задача -- программа на джаве должна общаться с девайсами через USB. Пришел на работу, полез смотреть, что есть на эту тему готового -- ну как-то не верилось мне, что до сих пор никому в голову не пришло работать с USB из java.
Пришло. Даже JSR есть -- №80. Пакет javax.usb официально зарезервирован. Есть reference implementation от IBM. Текущий статус? -- Черт его знает. Последние изменения датированы годом 2003 -- я еще в институте учился. Реализация для linux вроде худо-бедно рабочая. Реализации для windows -- нет. Внимание: вопрос на засыпку. Какой смысл в JSR для java, если реализация есть только для одной платформы? Кому нужно чисто для линукс -- мог бы и через usbfs извернуться.
Есть альтернатива: jUSB. Текущий статус -- такой же, все бросили. Реализация есть для linux/bsd/mac. Для windows -- какая-то alpha, на базе libusb. libusb в свою очередь -- проект про кроссплатформенному C-API для USB. Тоже с неизвестным статусом, тоже заброшен. win-реализация требует для сборки cygwin, и работает через свой драйвер USB уровня ядра (kernel mode). У меня смутное ощущение, чтолибо лыжи не едут, либо я ебанутый я чего-то здесь не понимаю -- USB что, настолько сложный стандарт? В винде невозможно работать с USB устройством иначе как проинсталлировав в систему свой драйвер? Я даже не столько жалуюсь, сколько реально не понимаю ситуации. Вроде USB очень современный, актуальный стандарт, over 9000 устройств через него подключаются -- мне бы казалось, что уже везде должны быть красивые и удобные привязки (binding) для работы с ним -- ан нет, в Java такого нет. И причина не понятна
Кстати, такая же подстава с java bluetooth. Есть JSR-82, есть какие-то реализации - в основном для J2ME. Здесь ситуация обратная -- с windows версией все вроде ок, linux/bsd версия есть, но с некоторыми шаманскими плясками. Опять же, проект стоит на месте уже лет 5. Странно.
Пришло. Даже JSR есть -- №80. Пакет javax.usb официально зарезервирован. Есть reference implementation от IBM. Текущий статус? -- Черт его знает. Последние изменения датированы годом 2003 -- я еще в институте учился. Реализация для linux вроде худо-бедно рабочая. Реализации для windows -- нет. Внимание: вопрос на засыпку. Какой смысл в JSR для java, если реализация есть только для одной платформы? Кому нужно чисто для линукс -- мог бы и через usbfs извернуться.
Есть альтернатива: jUSB. Текущий статус -- такой же, все бросили. Реализация есть для linux/bsd/mac. Для windows -- какая-то alpha, на базе libusb. libusb в свою очередь -- проект про кроссплатформенному C-API для USB. Тоже с неизвестным статусом, тоже заброшен. win-реализация требует для сборки cygwin, и работает через свой драйвер USB уровня ядра (kernel mode). У меня смутное ощущение, что
Кстати, такая же подстава с java bluetooth. Есть JSR-82, есть какие-то реализации - в основном для J2ME. Здесь ситуация обратная -- с windows версией все вроде ок, linux/bsd версия есть, но с некоторыми шаманскими плясками. Опять же, проект стоит на месте уже лет 5. Странно.
16 июля 2010 г.
NIO
С завидной регулярностью забываю, что пакет NIO -- это не только ценный мех не только для ввода-вывода. В нем еще есть много полезных операций с примитивами -- в частности, такие вещи, которые бы в С делались простым приведением типа указателя, а в java так сходу даже и не вспоминается, как бы это сделать, и чтобы не сильно через задницу. Так вот — не через задницу это через java.nio. Вот, например, потребовалось сконвертировать int[] в byte[] -- пожалуйста:
final int[] idata = ...; final ByteBuffer buff = ByteBuffer.allocate( idata.length * 4 ); buff.asIntBuffer().put( idata ); data = buff.array();
Интересное изнутри F/J
В статье про Fork/Join framework Дог Ли отмечает интересную деталь. Он реализовывал двунаправленную очередь (Deque) для задач на базе массива. И обнаружил, что если хранить в массиве непосредственно ссылки на объекты, то в многоядерном окружении это будет работать медленнее, чем если ввести дополнительный уровень, и хранить в массиве ссылку на промежуточный объект (Entry), а уж из объекта ссылаться на саму задачу. Он предполагает, что этот эффект возникает из-за уменьшения cache contention, потому что данные массива в памяти идут подряд, а Entry создаются джавой как получится, и чаще всего будут более-менее разбросаны по памяти. Я так понимаю, речь идет о cache contention при записи -- насколько мне помнится, в многоядерном окружении если разные ядра читают-пишут в одну и ту же область памяти -- это действительно довольно затратно, потому что приходится постоянно блокировать/разблокировать сегмент кэша на эксклюзивный доступ для конкретного ядра.
Ну а еще таким образом можно сделать ссылку на объект volatile, и упросить синхронизацию :)
Ну а еще таким образом можно сделать ссылку на объект volatile, и упросить синхронизацию :)
JSR-166y -- fork/join
Ух ты блин! Читал "Scala 2.8.0: what's new" и наткнулся на загадочную фразу: "Actors can be configured to use the efficient JSR166y fork/join pool, resulting in significant performance improvements on 1.6 JVMs". Поскольку я никогда не слышал о fork/join в 1.6, полез копать. Оказывается, группа разработки JSR-166 (который java.util.concurrent) не угомонилась, когда их работа была включена в jdk. Нет, эти гарные хлопцы продолжили свои бесчеловечные экспериментыразработки. Они разработали расширение для JSR-166, называется JSR-166y, иначе Fork/Join framework. Это фреймворк, упрощающий разработку кода для многоядерных систем. В целом можно сказать, что это что-то в духе (отдаленно) map/reduce идеологии, когда код пишется в терминах элементарных операций, независимых друг от друга, которым совершенно по барабану, на каком ядре/узле сети они выполняются, а уже специальный управляющий модуль раскидывает куски данных по вычислительным узлам, и собирает результаты работы.
Статья Дога Ли с описанием исходного Fork/Join фраймворка здесь. Вкратце: вычисления обертываются в наследника ForkJoinTask (который, в свою очередь, наследник Future) и помещаются в ForkJoinPool (менеджер потоков). Выполняемый в пуле ForkJoinTask может внутри себя создать и запустить (асинхронно) другой ForkJoinTask, используя метод fork(). И может дождаться его завершения методом join() (близкий аналог Future.get()). В такой семантике легко записываются алгоритмы типа "разделяй и властвуй" -- в примерах обычно показывается, как удобно реализовывать merge sort в такой нотации.
Основной бонус перед ExecutorService/Future, как я его сейчас понял -- не нужно явно указывать ExecutorService каждый раз при создании новой задачи. То есть вместо
мы имеем
Разумеется, "верхний" ForkJoinTask все равно нужно явно поместить выполняться в какой-то пул -- но внутренняя реализация ForkJoinTask.exec не имеет никакой информации о пуле -- она вообще пул не видит, она видит только задачи (ForkJoinTask). На мой взгляд, это очень удобно. Последний раз, когда я реализовывал асинхронный рекурсивный обход дерева, я сильно ругался на то, что мне все время приходится тащить с собой ссылку на ExecutorService. Т.е. именно для рекурсивных алгоритмов, как я вижу, такой подход становится особенно удобным.
Дополнение можно просто скачать как отдельную библиотеку. Возможно, оно будет включено в jdk 1.7. Я бы приветствовал :)
Статья Дога Ли с описанием исходного Fork/Join фраймворка здесь. Вкратце: вычисления обертываются в наследника ForkJoinTask (который, в свою очередь, наследник Future) и помещаются в ForkJoinPool (менеджер потоков). Выполняемый в пуле ForkJoinTask может внутри себя создать и запустить (асинхронно) другой ForkJoinTask, используя метод fork(). И может дождаться его завершения методом join() (близкий аналог Future.get()). В такой семантике легко записываются алгоритмы типа "разделяй и властвуй" -- в примерах обычно показывается, как удобно реализовывать merge sort в такой нотации.
Основной бонус перед ExecutorService/Future, как я его сейчас понял -- не нужно явно указывать ExecutorService каждый раз при создании новой задачи. То есть вместо
final ExecutorService es = ...; final Future<?> f = es.submit( task ); final Object res = f.get();
мы имеем
final ForkJoinTask task = ...; task.fork(); final Object res = task.join();
Разумеется, "верхний" ForkJoinTask все равно нужно явно поместить выполняться в какой-то пул -- но внутренняя реализация ForkJoinTask.exec не имеет никакой информации о пуле -- она вообще пул не видит, она видит только задачи (ForkJoinTask). На мой взгляд, это очень удобно. Последний раз, когда я реализовывал асинхронный рекурсивный обход дерева, я сильно ругался на то, что мне все время приходится тащить с собой ссылку на ExecutorService. Т.е. именно для рекурсивных алгоритмов, как я вижу, такой подход становится особенно удобным.
Дополнение можно просто скачать как отдельную библиотеку. Возможно, оно будет включено в jdk 1.7. Я бы приветствовал :)
9 июля 2010 г.
Не пущать!
Вчера и сегодня потратил два дня, пытаясь локализовать реализацию javax.measure из org.jscience. Уже не первый раз наталкиваюсь на странную штуку -- грамотные, вроде бы, программисты (Жан-Мари Дотель -- очень грамотный программист) создавая библиотеку, предназначенную для расширения ухитряются закрыть все возможности для этого.
Класс UnitFormat extends java.util.text.Format. Изначально, по задумке Жан-Мари, предназначен для локализации. Там даже комментарии в коде такие есть. И локализация была бы просто элементарной, если бы Жан-Мари не ухитрился во всех удобных точках вмешательства проставить такие права доступа, что к ним хрен доберешься.
В прошлый раз такая же засада была с gui-commands -- в конечном счете, штук 10 классов из этой библиотеки перекочевали в source path моего проекта, и подверглись вивисекции. Хотя этот метод мне всегда напоминал "guriella patching"
Класс UnitFormat extends java.util.text.Format. Изначально, по задумке Жан-Мари, предназначен для локализации. Там даже комментарии в коде такие есть. И локализация была бы просто элементарной, если бы Жан-Мари не ухитрился во всех удобных точках вмешательства проставить такие права доступа, что к ним хрен доберешься.
В прошлый раз такая же засада была с gui-commands -- в конечном счете, штук 10 классов из этой библиотеки перекочевали в source path моего проекта, и подверглись вивисекции. Хотя этот метод мне всегда напоминал "guriella patching"
6 июля 2010 г.
Никогда, никогда, никогда блядь не пишите так:
public MyObject(final int param){ if( param < 0 ){ throw new IllegalArgumentException("Incorrect param"); } }надо как минимум вот так:
public MyObject(final int param){ if( param < 0 ){ throw new IllegalArgumentException("Incorrect param: must be >=0, but got "+param); } }Это всего на пару дюжин символов длиннее -- но знали бы вы, сколько времени это иногда может сэкономить при отладке... Собственно, часто это может сделать саму отладку просто не нужной -- будет сразу понятно, где возник косяк. Особенно обидно, что такие вещи часто встречаются в самом jdk -- класс Color тому наглядный пример. В свое время в Матконструкторе мы частенько создавали Color из javascript, и когда получаешь "Color parameter outside of expected range: Alpha" это ни разу не помогает что-либо понять.
7 июня 2010 г.
IntellJ IDEA 9 Community Edition
Она меня достала. Я не знаю, как нежно любимая мною компания JetBrains могла выпустить на рынок такое говно. Я не знаю, какие цели они при этом преследовали. Но на мой взгляд, это epic fail. Она дико тормозит, на совершенно скромных по размерам проектах. Она жрет память так, как будто рассчитывает, что я буду втыкать новые планки по мере ее желания. На P4 3.2Ghz, у нее в распоряжении 700Мб памяти -- и проект открывается 10 минут!!! Перерасчет индексов -- 10 минут! Разработчики -- да вы просто охуели!
Более того, она глючит чуть ли не каждый день. Это само по себе не так важно -- старые версии тоже подглючивали, безглючных программ не бывает. Но в старых версиях это ничему не мешало -- ну не выполнилась команда сейчас, выполнится в следующий раз. Они глючили безопасно -- а эта падла то настройки собьет, то JDK забудет.
Лучи поноса JetBrains.
P.S. Ах, да. Совсем забыл. Ее папка кэшей занимает 7Гб. Семь гигабайт! Разработчики, да вы что, совсем белены объелись? Чего там хранить на 7Гб??? content.dat.storageData -- 7.5Гб один файл. Неудивительно, что ей нужно 10 минут чтобы хотя бы прочитать его.
Более того, она глючит чуть ли не каждый день. Это само по себе не так важно -- старые версии тоже подглючивали, безглючных программ не бывает. Но в старых версиях это ничему не мешало -- ну не выполнилась команда сейчас, выполнится в следующий раз. Они глючили безопасно -- а эта падла то настройки собьет, то JDK забудет.
Лучи поноса JetBrains.
P.S. Ах, да. Совсем забыл. Ее папка кэшей занимает 7Гб. Семь гигабайт! Разработчики, да вы что, совсем белены объелись? Чего там хранить на 7Гб??? content.dat.storageData -- 7.5Гб один файл. Неудивительно, что ей нужно 10 минут чтобы хотя бы прочитать его.
30 апреля 2010 г.
ReentrantLock и synchronized(lock)
Пока рылся в исходниках JetLang, наткнулся на такой класс, как RunnableBlockingQueue.java. Мне показалось интересным, что в нем используется ReentrantLock, хотя по функциональности вполне подойдет обычный synchronized() и wait()/notify() -- никакой новой функциональности из ReentrantLock не используется. Стало интересно, есть ли какой-либо эффект в плане производительности?
Взял, прямолинейно переписал RunnableBlockingQueue с использованием plain old synchronized style. Создал свой класс MyPoolFiber, использующий именно эту реализацию очереди, и сравнил производительность на примере пинг-понга.
С дефолтными настройками оба варианта неразличимы по производительности. А вот результат с -server меня удивил -- ReentrantLock весьма заметно быстрее. Примерно 7 к 6 в пользу ReentrantLock. Не ожидал -- мне казалось, что новое concurrency API более мощное, но и более медленное -- в тех случаях, когда используется теми же методами, что и старое.
Взял, прямолинейно переписал RunnableBlockingQueue с использованием plain old synchronized style. Создал свой класс MyPoolFiber, использующий именно эту реализацию очереди, и сравнил производительность на примере пинг-понга.
С дефолтными настройками оба варианта неразличимы по производительности. А вот результат с -server меня удивил -- ReentrantLock весьма заметно быстрее. Примерно 7 к 6 в пользу ReentrantLock. Не ожидал -- мне казалось, что новое concurrency API более мощное, но и более медленное -- в тех случаях, когда используется теми же методами, что и старое.
JetLang
Игрался с библиотекой JetLang. Библиотека довольно компактная, и реализует идею message-based concurrency для джавы. Основной плюс идеи организации взаимодействия потоков через отправку-прием сообщений в том, что, при грамотной реализации, можно обеспечить очень низкий уровень столкновений блокировок (lock contention).
Вообще, я поначалу был несколько разочарован -- ожидал что-то вроде Scala Actors в миниатюре, пусть с ограничениями. Но JetLang это скорее бэкграунд, на фоне которого можно самому реализовать что-то вроде Акторов, если захочется.
С другой стороны плюс в том, что библиотека очень маленькая, и очень грамотно архитектурно реализована. Хотя некоторые концепции непривычны -- широкое использование Disposable, например, я с нетерпением жду оказии попробовать JetLang в каком-нибудь реальном проекте.
Вообще, я поначалу был несколько разочарован -- ожидал что-то вроде Scala Actors в миниатюре, пусть с ограничениями. Но JetLang это скорее бэкграунд, на фоне которого можно самому реализовать что-то вроде Акторов, если захочется.
С другой стороны плюс в том, что библиотека очень маленькая, и очень грамотно архитектурно реализована. Хотя некоторые концепции непривычны -- широкое использование Disposable, например, я с нетерпением жду оказии попробовать JetLang в каком-нибудь реальном проекте.
20 апреля 2010 г.
JFormattedTextField.selectAll
Нужно было сделать, чтобы JFormattedTextField выделял свое содержимое при получении фокуса. Стандартный addFocusListener -> .selectAll не сработал. Оказывается (см Sun bug #4740914) JFormattedTextField переопределяет processFocusEvent и внутри сначала зовет базовую реализацию (которая, в числе прочего, и FocusListener-ы оповещает), а потом переформатирует свое содержимое -- что, разумеется, все выделение напрочь убивает.
Решений нашлось два. Либо уныло-универсальное addFocusListener -> SwingUtilities.invokeLater -> selectAll, либо отнаследовать свой собственный класс от JFTF, и переопределить processFocusEvent, дописав в конец selectAll().
Второй метод мне нравится несколько более, но использовать пришлось первый, потому что создание JFTF в моем случае находится вне моего кода, и заменить JFTF на свою реализацию я не смогу без нерационально большого геммороя.
Вообще, в свинге очень много мест, где приходится использовать invokeLater без явной надобности -- просто потому, что черт ногу сломит разбираться с точной последовательностью обработки каких-либо событий.
Решений нашлось два. Либо уныло-универсальное addFocusListener -> SwingUtilities.invokeLater -> selectAll, либо отнаследовать свой собственный класс от JFTF, и переопределить processFocusEvent, дописав в конец selectAll().
Второй метод мне нравится несколько более, но использовать пришлось первый, потому что создание JFTF в моем случае находится вне моего кода, и заменить JFTF на свою реализацию я не смогу без нерационально большого геммороя.
Вообще, в свинге очень много мест, где приходится использовать invokeLater без явной надобности -- просто потому, что черт ногу сломит разбираться с точной последовательностью обработки каких-либо событий.
4 апреля 2010 г.
Вышел jdk 1.6 под Snow Leopard
В последнем обновлении наконец-то появилась шестая джава под мак (SL).
Тут же появились и проблемы с ней. java.io.IOException: Keystore was tampered with, or password was incorrect -- вылетает при работе с SSL ссылка
Корень проблем оказался прост -- эппл сменила (скорее всего, просто по ошибке) дефолтный пароль на хранилище сертификатов с changeit на changeme. Если сменить его обратно -- все заработает как встарь. Например, вот так:
Тут же появились и проблемы с ней. java.io.IOException: Keystore was tampered with, or password was incorrect -- вылетает при работе с SSL ссылка
Корень проблем оказался прост -- эппл сменила (скорее всего, просто по ошибке) дефолтный пароль на хранилище сертификатов с changeit на changeme. Если сменить его обратно -- все заработает как встарь. Например, вот так:
sudo -i cd /System/Library/Frameworks/JavaVM.framework/Home/lib/security/ keytool -storepasswd -keystore cacerts
1 апреля 2010 г.
Кодировка консоли в java
Кодировка консоли в Win не совпадает с системной кодировкой (которая
Я долгое время был уверен, что такого метода в jdk не предусмотрено. Но, совершенно случайно, раскапывая исходники стандартной библиотеки обнаружил решение для Sun JDK 1.6+ -- приватный статический метод
возвращает на моей системе Cp866, как положено.
Charset.defaultCharset()
). А знать ее часто бывает полезно. Например, запускаемые внешние процессы могут возвращать описания ошибок на русском -- и фиг вы их прочитаете. Конечно, можно просто зашить кодировку Cp866 для русской версии -- но это неправославное решение. А ну как другая локаль будет? Хотелось бы иметь возможность спросить, в какой кодировке работает наша консоль.Я долгое время был уверен, что такого метода в jdk не предусмотрено. Но, совершенно случайно, раскапывая исходники стандартной библиотеки обнаружил решение для Sun JDK 1.6+ -- приватный статический метод
java.io.Console.encoding()
final Class<Console> clazz = Console.class; final Method method = clazz.getDeclaredMethod( "encoding", new Class[0] ); method.setAccessible( true ); final String encoding = ( String )method.invoke( null );
возвращает на моей системе Cp866, как положено.
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 не обязательны)
Что помешает второму потоку вклиниться между строчками 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 ему нужно будить. Такой вот расклад
Ответ оказался довольно прост. Другое поведение не имеет смысла, потому что не дает возможность реализовать то, для чего нужен 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 я задался вопросом -- почему там так часто встречается организация блокировки через
хотя для этих целей легко можно использовать synchronized(this)? Я еще понимаю, если нужно несколько мониторов для разных наборов атомарных изменений (хотя, на мой взгляд, это часто первый звоночек что объекту нужна декомпозиция), но я часто видел такой код и когда монитор только один. Какое-то время это было для меня загадкой, пока у кого-то из гуру я не встретил ответа -- использование this как монитора синхронизации может нарушать инкапсуляцию вашего объекта. Монитор синхронизации -- это штука с состоянием: у него есть владелец (owner) который может меняться. Давая возможность клиентам работать с вашим объектом вы не можете запретить им работать с его монитором -- а это может менять его состояние, и нарушать те инварианты, на которые вы рассчитывали, проектируя класс.
Я как-то сразу принял такую версию, хотя сходу не мог придумать очевидного способа как-то некорректно вмешаться в работу монитора. Вроде бы я придумал какой-то пример когда внешнее воздействие приводило к дедлоку, но вспомнить его мне не удается.
Но вот вчера у меня, наконец, сложился явный и четкий пример. Итак, код:
на первый взгляд кажется, что код в строках 17-18 никогда не может быть выполнен. Оба метода синхронизированы, никакой посторонний поток не может влезть, пока текущий поток внутри run(Callable). Однако, сломать этот объект крайне просто:
object.wait() должен вызываться внутри synchronized(object), и, на время ожидания он отпускает монитор. То есть пока поток thread ждет 2 секунды на мониторе runner этот монитор свободен, несмотря на то, что выше по стеку есть synchronized(runner). И в эти 2 секунды любой другой поток может захватить этот монитор -- что мы и делаем, демонстрируя нарушение инварианта класса.
Какой отсюда вывод? Вывод такой: если вы используете callback интерфейсы -- т.е. если ваш код выполняет внутри себя какой-то другой код, пришедший "со стороны" -- вы должны делать это либо вне синхронизации, либо использовать монитор синхронизации, до которого клиентский код не сможет добраться, вроде
На данный момент я не вижу других способов (кроме callback), как "открытая" синхронизация с помощью synchronized(this) может нарушить инкапсуляцию. Если класс колбэки не использует -- можно использовать синхронизированные методы спокойно.
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 коде синхронизированный метод выглядит заметно компактнее, чем метод, все тело которого обернуто в синхронизированный блок. Может, разработчики языка тем самым хотели на что-то намекнуть?
Вольный перевод отсюда (сноска в конце страницы)
Ну так и в 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). Когда нужно отправить данные пользователю ваш код просто вызывает что-то вроде
Другой вариант -- всем известные генераторы, как замена итераторам.
В общем, штука удобная и интересная. К тому же, если верить автору, еще и достаточно быстрая. Если она в самом деле будет включена в jdk1.7 -- будет приятно.
Источник: http://classparser.blogspot.com/ via Levin Matveev blog
Так вот, сопроцедуры. Это такая штука, когда, наряду с обычным 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
Нашел себе подсветку синтаксиса для скриптов в блоге.
В теле поста пишем
и в результате получаем:
преобразование делает JavaScript, подгружаемый в начале страницы. В предыдущих постах я использовал highlighter, выдающий готовый html-код, который надо было копипастить в пост. В итоге получалось, что код из двух строчек с подсветкой занимает пол страницы, причем собственно код в этой мешанине html-тегов уже совершенно не видно -- неудобно.
Подробности здесь: Awesome syntax highlighting
В теле поста пишем
<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
* 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.
Джабберовский XMPP протокол (и его реализация в Smack) произвел хорошее впечатление. Простой и расширяемый. В будущих проектах собираюсь попробовать предоставлять network interface через него -- на пару к обычному HTTP.
5 марта 2010 г.
Запуск внешних программ
Недавно в DataGuard пришлось разбираться с запуском внешних программ из java. Некоторые вещи оказались довольно нетривиальны, так что я решил поделиться.
На первый взгляд, все достаточно просто -- для простых случаев есть
К сожалению, ничего подобного. Несмотря на то, что API выглядит просто и очевидно, корректное его использование совсем не просто, и не очевидно. Какие конкретно подводные камни нас ждут?
Главный из них -- потоки ввода-вывода (IO streams). У порождаемого процесса нет терминала, к которому он привязан, его stdin, stdout, stderr выдаются порождающему процессу -- то есть, нам. Причем обрабатывать их -- наша обязанность. Потоки, созданные ОС имеют ограниченный размер буфера. Если, к примеру, буфер stdout для запущенного процесса заполнен, со стороны java никто его не читает (==не освобождает) а процесс настойчиво хочет что-то вывести -- то процесс просто окажется заблокирован на IO, и будет ждать, пока stdout кто-нибудь освободит. Если мы не предусмотрели в java код, читающий
Самый опасный момент здесь в том, что размер буфера заранее не определен. Поэтому приложение может в одном случае работать как часы, а в другом -- непонятно зависать.
Конкретный пример: в обычном, штатном режиме работы внешний процесс выдает одну-единственную строчку "Ок" и завершается. Строчка вполне влезает в буфер, поэтому код
работает корректно. Но наступает день Х, когда звезды складываются неудачно. И процесс завершается с ошибкой. И, как и положено уважающей себя программе, старается эту ошибку максимально подробно описать. И пытается вывести в stdout простыню текста, превышающую размер буфера. Вуаля -- процесс ждет на выводе, java-программа -- на
На мой взгляд -- это пример плохо спроектированного API. Простая вещь -- запустить внешний процесс не заморачиваясь с его выводом -- делается весьма нетривиально. Более того, из самого API это никак не следует. Да, в документации к Process это прописано, но я считаю, что хороший API это такой, использование которого, по крайней мере для простых задач, очевидно без документации. Можно было бы дополнить контракт, например, так: "если клиент не запросил
Но наши друзья из Sun этого не сделали, так что приходится отдуваться самим: ProcessRunner
Что делает: берет сконфигурированный ProcessBuilder, создает внешний процесс, запускает асинхронно "помпы", прокачивающие его потоки ввода-вывода либо в пустоту (если пользователь ничего не задал) либо из/в заранее заданные потоки. Метод
В этом примере ввод-вывод
Здесь stdout/stderr будут считаны в предоставленные нами потоки. Обратите внимание, что если флаг
Больше примеров использования можно посмотреть в тестах ProcessRunnerTest
На первый взгляд, все достаточно просто -- для простых случаев есть
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
25 февраля 2010 г.
Data Oriented Programming вместо ООП
Пара интересных статей:
Musings on Data Oriented Design
Pitfalls of Object Oriented Programming (GCAP)
Суть вопроса, который поднимают авторы, такова -- за последние 30 лет развития IT скорость выполнения команд процессором выросла почти в 50000 раз, но время ответа памяти на запрос (memory latency) уменьшилось всего лишь раз в 10. В итоге мы получаем, что "стоимость" запроса к памяти (в циклах процессора) выросла примерно в 400 раз.
При этом современные компиляторы очень хорошо умеют работать с кодом -- фактически, машинный код, получаемый на выходе из компилятора может вообще не иметь ничего общего, с тем, что изначально было написано человеком -- кроме того, что делает то же самое. Но компиляторы очень мало что делают с данными -- размещение данных в памяти (data layout) генерируемое компилятором фактически очень мало отличается от того, что задекларировал программист в коде (разве что компилятор добавит туда от себя что-то -- vtable например, да выровняет по границам машинных слов). Хотя при современном состоянии дел с memory latency было бы гораздо выгоднее оптимизировать расположение данных под выполняемые задачи.
В частности, объектный подход, предлагающий группировать данные, относящиеся к одному объекту вместе, часто сильно уменьшает эффективность программы за счет того, что данные, нужные конкретному алгоритму, оказываются разбросанными по памяти. Итог - большое количество кэш-промахов, загрязнение кэша реально не нужными сейчас данными, большое количество простоев процессора в ожидании данных из основной памяти.
Вместо этого предлагается программисту (пока компиляторы не научились) продумывать размещение данных в памяти исходя из того, как они будут обрабатываться. В частности, в презентации (pdf) Том Албрехт показывает, как им удалось в 3-4 раза ускорить обновление-прорисовку дерева объектов сцены (scene tree) за счет изменения компоновки данных (там, правда, еще про предсказание ветвлений упомянуто)
Musings on Data Oriented Design
Pitfalls of Object Oriented Programming (GCAP)
Суть вопроса, который поднимают авторы, такова -- за последние 30 лет развития IT скорость выполнения команд процессором выросла почти в 50000 раз, но время ответа памяти на запрос (memory latency) уменьшилось всего лишь раз в 10. В итоге мы получаем, что "стоимость" запроса к памяти (в циклах процессора) выросла примерно в 400 раз.
При этом современные компиляторы очень хорошо умеют работать с кодом -- фактически, машинный код, получаемый на выходе из компилятора может вообще не иметь ничего общего, с тем, что изначально было написано человеком -- кроме того, что делает то же самое. Но компиляторы очень мало что делают с данными -- размещение данных в памяти (data layout) генерируемое компилятором фактически очень мало отличается от того, что задекларировал программист в коде (разве что компилятор добавит туда от себя что-то -- vtable например, да выровняет по границам машинных слов). Хотя при современном состоянии дел с memory latency было бы гораздо выгоднее оптимизировать расположение данных под выполняемые задачи.
В частности, объектный подход, предлагающий группировать данные, относящиеся к одному объекту вместе, часто сильно уменьшает эффективность программы за счет того, что данные, нужные конкретному алгоритму, оказываются разбросанными по памяти. Итог - большое количество кэш-промахов, загрязнение кэша реально не нужными сейчас данными, большое количество простоев процессора в ожидании данных из основной памяти.
Вместо этого предлагается программисту (пока компиляторы не научились) продумывать размещение данных в памяти исходя из того, как они будут обрабатываться. В частности, в презентации (pdf) Том Албрехт показывает, как им удалось в 3-4 раза ускорить обновление-прорисовку дерева объектов сцены (scene tree) за счет изменения компоновки данных (там, правда, еще про предсказание ветвлений упомянуто)
15 февраля 2010 г.
JCaptcha
Разбирался на прошлой неделе с JCaptcha. Хорошая библиотека, но документация -- отвратная. Огромный разрыв между beginners guide и реальным использованием никак не покрыт. То есть начинаешь читать 5 minutes integration -- все вроде просто. Шаг чуть дальше -- нигде кроме как в исходниках инфы не найдешь.
Последний раз так с MFC мучался в дремучих 90-х. Думал уже, в мире джава таких проектов нет...
Вот, для примера, "переведенный" мною с языка spring-configuration на java код инициализации службы генерации капч:
Последний раз так с MFC мучался в дремучих 90-х. Думал уже, в мире джава таких проектов нет...
Вот, для примера, "переведенный" мною с языка spring-configuration на java код инициализации службы генерации капч:
private static final ImageCaptchaService instance;// = new DefaultManageableImageCaptchaService(); private static final int FONT_SIZE_MIN = 25; private static final int FONT_SIZE_MAX = 29; private static final int HEIGHT = 40; private static final int WIDTH = 200; static { //Шрифт, которым будет написана капча. Размер не важен final Font font = new Font( "Times New Roman", Font.PLAIN, 12 ); //разные буквы разными шрифтами разного размера final FontGenerator fontGen = new RandomFontGenerator( FONT_SIZE_MIN, FONT_SIZE_MAX, new Font[]{ font } ); //...и разными цветами final ColorGenerator colorGen = new RandomListColorGenerator( new Color[]{ Color.RED, Color.GREEN, Color.BLUE, Color.BLACK } ); //однотонный белый фон-подложка. здесь, по-сути, задается размер капчи final BackgroundGenerator bgGen = new UniColorBackgroundGenerator( WIDTH, HEIGHT, Color.WHITE ); //вырезаем слова от 6 до 9 букв final TextPaster textPaster = new RandomTextPaster( 6, 9, colorGen ); //список слов можно задать final FileDictionary dict = new FileDictionary( "toddlist" ); final WordGenerator wordGen = new DictionaryWordGenerator( dict ); //final WordGenerator wordGen = new ComposeDictionaryWordGenerator( dict ); final ComposedWordToImage wordToImage = new ComposedWordToImage( fontGen, bgGen, textPaster ); final CaptchaFactory factory = new GimpyFactory( wordGen, wordToImage ); final GenericCaptchaEngine engine = new GenericCaptchaEngine( new CaptchaFactory[]{ factory } ); instance = new GenericManageableCaptchaService( engine, 300, 500000, 1 ); }
12 февраля 2010 г.
JavaMail + SSL
Продолжение разбирательств. Краткий итог: итак, самый простой и надежный метод работы с SSL -- использовать
Все остальные настройки как в обычном smtp (только порт обычно 465 вместо 25). Работает с gmail -- проверено
Подробное описание:
Вариант с
не работает. Точнее, не совсем работает -- не так, как ожидается. Раскопки в исходниках JavaMail (благо, они доступны) показывают, что свойство
В числе прочего это означает, что если вы вместе с mail.transport.protocol = smtps укажете, как описано в документации, свойства типа mail.smpts.host -- они будут проигнорированы (использоваться будут свойства типа mail.smtp.host) и JavaMail будет ломиться на localhost (который используется в качестве адреса smtp сервера по-умолчанию).
System.setProperty("mail.smtp.ssl.enable","true");
Все остальные настройки как в обычном smtp (только порт обычно 465 вместо 25). Работает с gmail -- проверено
Подробное описание:
Вариант с
System.setProperty("mail.transport.protocol", "smtps")
не работает. Точнее, не совсем работает -- не так, как ожидается. Раскопки в исходниках JavaMail (благо, они доступны) показывают, что свойство
mail.transport.protocol
используется только методом Session.getTransport()
(который без аргументов). Но самой библиотекой этот метод не используется -- предполагается, что его вызывает клиентский код явно. Если вы отправляете почту через явно создаваемый транспорт, и получаете его через session.getTransport()
-- свойство mail.transport.protocol
будем иметь эффект. Если же вы, как я, отправляете почту через статический метод Transport.send()
, то внутри него библиотека ищет подходящий транспорт через вызовом getTransport(Address), что отсылает нас к свойству mail.transport.protocol.rfc822
(rfc822 -- такой "тип адреса" у адреса электронной почты). Если хочется переопределить транспорт для такого типа адресов на smpts -- надо писать System.setProperty("mail.transport.protocol.rfc822", "smtps");, либо использовать
session.setProtocolForAddress("rfc822", "smpts")
. Если просто написать mail.transport.protocol = smtps, то Transport.send() это указание проигнорирует. В числе прочего это означает, что если вы вместе с mail.transport.protocol = smtps укажете, как описано в документации, свойства типа mail.smpts.host -- они будут проигнорированы (использоваться будут свойства типа mail.smtp.host) и JavaMail будет ломиться на localhost (который используется в качестве адреса smtp сервера по-умолчанию).
JavaMail + SSL
Намучался искать нормальный (авторитетный) источник информации по отправке secure mail с помощью JavaMail. Secure -- это который через SSL и порт 465 (обычно).
В итоге нашел описание на самом java.sun.com (ссылка в заголовке).
Если я их правильно понял, есть 3 основных варианта (для самой свежей версии JavaMail -- 1.4.3)
Первый вариант просто указать системное свойство mail.smtp.ssl.enable=true. Провайдер вроде как должен сам понять, если имеет дело с секьюрным сервером.
Второй вариант -- последней версии можно использовать имя протокола smtps (pops, imaps) -- и, соответственно, все свойства задавать в виде mail.smtps.host, mail.smtps.port, etc.
Последний, самый старый вариант -- инсталлировать SSLSocketFactory через системное свойство mail.smtp.ssl.socketFactory.class
И еще гмайл и похожие сервера требуют установки свойства mail.smtp.starttls.enable=true.
В общем, JavaMail как обычно напоминает шаманские пляски.
В итоге нашел описание на самом java.sun.com (ссылка в заголовке).
Если я их правильно понял, есть 3 основных варианта (для самой свежей версии JavaMail -- 1.4.3)
Первый вариант просто указать системное свойство mail.smtp.ssl.enable=true. Провайдер вроде как должен сам понять, если имеет дело с секьюрным сервером.
Второй вариант -- последней версии можно использовать имя протокола smtps (pops, imaps) -- и, соответственно, все свойства задавать в виде mail.smtps.host, mail.smtps.port, etc.
Последний, самый старый вариант -- инсталлировать SSLSocketFactory через системное свойство mail.smtp.ssl.socketFactory.class
И еще гмайл и похожие сервера требуют установки свойства mail.smtp.starttls.enable=true.
В общем, JavaMail как обычно напоминает шаманские пляски.
5 февраля 2010 г.
String.trim()
Удивительно, но String.trim() не отбрасывает символ неразрываемого пробела 0x00A0. Я был просто поражен -- и нет даже никакой опции, как это убрать.
Более того, \s в регулярках тоже не считает 0x00A0 пробельным символом! Пришлось извращаться в духе [\s\u00A0]
Более того, \s в регулярках тоже не считает 0x00A0 пробельным символом! Пришлось извращаться в духе [\s\u00A0]
3 февраля 2010 г.
Gallery of Processor Cache Effects
Блог разработчика из Микрософт Игоря Островского. Влияние кэша процессора на производительность
Подписаться на:
Сообщения (Atom)