Friday, August 6, 2010

Знакомьтесь, антипаттерн double-checked locking

На днях размышлял, как можно было бы ускорить следующую конструкцию:
Конструкция 1. Известная всем реализация ленивого синглтона.

public class Singleton
{
private Singleton(){}

private static Singleton INSTANCE;

public static synchronized Singleton getInstance()
{
if (INSTANCE == null)
{
INSTANCE = new Singleton();
//
// ... perform an INSTANCE initialization
}
return INSTANCE;
}
}

Основным недостатком является наличие synchronized, которое по-хорошему нужно
только при инициализации экземпляра объекта singleton, а не при каждом доступе к нему.
Таким образом, сразу же напрашивается мысль, а что если попробовать использовать критическую секцию только при инициализации объекта:
Конструкция 2. Первая попытка сделать короче время доступа к INSTANCE.

public class Singleton
{
private Singleton(){}

private static Singleton INSTANCE;

public static Singleton getInstance()
{
if (INSTANCE == null)
{
synchronized(Singleton.class)
{
INSTANCE = new Singleton();
//
// ... perform an INSTANCE initialization
}
}
return INSTANCE;
}
}

Теперь инициализация объекта будет происходить при первом доступе к объекту, но, к сожалению, данная конструкция не спасает от конкурентного доступа. Так как может получиться так, что конкурирующие процессы после проверки INSTANCE на null последовательно войдут в критическую секцию и проинициализируют Singleton несколько раз. Тут же придумал workaround: поставить проверку на null после входа в критическую секцию, сказано - сделано:
Конструкция 3. Вторая попытка или double-checked locking антиппатерн.
public class Singleton
{
private Singleton(){}

private static Singleton INSTANCE;

public static Singleton getInstance()
{
if (INSTANCE == null)
{
synchronized(Singleton.class)
{
if (INSTANCE == null)
{
INSTANCE = new Singleton();
//
// ... perform an INSTANCE initialization
}
}
}
return INSTANCE;
}
}

Полученная конструкция в теории должна работать, но так ли это? Довольный своим открытием, я решил посмотреть, что пишут эксперты о такой конструкции, есть ли какие-то другие, более эффективные варианты ускорения реализации паттерна singleton. Очень быстро обнаружил, что такая конструкция действительно используется и называется она double-checked locking (в некоторых источниках такую конструкцию называют даже антипаттерном [2]). Действительно она применяется для ускорения singleton, но имеет ряд неочевидных недостатков для некоторых языков.

Недостаток первый. В некоторых языках, в т.ч. и в Java значение переменной INSTANCE может быть присвоено при выполнении конструктора до его завершения. Это связано с выделением памяти, как только выделяется память, то INSTANCE получает соотв. значение (в Java это ссылка, в других языках это, возможно, указатель на некоторую выделенную для объекта область памяти). Таким образом, есть вероятность, что пока один поток будет выполняться в критической секции, другой, выполнив проверку INSTANCE на null, в критиескую секцию уже не пойдет, а сразу вернет неинициализированную (но не равную null) переменную INSTANCE.

Недостаток второй. Запись и чтение значений переменных в многопоточных программах на некоторых языках могут зависеть от реализации конкретной исполняющей среды/компилятора. Например, в JAVA используется кеширование переменных: из соображений эффективности каждый поток может хранить свою собственную приватную копию переменной. Эта копия может синхронизироватся с основной памятью в различные моменты, например, при входе в критическую секцию и при выходе из нее [3]. Как вариант, в Java можно использовать модификатор volatile для переменной INSTANCE, которое действительно гарантирует атомарный доступ к переменной. В таком случае переменная никогда не кешируется потоками и доступ к ней осуществляется, как если бы это происходило внутри критической секции (synchronize над этой переменной). Но по сути это приводит к тому же от, чего уходили - к наличию критической секции при доступе к переменной.

Таким образом, рекомендуется использовать конструкцию 1 [2], либо использовать факт о ленивой загрузке классов [1]: т.е. что сам класс Singleton будет загружен лишь при первом обращении к нему, соотв. будут проинициализированны все static-поля и секции:
Конструкция 4. Если быть проще...
public class Singleton
{
private static Singleton INSTANCE = new Singleton();

static
{
// further INSTANCE initialization
}

private Singleton()
{
// Singleton initialization
}

public static Singleton getInstance()
{
return INSTANCE;
}
}


Материалы.

[1] http://www.ibm.com/developerworks/java/library/j-dcl.html
[2] http://www.javamex.com/tutorials/double_checked_locking_fixing.shtml
[3] http://www.javamex.com/tutorials/synchronization_concurrency_synchronized1.shtml

Thursday, July 22, 2010

Переменные среды в Linux

Установка некоторых переменных среды через консоль:

export JAVA_HOME=/path/to/java/jdk1.6.0_20
export PATH=$JAVA_HOME:$PATH

export M2_HOME=/path/to/java/maven
export M2=$M2_HOME/bin
export PATH=$M2:$PATH

export используется, если задается глобальная переменная. Если переменная локальная (т.е. только для данного процесса), то export не требуется.
Посмотреть значение переменной можно, например, так:

echo $PATH

Кстати, в отличие от Windows, в PATH элементы разделяются двоеточием, а не точкой с запятой.
Для того, чтобы переменные среды устанавливались при загрузке пользовательского профайла рекомендуется использовать ~/.pam_environment для того, чтобы добавить туда скрипт с заданием переменных. Общие рекомендации по установке persistent environment variables можно узнать, набрав в консоли man pam_env, либо здесь.

