четвер, 3 листопада 2011 р.

Вдогонку Zookeeper Tech Talk

Свежак с очередного тектолка в Cogniance - презентация с моего доклада о Zookeeper. Спасибо Стэну Шилко - тема очень клевая. Заметил за собой страсть к выступлениям - зарядило очень сильно, надеюсь всем тоже было интересно, хотя может и не везде понятно :)

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

Из ответов на вопросы - нашел две реализации высокоуровневых примитивов: KeptCollections и Menagerie. Они больше дополняют друг друга, чем конкурируют, но более близко с ними познакомлюсь позже.

Из планов на последующие Java Evenings - обзор KeptCollections и Menagerie, попробую покодить что-то свое и представить кое какие рекомендации по использованию Zookeeper'а

Как обычно ссылка на слайды: http://goo.gl/Pwcfj 



понеділок, 17 жовтня 2011 р.

Вдогонку java.util.concurrent Tech Talk

Во вторник 11-го октября отгремел очередной Java Evening в Cogniance c пиццей, пивом, интересными людьми и интересными темами. Среди героев дня был и я со своим докладом по java.util.concurrent, В прошлом (и первом) Java Evening у меня не вышло поучаствовать, зато на этот раз я без лишней скромности откусил из общего времени целых 80 минут вместо положенных 15ти!

Выступать мне не впервой - участвовал когда-то в конференции OSDN, но это было до войны при немцах. Ну и конечно же, защита диплома - чем не бесценный опыт :)

Но в качестве Java-разработчика - это мое первое публичное явление народу. Да и тема была не самая легкая - почему то java.util.concurrent, хоть она и у всех на слуху, молча обходят стороной.  В общем пришлось постараться - подготовил и теории выше крыши, и примеров приготовил для иллюстрации.

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

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

Теперь без лишней скромности могу сказать, что готов читать книгу Гетца. Заказал ее на следующий же день. Мультипоточность на этом для меня только начинается - впереди архипелаг Java Memory Model, который и станет основной темой моих будущих писаний.

Nevertheless, выкладываю слайды и примеры, дабы увековечить этот великий исторический момент.

Качаем, смотрим, изучаем, спрашиваем. Сразу скажу что AtomicCyclicCounter - неверно написан полностью :) Пишите свои варианты почему, выигравшему - автомобиль!

Так же в презентации, на третьем слайде - Они. Как зовут каждого, тоже пишите в комментарии, первому счастливчику - автомобиль!

Смотреть слайды 
Смотреть примеры - это SVN-репозиторий, можно сделать read-only checkout










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

Avoid SSLException with all-trusting TrustManager

Anyone who stumbled upon SSL in their day-to-day Java joy, know that by default Java is very picky about certificates on the other end. Whether you want to submit a HTTP request to the site with expired certificate (yes, people change them, and they may forget to do that) or happen to work against an endpoint with self-signed certificate (not everyone are ready to blindly give hundreds of bucks for those expensive bits), you'll be kindly refused (if SSLException can be described as kind)

In such cases you should find some way around the issue to make things work.Whenever its about the remote service, I preferred to maintain a separate truststore for the project, but having a headache of keystores during development is a motivation killer.

Here is how you can avoid JVM to check peer certificates for validity. The below code will accept any SSL certificate presented, this means - no security unless you know what you're doing. Generally this property switch will be of great use at development time or debugging time.

import java.security.cert.X509Certificate;

// ...

if ("true".equalsIgnoreCase(System.getProperty("trust.ignore", "true"))) {
	TrustManager[] trustAllCerts = new TrustManager[]{
		new X509TrustManager() {
			public X509Certificate[] getAcceptedIssuers() {
				return null;
			}
			public void checkClientTrusted(
				X509Certificate[] certs, String authType) {
			}
			public void checkServerTrusted(
				X509Certificate[] certs, String authType) {
			}
			public boolean isClientTrusted( X509Certificate[] cert) {
				return true;
			}
			public boolean isServerTrusted( X509Certificate[] cert) {
				return true;
			}
		}
	};

	// Install the all-trusting trust manager
	try {
		SSLContext sc = SSLContext.getInstance("SSL");
		sc.init(null, trustAllCerts, new java.security.SecureRandom());
		HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
	} catch (Exception e) {
	}
}

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

java.util.concurrent Tech Talk

Я тут подписался на Tech Talk о java.util.concurrent с целью нести свет в массы. Тема довольно интересна мне и довольно давно, но играть в КО не хочется и тратить время на то, что всем известно, когда можно с гораздо большим удовольствием есть пиво - тоже. Поэтому я добросовестно подготовил предварительный список тезисов для освещения. Комментарии и предложения - приветствуются.

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

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

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

Итак агенда:

0. Интро
Введение в многопоточность - зачем, почему, когда и как - в общем короткое вступление, без которого никак
Кратко о базовых конструкциях языка для поддержки многопоточности - о Thread, Runnable, мониторах, synchronized-блоках. Тут же пример кода. Пару слов о Vector, Hashtable и synchronized-коллекциях
Мотивация к старту JSR166, картинки с мордами Дага Ли, Брайана Гетца, Дийкстры, Хоара и Бринч-Хансена

1. Подходы
Блокирующая и неблокирующая многопоточность - в чем разница

1.1. Блокирующая многопоточность
ReentrantLock vs native Java locks
Lock & Condition, тут же упомянем TimeUnit и как в ХХI веке спят потоки
Semaphore

1.2. Неблокирующая многпоточность
Atomic типы и как они устроены, CAS, пример кода
Атомарные операции на сложных shared state, про разные AtomicReference, пример кода
Краткое сравнение подходов

2. Распределение работы

