суббота, 23 марта 2013 г.

Intrinsic-методы

Если вы скопируете код String.equals() в свою программу и сравните производительность вашего метода и настоящего, то, скорее всего, будете удивлены, как и автор вопроса: скорость вашей реализации может быть медленнее в десятки раз. JVM просто заменяет код native-реализацией, несмотря на то, что они не помечены соответствующим словом. Такие заменяемые методы называют intrinsic-методами.

Официальной информации немного. Список таких методов можно получить ковыряя исходники JVM. Тезка предложил скрипт для получения списка классов, у которых есть такие методы. Однако не все методы в них intrinsic.

$ grep do_intrinsic vmSymbols.hpp | perl -pe s/\#.*//g | perl -pe s/\\/\\/.*//g |  awk '{ print $2 }' | sort | uniq | perl -pe s/_/./g | perl -pe s/,//g

java.lang.Boolean
java.lang.Byte
java.lang.Character
java.lang.Class
java.lang.Double
java.lang.Float
java.lang.Integer
java.lang.invoke.InvokeDynamic
java.lang.invoke.MethodHandle
java.lang.invoke.MethodHandleNatives
java.lang.Long
java.lang.Math
java.lang.Object
java.lang.reflect.Array
java.lang.reflect.Method
java.lang.ref.Reference
java.lang.Short
java.lang.String
java.lang.StringBuffer
java.lang.StringBuilder
java.lang.System
java.lang.Thread
java.lang.Throwable
java.nio.Buffer
java.util.Arrays
sun.misc.AtomicLongCSImpl
sun.misc.Unsafe
sun.reflect.Reflection

воскресенье, 17 марта 2013 г.

Флаги модулей аутентификации JAAS: required, requisite, sufficient, optional (шпаргалка)

Пока читал про модули аутентификации JAAS, немного запутался во флагах required, requisite, sufficient, optional. Итого моих разбирательств стали несколько исписанных листков с самыми кошмарными диаграммами. Зато разобрался.

Вспомните про операторы &, &&, |, ||. Флаги ведут себя похожим образом.
required (&) – требуется успех всех модулей. 
optional (|) – требуется успех хотя бы одного.

Следующие два прерывают выполнение когда становится ясен итоговый результат.
requisite (&&) – требуется успех всех модулей. Завершение при неудаче.
sufficient (||) – требуется успех хотя бы одного. Завершение при успехе.

Ну и не надо забывать, что успешный sufficient после неудачного required не спасет ситуацию.

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

Прерывание потока. Для чего нужен Thread.currentThread().interrupt()

Часто можно встретить код, вроде такого:

try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    //Восстанавливаем статус прерывания потока
    Thread.currentThread().interrupt();
}

Если не понимать логику работы прерываний, то напрашивается вопрос: для чего поток прерывает сам себя? На самом деле, выражение "прерывание потока" не очень удачное. Для этого есть устаревший(deprecated) и небезопасный метод stop, которым не рекомендуется пользоваться. Сам разработчик потока должен думать о корректном завершении работы своего потока.

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

Часто хорошим решением в этой ситуации является генерация исключения InterruptedException. Посмотрим, например, на поведение Thread.sleep() – приостанавливает поведение потока, но проверяет флаг, и как только замечает, что он установлен, бросает InterruptedException.

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

В следующем примере поток успешно поспит 2 раза, но третий сон будет прерван. Метод sleep2sec узнает об этом, но утаит эту информацию. Поэтому, поток будет выполнятся бесконечно, несмотря на проверку isInterrupted().

public class CatLife{
    
    final public static void main(String[]args) throws InterruptedException{
        Thread cat = new Thread(new Cat());
        cat.start();
        Thread.sleep(5000);
        cat.interrupt();
        cat.join();
    } 
    
    static class Cat implements Runnable{

        @Override
        public void run() {
            while(!Thread.currentThread().isInterrupted()){
                System.out.println("Сон начинается");
                sleep2sec();
                System.out.println("Сон закончился\n");
            }
            System.out.println("Завершение работы потока");
        }
        
        private void sleep2sec(){
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                System.out.println("Сон прерван");
            }
        }
        
    }

}

Тут есть 2 решения:
1. Пробросить исключение.
2. Снова установить флаг прерывания: Thread.currentThread().interrupt() в catch-секции, чтобы условие цикла выполнилось.

суббота, 16 марта 2013 г.

ArrayList, Vector, CopyOnWriteArrayList в многопоточной среде

Я думаю всем известно, что ArrayList совершенно непригоден для многопоточного использования. У меня пробегают мурашки, когда представляю, что, например, метод add будут использовать 2 потока одновременно:

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

«Ага!, – скажет кто-то. – Тут нужно использовать java.util.Vector», потому, что:

public synchronized boolean add(E e) {
    modCount++;
    ensureCapacityHelper(elementCount + 1);
    elementData[elementCount++] = e;
    return true;
}

Красота – и add, и remove и прочие модифицирующие методы защищены блокировкой (кстати, такая блокировка называется intrinsic (внутренняя) lock или monitor lock). Обратите внимание на переменную modCount! Мы еще о ней поговорим.

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

Давайте глянем на итератор вектора:

public synchronized Iterator<E> iterator() {
    return new Itr();
}

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

private class Itr implements Iterator<E> {
    ...
    int expectedModCount = modCount;

    public E next() {
        synchronized (Vector.this) {
            checkForComodification();
            int i = cursor;
            if (i >= elementCount)
                throw new NoSuchElementException();
            cursor = i + 1;
            return elementData(lastRet = i);
        }
    }

    ...

    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

Очень интересная строка – инициализация переменной expectedModCount. Как мы уже видели выше, добавление элемента (а также и удаление) влечет увеличение переменной modCount. Итератор запоминает это значение во время создания, и откажется итерироваться, если заметит (вызовом checkForComodification), что состав контейнера изменился. Другими словами,  для нормальной работы, требуется совпадение версий вектора и итератора. В достоверности этого можно убедиться и с одним потоком:

Vector<Integer> v = new Vector<Integer>();
v.add(1);
Iterator<Integer> it = v.iterator();
v.add(2);
while(it.hasNext())
    System.out.println(it.next());  //ConcurrentModificationException

Давайте посмотрим как с этим справляется класс CopyOnWriteArrayList

public boolean add(E e) {

    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}

Для меня это было неожиданно – при добавлении (и удалении) создается, а затем используется копия массива. И новый элемент добавляется в эту копию. Как это влияет на работу итератора? Все итераторы продолжают работать со своими старыми массивами,  которые были актуальны на момент создания итераторов. Поэтому все изменения контейнера другими потоками не повлияют на их работу.

Резюме
Контейнеры в многопоточной среде:
ArrayList – непригоден
Vector – пригоден, но скорее всего, не удасться нормально использовать итераторы
CopyOnWriteArrayList – пригоден. Однако, из-за создания копий, разумно использовать когда количество итераций во много раз превосходит число модификаций.

Домашнее задание
1. Выяснить результат работы:

Vector<Integer> v = new Vector<Integer>();
Iterator<Integer> it = v.iterator();
v.add(1);
System.out.println(it.hasNext());

2. Убедиться (проверив исходный код класса CopyOnWriteArrayList), что итератор работает именно с тем массивом, который был на момент создания итератора.