Как оказалось, ~/.pam_environment не является скриптовым файлом, а представляет собой лишь список переменных среды. К тому же, у меня не получилось переопределить PATH, используя упомянутый файл. Такое переопределение приводило к тому, что в машину вообще нельзя было залогиниться. Пришлось войти в консольном режиме и с помощью vim удалять строки в ~/.pam_environment, отвечающие за переопределение PATH.

В конце концов, я взял и добавил нужные строки для определения переменных среды в ~/.profile. Сработало.

Tuesday, July 13, 2010

Использование system properties в eclipse.ini

Понадобилась небольшая кастомизация eclipse.ini.
Потребовалось уметь создавать свои параметры из eclipse.ini и уметь читать их внутри eclipse. Первый поиск практически ничего не дал конкретного по eclipse, но нашелся другой, более общий варинт: использование системных свойств (system properties) java. Системное свойство можно определить через eclipse.ini в качестве аргумента java-машины, например:

...
-vmargs
-Dfile.encoding=UTF-8
-Dosgi.requiredJavaVersion=1.5
-DMyCustomProperty=aPropertyValue
-Xms256m
...

Теперь в коде можно получить значение свойства MyCustomProperty следующим образом:

String value = System.getProperty("MyCustomProperty");

Friday, July 9, 2010

Создание архивов с помощью VBS

Есть, на мой взгляд, красивый способ создания архивов с помощью VB-скрипта, который хочу выложить (автор скрипта не я, но в своих скриптах использую эту рутину оч. активно). Грубо, идея в том, чтобы создать файл пустого архива, а потом в него добавить средствами Shell файлики.


';===================================
';
'; Simple zipping routine:
';
'; source is folder path to be zipped (recursive zip)
'; dest is file path for output zip archive
';
'; the main idea comes from here:
';
'; http://www.codecomments.com/archive299-2006-2-295877.html
'; http://forum.script-coding.info/viewtopic.php?id=1020
';
';===================================
sub zip(source, dest)
'
' wait operation delay
'
const dt = 1000
Set fso = CreateObject("Scripting.FileSystemObject")
Set ShellApp = CreateObject("Shell.Application")

With fso.CreateTextFile(dest, True)
.Write Chr(80) & Chr(75) & Chr(5) & Chr(6) & String(18, Chr(0))
.Close
End With

ShellApp.NameSpace(dest).CopyHere source

WScript.Sleep dt
'
' waiting for acrhive creation
'
On Error Resume Next
Do
fso.OpenTextFile dest, 1, False
If Err.Number = 0 Then Exit Do
Err.Clear
WScript.Sleep dt
Loop
end sub

Wednesday, July 7, 2010

Некоторые скриптинговые рутины на VBS

Давно хотел выложить код на VBScript, который я использовал в скрипте для публикации проекта (под публикацией понималось выкладывание сборки в базу данных Lotus Notes и рассылка уведомлений по списку).

Работа с Lotus Notes в vbs:

sub run()
' получить сессию (COM-объект, соотв. важно,
' чтобы объект был зарегистрирован в Win-реестре)
Set nSession = CreateObject("Notes.NotesSession")
' получить базу
Set db = nSession.GetDatabase(STORAGE_DATABASE_SRV,STORAGE_DATABASE_PATH)
if not db is nothing then
' какая-то работа с базой
'...
'...
end if
' освобождеине COM-объекта
nSession = null
end sub



Сниппет с приаттачиванием файлов к документу:


const EMBED_ATTACHMENT = 1454
';================================================
';
'; Simple file attach routine
';
'; @param doc is Notes Document
'; @param itemName is RTF item name
'; @param path is path to the file to be attached
';================================================
sub attach(doc, itemName, path)
Set item = doc.getFirstItem(itemName)
if not item is nothing then
call doc.removeItem(itemName)
end if
Set rti = doc.CreateRichTextItem(ItemName)
Set object = rti.EmbedObject(EMBED_ATTACHMENT, "", path)
end sub

Запуск bin-файлов в Linux

Вот уже несколько дней живу под Ubuntu, после WinXP немного непривычно.
Скачал brand new гелиос, скачал jdk. Поставить jdk оказалось довольно просто, достаточно было только bin-файлу, в котором лежит установщик jdk присвоить атрибут x (exetuable). После WinXP для меня это было не очевидно.

Вот сниппет, где я разрешаю выполнение bin-файла, после чего я его запускаю на выполнение:

tim@epsilon:~/works/downloads$ sudo chmod +x jdk-6u20-linux-i586.bin
tim@epsilon:~/works/downloads$ ./jdk-6u20-linux-i586.bin


Было забавно запускать установку jdk через консоль, так как пришлось долго и нудно проматывать лицензионное соглашение, прочитав которое, я должен был вбить заветное yes в ответ на запрос "Do you agree to the above license terms? [yes or no]".

Tuesday, July 6, 2010

Настройка прокси для SVN клиента

При подключении через svn к внешнему репозиторию получил сообщение "could not connect to server (http://eclipse.gwf.svn.sourceforge)". Задал прокси в настройках клиента - все заработало.

Шаблон для задания параметров в win-реестре:


Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Tigris.org\Subversion\Servers\global]
"http-proxy-host"="abcproxy"
"http-proxy-password"="pwrd"
"http-proxy-port"="8080"
"http-proxy-username"="ABCCORP\\usr"


Существует еще ряд других способов настройки конфигурации svn-клиента, о которых
можно подробно почитать в документации, гл. 7:

Ben Collins-Sussman, Brian W. Fitzpatrick, C. Michael Pilato: Version Control with Subversion For Subversion 1.5, Chapter 7. Customizing Your Subversion Experience