Executor, ExecutorService, Сallable, Thread-пулы
Fork/Join framework (привет семерочка)
пример кода

3. Конкуррентные типы данных в JSR166

Опять слегка вспомним про Atomic типы
CopyOnWriteArrayList, ConcurrentHashMap, ConcurrentSkipListMap, ConcurrentSkipListSet, CopyOnWriteArraySet
перформанс Vector, Hashtable vs sync collections vs CopyOnWriteArrayList, ConcurrentHashMap - где то в закладках были графики
Немного об итераторах
Генерация случайных чисел, ThreadLocalRandom

4. Очереди
blocking queues and deques

5. Синхронизация потоков
CountDownLatch, CyclicBarrier, Exchanger

6. Что еще есть в мире Java кроме JSR166 - вот это не обещаю что успею, но если очень будет интересно, постараюсь
Disruptor http://code.google.com/p/disruptor/
NonBlockingConcurrentHashMap, http://sourceforge.net/projects/high-scale-lib

7. Чего ждать в Java 8 - тоже по заявкам
jsr166e: http://dhruba.name/2011/08/28/jsr166e-upcoming-java-util-concurrent-apis-for-java-8/

8. Q&A не планирую - все вопросы по ходу дела, в заключение забросаю книгами и ссылками.

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

Lock та Condition

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

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

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

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

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

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


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

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

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


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

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

import java.util.concurrent.TimeUnit;

public class TimeUnitDemo {

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


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

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

public class UninterruptiblyWaitForTheEnd {

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

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

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

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

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

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


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


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

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

* * *

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


До зустрічі!

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

2000 pageviews! Yikes!


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

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

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

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

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

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

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

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

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

Hucking Bot for Assembla

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

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

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

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

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

The picture is worth thousand words:




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

What else? Yay! Skype support coming soon!

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

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

Atomic-типы

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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


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

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

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

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

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

Jackson Pretty Printing

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

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

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

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

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

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

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

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

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

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

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

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

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

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


середа, 9 березня 2011 р.

Многопоточность. Wait / notify и приоритеты при захвате монитора. Spurious wakeups

javenue поставил точку в нашем споре, написав замечательную статью. Суть спора сводится к тому что поток, выходящий из wait-set монитора, имеет приоритет над другими потоками из очереди этого монитора.

Читайте детальное описание проблемы здесь

вівторок, 8 березня 2011 р.

Как выглядит счетчик в байт-коде?

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

Что такое байт-код?

Байт-код - это инструкции интерпретатору Java-машины, своего рода ассемблерный язык.
Рассмотрим простой класс - счетчик, значение которого можно увеличить или уменьшить.

// Counter.java
public class Counter {
    private int counter = 0;

    public int inc() {
        return ++counter;
    };

    public int dec() {
        return --counter;
    }

    public int getValue() {
        return counter;
    }

    public static void main(String[] args) {
        Counter c = new Counter();
        System.out.println(c.inc());
    }
}

Скомпилировав его в Counter.class, мы можем теперь декомпилировать класс в байт-код с помощью утилиты javap

grim@blackbox:~/projects/privy/trunk/scl/sourcepath$ javap -c Counter

Compiled from "Counter.java"
public class Counter extends java.lang.Object{
public Counter();
  Code:
   0: aload_0
   1: invokespecial #1; //Method java/lang/Object."<init>":()V
   4: aload_0
   5: iconst_0
   6: putfield #2; //Field counter:I
   9: return

public int inc();
  Code:
   0: aload_0
   1: dup
   2: getfield #2; //Field counter:I
   5: iconst_1
   6: iadd
   7: dup_x1
   8: putfield #2; //Field counter:I
   11: ireturn

public int dec();
  Code:
   0: aload_0
   1: dup
   2: getfield #2; //Field counter:I
   5: iconst_1
   6: isub
   7: dup_x1
   8: putfield #2; //Field counter:I
   11: ireturn

public int getValue();
  Code:
   0: aload_0
   1: getfield #2; //Field counter:I
   4: ireturn

public static void main(java.lang.String[]);
  Code:
   0: new #3; //class Counter
   3: dup
   4: invokespecial #4; //Method "<init>":()V
   7: astore_1
   8: getstatic #5; //Field java/lang/System.out:Ljava/io/PrintStream;
   11: aload_1
   12: invokevirtual #6; //Method inc:()I
   15: invokevirtual #7; //Method java/io/PrintStream.println:(I)V
   18: return

}

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

Дальше приведу краткий разбор метода inc():

0 aload_0                              ; загружаем ссылку на экземпляр обьекта из переменной и толкаем ее в стек
 1 dup                                  ; копируем верхний элемент стека и толкаем его в стек
 2 getfield #2 <counter.counter>        ; извлекаем из стека ссылку, получаем у нее поле counter (в пуле констант имеет номер #2), и толкаем это значение в стек
 5 iconst_1                             ; толкаем в стек константу-единицу
 6 iadd                                 ; извлекаем два верхних элемента стека, выполняем сложение, результат толкаем в стек
 7 dup_x1                               ; копируем верхний элемент стека и размещаем его перед вторым элементом в стеке
 8 putfield #2 <counter.counter>        ; извлекаем из стека последовательно значение и ссылку на экземпляр, устанавливаем новое значение поля counter
11 ireturn                              ; извлекаем из стека значение и интерпретатор передает контроль вызывающему методу.


Как видно одна операция инкремента скомпилировалась в целых 6 инструкций (со 2 по 8). Это и есть пресловутая неатомарность операции. Один из последствий неатомарности при вызове метода из нескольких потоков возможно несогласованное чтение одного и того же значения, что приведет к возврату одного и того же значения обоими потоками, что противоречит контракту inc().

На этом завершаю, ибо нашел замечательную статью Антона Архипова Java Bytecode Fundamentals (на английском языке). Лучше, чем он, пожалуй, про байт-код расписать не смогу.

Ссылки

Bytecode Basics A First Look at the Bytecodes of the Java Virtual Machine by Bill Venners

The JavaTM Virtual Machine Specification Second Edition Tim Lindholm Frank Yellin

Java Bytecode Fundamentals by Anton Arhipov

четвер, 3 березня 2011 р.

Amazon Kindle: перші враження

Став щасливим власником читалочки Kindle. Замовив у неділю і вже у четвер кур’єр привіз пакуночок прямо мені на роботу. Що я можу сказати? Сервіс у Амазона на висоті. Незважаючи на певну зневагу до нашої країни більшості закордонних компаній, сервіс від Амазона приємно вразив. Мабуть чи не вперше в житті, річ, яку придбав, приносить самі лише позитивні враження, від найпершого моменту як я зайшов на Амазон у неділю і до цього самого моменту часу.

Загальний вигляд

Фотки в мене немає, знайдете валом їх в інтернеті. Скажу лише, що мій графітовий. Взяв до нього ще обкладинку з підсвіткою - найпопулярніший, з їх слів, комплект. Обкладинка оснащена діодною підсвіткою, яка на перший погляд, слабенька, але вночі в темноті в маршрутці читати було дуже комфортно, навіть комфортніше, ніж у нашому освтленому жовтавим світлом метро.

Контрастність екрану дуже залежить від зовнішнього освітлення, іноді читати дуже важко, особливо при приглушеному жовтуватому світлі. Чесно кажучи, чекав трохи більше контрасту, але такий у більшості пристроїв, і Амазон тут не виявився винятком.

Зручно, що кнопки листання книги дубльовані з обох боків пристрою. Сама QWERTY-клавіатура не з найзручніших, але не є зайвою ні в якому разі. Без неї набір адрес веб-сторінок та підключення до Wi-Fi були б катастрофічно незручними.

Зв’язок

З інших приємностей порадувала підтримка 3G прямо з коробки. Я очікував, що доведеться оплачувати щомісячні рахунки за інет, - нічого подібного. Пристрій одразу поліз на Амазон, зареєструвався, з мого боку налаштувань взагалі не було. Це було б навіть не в тему, бо я був занадто зайнятий самим девайсом, вертінням його у руках та показами для колег )

Як я розумію, Амазон робить ставку на продажі електронних книг онлайн, і це, в принципі, досить влучний розрахунок. Я не втримався теж і, - трохи для фану, а трохи й для діла - придбав кілька книг, які давно хотів, але не наважувався, замовити - все таки витратити 20 баксів за доставку книги вартістю 30 баксів - трохи занадто.

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

Пітримка Wi-Fi також на борту - без проблем заграла моя домашня мережа.

Функціональність

На борту також експериментальний веб-оглядач та MP3-плеєр. Сторінки рендеряться в цілому коректно, хоча послухати музику через флеш-програвачі не вийде.
Сьорфінг настільки зручний, наскільки може бути зручною навігація клавішами. Інтернет не обмежується, що для парубка з Украіни дивно і приємно.

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

Формати

Болюча тема, Підтримуються PDF, TXT, MOBI та ще кілька. Я вже очікую, як буду прикручувати підтримку fb2, яка відсутня. Що трохи напрягло - так це те, що скачати PDF з інтернету не вдалося через внутрішнє обмеження в браузері - довелося висилати поштою.

Загалом

Довольний, як слон. Скільки годин в транспорті тепер не втрачені для мене - важко уявити, а цікавих книжок вистачить на все життя!

понеділок, 28 лютого 2011 р.

Invocation of var-arg method via Reflection API

When trying to invoke the method accepting array or variable arguments, you should take few things into account.

The argument type to be matched is an array of variable argument type. So if you declare String... or String[] in your method's signature, you should match for String[].class in both cases.

// Test.java
package milkyway.Test;

public class Test {
    public static void main(String... args) {
        System.out.println("yo");
    }
}
 
// A long time later in a galaxy far far away
ClassLoader scl = ClassLoader.getSystemClassLoader();
Class c = scl.loadClass("milkyway.Test");
Method m = c.getMethod("main", String[].class);

Later, you may want to invoke the method, and you can be tempted to pass null, or an array to your method, but watch out. Neither of these will work

m.invoke(null);
m.invoke(null, null);
m.invoke(null, new String[0]);
m.invoke(null, new String[] { "one", "two", "three" });

All will result in java.lang.IllegalArgumentException with various reasons.

To make the invoke() happy, you should explicitly cast the argument to Object type:

m.invoke(null, (Object)null);
m.invoke(null, (Object)new String[0]);

If you want to pass no variable arguments in the case of var-arg method, pass null or empty array as we just did. This tells invoke() to call the method with empty list of variable arguments.

Quick merge of two arrays in Java

Given two arrays, the goal is to get a concatenation of two. You may go through lands of JCF if you want, but also you may be puzzled if this can be done without it. I did.

Here is the code:

public class Utils {
    @SuppressWarnings("unchecked")
    public static <T> T[] mergeArrays(T[] a, T[] b) {

        if (a == null || a.length == 0) return b;
        if (b == null || b.length == 0) return a;

        T[] m = (T[]) Array.newInstance(a.getClass().getComponentType(), a.length + b.length);

        System.arraycopy(a, 0, m, 0, a.length);
        System.arraycopy(b, 0, m, a.length, b.length);

        return m;
    }
}

The first key point here, that we create new array instance with the help of Reflection API, using Array.newInstance() method.

The second key point is we obtain array element type at runtime with Class.getComponentType() method.

And final point is we use System.arraycopy() method to copy array contents, which is native call and is fastest you can get on JVM.

This doesn't work on arrays of primitive types, but version for primitives is as easy as search and replace T with required primitive type.

неділя, 27 лютого 2011 р.

Cертификат по Java!

Получил свой первый сертификат по Java! Тренироваться на тестах намного полезнее для сдачи чем чтение JLS. Будете читать JLS - станете хорошими читателями JLS, ну а я хочу хорошо сдать экзамен.

субота, 26 лютого 2011 р.

java.util.concurrent. Часть первая. Почему?

Часть первая, в которой множеством слов раскрывается смысл существования этого API
Эта статья, хоть и не является прямым переводом, основана на статье Брайана Гетца Concurrency in JDK 5.0



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

Однако потоки были реализованы современными операционными системами задолго до массового появления многоядерных процессоров. Зачем же нужны потоки на однопроцессорных системах?

Их несколько
  1. Отзывчивый графический интерфейс. Длительные операции могут быть выполнены в отдельном потоке, в то время как приложение способно обслуживать события от клавиатуры и мышки. 
  2. Возможность более эффективного использования ресурсов: процессора, памяти, жесткого диска и сети. В то время как один из потоков простаивает, ожидая завершения операции чтения файла, второй поток может в это время устанавливать сетевое соединение с клиентом, а третий - обрабатывать какой нибудь запрос. 
  3. Простота модели “поток-на-запрос” и идея фоновых системных процессов. Оставляя детали распределения ресурса процессора на совесть операционной системы, мы получаем возможность сконцентрировать программу на обработки самого запроса. Мы также можем выделить определённые задачи (например, сборку мусора или системный отладчик) в отдельные потоки, и таким образом “незаметно” добавить новые свойства программе. 
Первые два пункта в основном понятны, третий распишу более подробно.

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

Однако алгоритм не создается для одного запроса. Если требуется обрабатывать большое количество входных данных, то возникает желание либо уменьшить общее время их обработки, либо увеличить скорость их обработки (и таким образом увеличить пропускную способность обработчика). Самый простой способ в данном случае - поставить больше однопоточных алгоритмов и заставить их работать параллельно. Возможно, придется как-то согласовывать доступ к общим ресурсам и координировать их выполнение, но это обычно значительно проще чем разработка нового алгоритма, который за раз может обработать больше чем один набор данных (такой алгоритм нужно не только разработать, но еще и доказать). Это и понимается под простотой модели “поток-на-запрос”.

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

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

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

Поскольку в Java данные - это объекты, то к классам этих объектов, которые будут обрабатывать вызовы из нескольких потоков, предъявляется требование многопоточной безопасности (thread safety). Что это значит? Это. конечно же значит, что вызовы методов обьекта будут безопасными, что, конечно, правильно, но нисколько не помогает нам понять, что же такое потокобезопасность класса.

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

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

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


До версии 5.0 единственными средствами JDK для придания классам свойств потокобезопасности были синхронизация выполнения методов и блоков с помощью ключевого слова synchronized, а взаимодействие осуществлялось с помощью внутренних блокировок объектов и volatile-переменных. Этого оказалось недостаточно для миллионов современных программистов, поэтому наиболее инициативный из них, по имени Даг Ли написал книгу Concurrent Programming in Java. Многие описанные в книге идеи легли в основу JSR 166: Concurrency Utilities и в результате под общим зонтиком java.util.concurrent API, в JDK 5.0 было добавлено множество новых высокоуровневых средств для облегчения задач взаимодействия потоков: блокирующие структуры данных, различные средства синхронизации и много других интересных вещей, которые я рассмотрю в следующей части.

вівторок, 22 лютого 2011 р.

Кеши значений в Java

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

В общем, уже пару недель как я учу кунг-фу, поэтому решил собрать в одном месте информацию по внутренним кешам Java.

Собственно их мне известно три.

Кеш строк

Многие знают, а кто не знает - узнают, что в Java строковые константы часто оказываются равны. Это называется интернированием. Например

public class Nan {
    public static void main(String[] args) {
        CharSequence cs = "hello";
        String s = "hello";
        System.out.println(cs == s);
    }
}

--
true

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

С другой стороны, если вам все же нужно различать одинаковые строки, пользуйтесь оператором new.

public class Nan {
    public static void main(String[] args) {
        String s1 = new String("hello");
        String s2 = new String("hello");
        System.out.println(s1 == s2);
    }
}

-- 
false

Интернирование может быть сделано вручную с помощью метода intern().

public class Nan {
    static String concat(String x, String y) {
        return x + y;
    }
    static void showTruth(String x, String y) {
        System.out.println("ref: " + (x == y) + "\tintern: " + (x == y.intern()));
    }

    public static void main(String[] args) {
        String s = "Hello";
        showTruth(s, "Hello");
        showTruth(s, new String("Hello"));
        showTruth(s, concat("He", "llo"));
    }
}
--
ref: true intern: true
ref: false intern: true
ref: false intern: true


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

Кеши целочисленных оберток

В каждом из классов Long, Integer, Short и Byte присутствует внутренний кеш ссылок на значения от -128 до 127, причем в каждом из классов он реализован по-разному. Лучи ненависти в сторону Sun направляют противники копипасты под негодующие крики ненавистников Java. Что интересно, в Sun JVM присутствует возможность во время выполнения изменить верхний (и только!) предел кеша для Integer (и только!) на произвольное значение с помощью опции -XX:AutoBoxCacheMax или выставив системный параметр java.lang.Integer.IntegerCache.high в необходимое значение

В общем и кратко:

public class Nan {
    public static void main(String[] args) {
        Integer i1 = 10;
        Integer i2 = 10;
        System.out.println(i1 == i2);

        i1 = 128;
        i2 = 128;
        System.out.println(i1 == i2);
    }
}

--
true
false

-- с -Djava.lang.Integer.IntegerCache.high=128
true
true

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

Кеш логических значений

У обертки Boolean также присутствует кеширование. Его valueOf всегда возвращает ссылку на один из двух внутренних экземпляров: Boolean.TRUE или Boolean.FALSE. В отличии от String у Boolean нету метода intern() поэтому бездумное использование конструктора приведет к перерасходу памяти. Лучше всего всегда-всегда использовать valueOf.

Заключение (censored)

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

неділя, 6 лютого 2011 р.

2NF для детей

Начало лирического отступления


Довольно интересно повторять все эти нормальные формы, отношения, зависимости. Сразу вспоминается Гриша, как я его не то чтобы особо любил, как преподавателя, но уважал. И курс его мне нравился, один из немногих который я сознательно учил. Помню мы с Костиком и Серегой отсканировали книжку Ульмана, может быть один из последних экземпляров, который кто либо видел :). Вот этот скан меня и выручает.


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


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


Конец лирического отступления

Что же такое вторая нормальная форма или 2NF? Так чтоб трехлетний ребенок понял...

Для начала разберемся в целях, которые преследует нормализация.

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

Например если у отношения ’Семья’ есть атрибут ’Дети’, мы можем легко сравнить две строки ’Вася’ и ’Аня’ и определить их лексикографический порядок. Но сравнивать строки ’Вася’ и ’Аня,Саша’ и определять их порядок бессмысленно, а попытка решить эту проблему без декомпозиции, приведет либо к введению правил на сравнение скаляров и списков, либо к усложнению языка формул. Логики предикатов первого порядка тут недостаточно. Поэтому 1NF требует, чтобы все значения были простыми значениями из домена.

То есть первая НФ имеет дело, со структурой значений записей.

Вторая (и третья) НФ имеет дело уже с ключами и зависимостями в схеме. Перечислим ее цели с пояснениями.


  1. Главной целью приведения ко второй нормальной форме есть желание избавиться от избыточности хранения данных и как следствие избежать аномалий модификации этих данных (аномалий изменения, вставки и удаления)
  2. Второй по порядку, но не по значению, целью нормализации в 2NF есть максимально разбить модель данных на отдельные отношения, чтобы их можно было комбинировать и использовать в выражениях новыми, не предусмотренными изначально способами.
  3. Минимизировать усилия по изменению схемы в случае необходимости. Чем меньше зависимостей внутри схемы, меньше изменений в ней потребуется при изменении модели данных.
  4. Понятность схемы для пользователя. Чем держать все данные в одной большой таблице, проще представить данные как несколько связанных и логически разделенных отношений. Это проще читать, воспринимать, проектировать и поддерживать. В конце концов, любая модель данных начинается на доске или бумаге в виде кружочков, блоков и линий, которые так любят рисовать дети и программисты.


Например, у нас есть схема

R = { 'Идентификатор', 'Название СД-Диска', 'Название группы' },

где первичным ключом является ’Идентификатор’, а альтернативным ключом - ’Название СД-диска’. Эта схема находится во 2NF, поскольку неключевой атрибут ’Название группы’ зависит только от ключа и не зависит от подмножества атрибутов этого ключа (которых собственно нет, см. ниже).

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

Вообще ставить вопрос о несоответствии 2NF можно только в случае если в схеме есть составные ключи. Схемы с простыми ключами как в примере всегда имеют 2NF. Указанная схема есть как раз пример такого случая, так как в ней оба ключа (а это ’Идентификатор’ и ’Название СД-диска’) простые, и подмножества атрибутов этих ключей пусты.

Несоответствие 2NF рассмотрим на схеме

R = { ’Название группы’, ’Название СД-диска’, ’Название песни’, 'Автор слов’, ’Композитор’ }

Одна и та же песня может входить в несколько дисков, также теоретически возможны одноименные альбомы с одноименными песнями у разных групп, например трибьюты. Поэтому ключом будет { ’Название группы’, ’Название СД-диска’, ’Название песни’ }. При этом атрибуты ’Автор слов’ и ’Композитор’ зависят от множества атрибутов { ’Название группы’, ’Название песни’ }. Это и есть нарушение 2NF.

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

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

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

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

R1 = { ’Название группы’, ’Название СД’, ’Название песни’ }
R2 = { ’Название группы’, ’Название песни’, ’Автор’, ’Композитор’ }

Обе схемы имеют 2NF, R1 - поскольку у нее нет неключевых атрибутов, а R2 - поскольку ’Автор’ и ’Композитор’ зависят от ключа { ’Название группы’, ’Название песни’ } и не зависят (функционально) от любого из атрибутов 'Название группы’ или ’Название песни’.

Проиллюстрируем на другом примере. У нас есть детали на складах. Все это представлено в таблице вида

----------------------------------------------
| ДЕТАЛЬ | СКЛАД | КОЛИЧЕСТВО | АДРЕС_СКЛАДА |
==================----------------------------

Ключом в данной схеме есть пара { ’Деталь’, ’Склад’ }, но ’Адрес_склада’ функционально зависит только от атрибута ’Склад’, то есть от подмножества атрибутов ключа. Поэтому требования 2NF несоблюдены. Чем плохо? Во-первых, адрес склада будет дублироваться для всех деталей на складе (избыточность) и если адрес изменится, нужно будет изменить все эти записи, чтобы сохранить целостность (могут возникнуть аномалии удаления). Во-вторых, если на складе еще нет деталей, то у нас нет возможности хранить адрес склада, так как схема такую ситуацию не предусматривает. Поэтому добаление нового склада невозможно (аномалия вставки), а вывоз деталей со склада означает, что мы потеряем информацию о его адресе (аномалия удаления).

Вот собственно и все.
Надеюсь было понятно, я же пошел разбираться с 3NF!

Що спільного між архітектурами UNIX та RDBMS?

Повторюю реляційну модель, реляційну алгебру та нормальні форми моделі даних.

Поняття нормальної форми 1NF, я думаю, є саме тим ключовим моментом, що дозволяє реляційним моделям домінувати ось уже 40 років. Ключовий момент 1NF - спрощення складених типів та ієрархічних моделей до плоских відношень зробило СУБД відносно простими системами, у тому плані що дані розбиті на атомарні значення, що мають прості типи. Це дозволяє за допомогою формальних мов (наприклад SQL) та функцій БД будувати запити, не передбачені на початку проектування БД і, таким чином сприяє довгому життєвому циклу реляційних баз даних.

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

Very good article on Java GC

"Recently, I have been working with a number of customers on JVM tuning exercises.  It seems that there is not widespread knowledge amongst developers and administrators about how garbage collection works, and how the JVM uses memory.  So, I decided to write a very basic introduction and an example that will let you see it happening in real time!" 
Read More 


Также на Хабре выложен перевод: Garbage Collection наглядно

пʼятниця, 21 січня 2011 р.

shellinabox: ajax web-based terminal for your linux


In case you are as lucky as me to work in banking were everything is secured and checked, you may miss access to your home box just as deeply as me. I managed to ssh to my box through corporate proxy one day, but the next day I found it blocked. I was desperate.

Luckily world hates firewalls and loves to innovate. There are quite a handful of web-based consoles available now. I didn't want to spend half a day comparing them so just started with shellinabox (http://code.google.com/p/shellinabox/). It is a web-based AJAX vt100 compatible terminal. It can export command-line tools that have interactive interface of some kind to the web and you can access that from anywere. Wave bye-bye to your PuTTy client, the only thing you need now is a web browser.

The project hosts prebuilt packages for apt-based distros, so you should have no problem in case you use either Ubuntu or Debian. For other distros you'll need to build it from sources. All that stuff can be found here (http://code.google.com/p/shellinabox/downloads/list)

Go grab one and install it with

grim@kepler:~$ wget http://shellinabox.googlecode.com/files/shellinabox_2.10-1_amd64.deb
grim@kepler:~$ sudo dpkg -i shellinabox_2.10-1_amd64.deb


In a few seconds you'll get it running at https://$lt;your-server-address>:4200/.

It has SSL preconfigured out of the box and self-signed certificate is generated for 20 years which should be enough I think. If you need to be 100% secure, you're better export it, carry around with you and import in those browsers you use most, so you could be sure that nobody standing in between you and classified on your box. I've imported the certificate at work and at my home PC for example.

Another thing is that port 4200 was blocked from work too. Bastards! If you hate your security folks just as ultimately as me, I'm sure need to drink some beer with you and share my pain.

What I did I put it behind Apache on the same box, so I could access it just as any other site on the web. Casting some sourcery onto openssl, mod_proxy and shellinabox parameters did the trick and it was nice one. Check it out on the shot!

субота, 15 січня 2011 р.

REST with Spring: Strict resource URLs

Russian translation is posted on my blog at Habrahabr

As you may know Spring MVC is using new annotation driven configuration model since 2.5. To enable it, you should use the <mvc:annotation-driven /> tag in your Spring configuration file..

What it does under the hood is it registers DefaultAnnotationHandlerMapping and AnnotationMethodHandlerAdaptor in your application context 

What DefaultAnnotationHandlerMapping does is searches your classes for @RequestMapping annotation and creates mapping for each and on top of that two mappings ending with .* and /.

So once you have following controller:

@Controller
@RequestMapping("/service/hotels")
public class HotelsCollectionController {

    @Autowired
    private HotelService hotelService;

    @RequestMapping(method = RequestMethod.GET)
    public String getHotelList(Model model) {

        List<Hotel> list = hotelService .getHotelList();

        model.addAttribute("hotels", list);

        return "service/hotels/read";
    }

    public void setHotelService(HotelService hotelService) {
        this.hotelService = hotelService;
    }
}

you will get three mappings internally for /service/hotels, /service/hotels/ and /service/hotels.*

The purpose of first two is clearly to be more friendly to the user and the last is used for content negotiation with ContentNegotiatingViewResolver.

And thats fine for most web applications. 

The problem arises when you try to apply RESTful approach to your web services using annotations for mapping. Since URL is now effectively a resource, different URLs are now different resources and your application should not be vague about interpreting it.

Another point is that Spring itself treats these implied URLs without too much care by default.

Naive me configured DefaultAnnotationHandlerMapping for application and set its defaultSuffixPattern property to false. And if that would be as simple as that, there was no much sense in this post.

Straightforward solves the problem at first site 

    <mvc:annotation-driven />

    <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
        <property name="useDefaultSuffixPattern" value="false" />
    </bean>

Now think about it. Once Spring sees <mvc:annotation-driven /> it creates one DefaultAnnotationHandlerMapping and places it in the context. Once it sees the above bean definition it creates *another* instance of DefaultAnnotationHandlerMapping and also places it in the context. So your happy app will have two HandlerMappings, one with default settings and another  configured one. Now which HandlerMapper will happen to chew the HTTP request first depends on the internal order of two, and is completely out of your control (well, almost... you can apply ordering, but that's kinda hack and besides trashes the story).

While for /service/hotels there is no difference, for /service/hotels/ and /service/hotels.* there is. Chances are that you will be using ContentNegotiatingViewResolver to negotiate best representation of resource for client and in this case you've effectively lost control on resolving the correct View, resulting from incorrect view being returned and ending with 500 on server. I hope I'll find time to gather the details later.

To avoid this, you should remove one of HandlerMappings from context. So we should remove <mvc:annotation-driven /> completely and do the hard work of registering AnnotationMethodHandlerAdaptor.

    <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
        <property name="useDefaultSuffixPattern" value="false" />
    </bean>

    <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />

This should do what is required and you will get desired 404 on implicit URLs. 

четвер, 6 січня 2011 р.

Создание своих архетипов и каталогов в Maven

Уже довольно продолжительное время (около года набежит) активно пользуюсь системой сборки Apache Maven и вполне ею доволен. Несмотря на свои очевидные и не очень недостатки, неоспоримым преимуществом является автоматическое управление зависимостями, хорошая структурированность проектов и отсутствие скриптов сборки как таковых, а следовательно проблем с ними.

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

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

Поиск плгинов и их настройка - это тоже мучительные круги ада, но по сравнению с xml-программированием на ant'e это еще ничего.

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

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

Архетип в мавене - это шаблон нового проекта, со структурой и заготовками исходных и конфигурационных файлов.

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

mvn archetype:create                                    \
  -DarchetypeGroupId=<archetype-groupId>                \
  -DarchetypeArtifactId=<archetype-artifactId>          \
  -DarchetypeVersion=<archetype-version>                \
  -DgroupId=<my.groupid>                                \
  -DartifactId=<my-artifactId>

Вопрос, откуда взять нужные параметры, всегда меня волновал, обычно я вежливо спрашиваю у Гугла и он мне обычно отвечает :)

Если мы например, захотим создать простоe приложение, то мы используем архетип под названием maven-archetype-quickstart, например вот так:

mvn archetype:create                                    \
  -DarchetypeGroupId=org.apache.maven.archetypes        \
  -DarchetypeArtifactId=maven-archetype-quickstart      \
  -DarchetypeVersion=1.0                                \
  -DgroupId=org.example                                 \
  -DartifactId=simpleapp

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

mvn archetype:generate

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

Погуглив немного, я нашел для себя решение этой проблемы, не совсем как по мне окончательное, но вполне себе с намеком на элегантность. Архетипы можно обьединяются в каталоги! Но как это может помочь?

Каталог определяется URL'ом, где он расположен, кроме того в мавене есть три предопределенных каталога, или если хотите, алиаса: internal, remote и local.

internalсодержит архетипы, встроенные в maven, их немного и они уже де-факто идут с самим дистрибутивом
remoteцентральный каталог maven, находится по адресу http://repo1.maven.org/maven2/archetype-catalog.xml, его местоположение зависит от текущих настроек мавена, например возможно переопределить этот урл на одно из зеркал репозитория
localкаталог из локального репозитория, обычно находится в ~/.m2/archetype-catalog.xml

У цели archetype:generate есть параметр archetypeCatalog, с помощью которого можно указать список каталогов, где нужно искать возможные архетипы. По умолчанию, значение параметра 'remote,local'. Но если убрать оттуда remote, то получим почти то, что нужно.

Например, вот так:

grim@blackbox:~/projects$ mvn archetype:generate -DarchetypeCatalog=local

[ ...булшит... ]

Choose archetype:
1: local -> maven-archetype-quickstart (quickstart)
2: local -> maven-archetype-archetype (archetype)
3: local -> maven-archetype-webapp (webapp)
Choose a number: 1:

Подсмотреть структуру файла можно из каталога remote. Например, чтобы получить список выше, файл должен иметь вид

<?xml version="1.0" encoding="UTF-8"?>
<archetype-catalog xsi:schemaLocation="http://maven.apache.org/plugins/maven-archetype-plugin/archetype-catalog/1.0.0 http://maven.apache.org/xsd/archetype-catalog-1.0.0.xsd"
    xmlns="http://maven.apache.org/plugins/maven-archetype-plugin/archetype-catalog/1.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <archetypes>
    <archetype>
      <groupId>org.apache.maven.archetypes</groupId>
      <artifactId>maven-archetype-quickstart</artifactId>
      <version>1.1</version>
      <description>quickstart</description>
    </archetype>
    <archetype>
      <groupId>org.apache.maven.archetypes</groupId>
      <artifactId>maven-archetype-archetype</artifactId>
      <version>1.0</version>
      <description>archetype</description>
    </archetype>
    <archetype>
      <groupId>org.apache.maven.archetypes</groupId>
      <artifactId>maven-archetype-webapp</artifactId>
      <version>1.0</version>
      <description>webapp</description>
    </archetype>
  </archetypes>
</archetype-catalog>


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

  <profiles>

    <profile>
        <id>dev</id>
        <activation>
            <activeByDefault>true</activeByDefault>
        </activation>
        <properties>
           <archetypeCatalog>local</archetypeCatalog>
        </properties>
    </profile>

  </profiles>

Усе! Теперь, при вызове, цель archetype:generate вместо тонн мусора, будет выводить вам то, что скажете.

К слову у плагина archetype есть любопытная цель crawl, которая сканирует ваш локальный репозиторий на предмет наличия архетипов и генерирует из всех найденных каталог. К сожалению, документация немного врет и по умолчанию файл генерируется совсем не там где ожидает увидеть его мавен. Готовить нужно так:

grim@blackbox:~/projects$ mvn archetype:crawl -Dcatalog=~/.m2/archetype-catalog.xml

Вопреки официальной документации параметр называется не catalogFile, а catalog.

Создаем свой архетип

Вторая проблема, которую мы рассмотрим - это создание собственного архетипа. Зачем это нужно? Нужно это по той простой причине, что при всем кажущемся обилии архетипов, среди них не оказалось подходящего для простой веб-разработки. Есть несколько близких по духу, например тот же maven-archetype-webapp, но дескрипторы в нем устаревшие, нету log4j и нормального темплейта jsp. В итогое после создания пустого проекта, нужно перейти в режим работы напильником, и с помощью гугла переделывать все как надо. В конце концов на десятый раз мне это надоело, и я решил создать свой собственный архетип, самый лучший и самый правильный. Более конкретно, создадим заготовку для простенького приложения на spring-mvc c использованием Servlet 2.5/JSP 2.1/JSTL 1.2 c готовым к работе логированием.

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

Для начала нужно создать проект для нашего архетипа, используя архетип maven-archetype-archetype, например с помощью того же archetype:сreate.

grim@blackbox:~/projects$ mvn archetype:create      \
   -DarchetypeArtifactId=maven-archetype-archetype  \
   -DartifactId=baremvc                             \
   -DgroupId=example

Чтобы не мучится в консоли, пересядем в эклипс, хотя конечно же все, что написано ниже, можно сделать и с помощью vim'а или еще чегото такого.
grim@blackbox:~/projects$ cd baremvc && mvn eclipe:eclipse

После чего делаем импорт и смотрим на структуру более детально.

 

Проект состоит из одной папки resources, в которой содержится две подпапки: archetype-resources и META-INF. В первой храниться костяк будущих выдающихся проектов, которые еще будут созданы поколениями програмистов после нашей смерти, во второй хранится файлик META-INF/maven/archetype.xml. Это дексриптор архетипа. В нем будет хранится описание того, что входит в архетип.

Дополним костяк, всем чем нужно: добавим туда простенькую jsp страничку, более-менее сносный web.xml, простенький контроллер и конфиг log4j.properties. Все это вы найдете в архиве, который можно скачать. Из интересных моментов, на которые стоит обратить внимание, это замены, которые делает мавен при создании проекта. В архетипе реализован механизм темплейтов на основе Velocity, который практически не документирован, если ктото уверен в обратном, поделитесь, буду благодарен.

В частности в контроллере через темплейты реализована подстановка имени пакета.

// HomeController.java
package $package;

import org.apache.commons.logging.Log;

@Controller
public class HomeController {

...


Тот же самый ход использован в конфигурации Spring MVC и при генерации целевого pom.xml. Пока что список известных мне переменных довольно скуден: $groupId, $artifactId, $version и $package. Думаю, всем понятно, что каждый значит, все это указывается при создании проекта из архетипа.

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

Когда процесс подготовки шаблонов завершен, нужно подготовить файл-дескриптор архетипа, тот самый archetype.xml.

<archetype xmlns="http://maven.apache.org/plugins/maven-archetype-plugin/archetype/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/plugins/maven-archetype-plugin/archetype/1.0.0 http://maven.apache.org/xsd/archetype-1.0.0.xsd">

  <id>baremvc</id>
  <sources>
    <source>src/main/java/HomeController.java</source>
  </sources>
  <resources>
      <resource>src/main/resources/log4j.properties</resource>
      <resource>src/main/webapp/home.jsp</resource>
      <resource>src/main/webapp/WEB-INF/applicationContext.xml</resource>
      <resource>src/main/webapp/WEB-INF/web.xml</resource>
  </resources>
  <testSources />
</archetype>   

Тэг <id></id> должен совпадать с artifactId нашего архетипа. Тэги <sources> и <resources> прдназначены для темплейтов разных частей архетипа. В частности ресурсы из папки webapp нужно указывать в тэге <resources>. Есть еще несколько допустимых секций. Ниже перечислены допустимые секции и папки, куда уйдут указанные в них шаблоны.
  • <sources> => src/main/java
  • <resources> => src/main/resources
  • <testSources> => src/test/java
  • <testResources> => src/test/resources
  • <siteResources> => src/site


После того как все готово, можно установить наш архетип в репозиторий с помощью

grim@blackbox:~/projects/baremvc$ mvn clean install

и теперь он готов к использованию.

grim@blackbox:~/projects/baremvc$ cd ..
grim@blackbox:~/projects$ mvn archetype:create          \
  -DarchetypeGroupId=org.example                        \
  -DarchetypeArtifactId=baremvc                         \
  -DarchetypeVersion=1.0                                \
  -DgroupId=org.example                                 \
  -DartifactId=baremvcapp

Наш маленький проект готов к запуску

grim@blackbox:~/projects/baremvcapp$ cd baremvcapp
grim@blackbox:~/projects/baremvcapp$ mvn tomcat:run

После чего по ссылке http://localhost:8080/baremvcapp можно узреть сие творение.

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

grim@blackbox:~/projects$ mvn archetype:crawl -Dcatalog=/home/grim/.m2/archetype-catalog.xml

Теперь при вызове archetype:generate в списке должна появиться строчка

1: local -> baremvc (baremvc)

с чем я нас и поздравляю.

В заключение осталось добавить, что описанный выше способ устарел (увы), и помечен как depreceted, но все еще работает, а про right way документации как то кот наплакал. Все что мне пока известно, это что дескриптор изменили, теперь он называется archetype-metadata.xml и имеет более мощный синтаксис. Надеюсь хватит еще на десяток лет вперед статьи писать.



Архив, который можно скачать


Исходный код, который можно посмотреть

Использованные ресурсы

  1. Guide to Creating Archetypes - http://maven.apache.org/plugins/maven-archetype-plugin-1.0-alpha-7/examples/archetype.html
  2. Maven Archetype Plugin - http://maven.apache.org/archetype/maven-archetype-plugin/
  3. Maven – размышления после двух лет использования - http://habrahabr.ru/blogs/personal/102181/