Собеседование по Java concurrency

Нашел интересный список из over 50 вопросов к интервью по Java concurrency и многопоточности. На все старался отвечать честно, что совсем не знал — гугл (~11 вопросов в основном на тему хитрых названий).

Ниже то что получилось.

Вопросы

Содержание

1) Назовите различия между Collections.synchronizedMap(new HashMap()) и ConcurrentHashMap.

SynchronizedMap оборачивает обычный Map используя единственный монитор для блокировки,

тогда как ConcurrentHashMap

  • позволяет делать неблокирующее чтение (но можно увидеть старые\невалидные данные)
  • на запись делит map на секции, к каждой из которых идет свой объект блокировки (это уменьшает общее время ожидания)

2) Что такое кооперативная многозадачность и есть ли она в Java. Если да, то какие преимущества. Если нет, то какая тогда в Java?

Способ деление времени CPU между потоками при котором каждый поток обязан отдавать управление следующему добровольно.

Преимущества — возможно меньшие накладные расходы на переключение контекста если среда исполнения полностью нами контролируется (нет лишних переключений контекста)

Недостатки — если один поток завис или ведет себя некорректно то вся система зависла и другие потоки никогда не получат управление.

В Java — вытесняющая многопоточность.

3) Что такое «зеленый потоки» и есть ли они в Java (в HotSpot JVM.6)?

Легковесные потоки (эмулируемые) виртуальной машиной или средой исполнения..

не подрузамевают под собой реального создания соотв. потоков ОС, как следсвие нет переключения между USER и KERNEL режимами ядра ос. В Java6 нету.

4) Различия в интерфейсах Runnable и Callable.

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

5) Напишите минимальный неблокирующий стек (всего два метода — push() и pop()).

public class Stack {

    private final AtomicReference<Element> headRef = new AtomicReference<Element>(null);

    public void push(T value) {
        Element newHead = new Element();
        newHead.value = value;
        Element oldHead;
        do {
            oldHead = headRef.get();
            newHead.next = oldHead;
        } while (!headRef.compareAndSet(oldHead, newHead));
    }

    public T pop() {
        Element oldHead = null;
        Element newHead = null;
        do {
            oldHead = headRef.get();
            if (oldHead == null) {
                return null;
            }
            newHead = oldHead.next;
        } while (!headRef.compareAndSet(oldHead, newHead));
        return oldHead.value;
    }

    public static class Element {
        private T value;
        private Element next;
    }

}

6) Напишите минимальный copy-on-write ArrayList (всего четыре метода — void add(int indx, int item), int get(int indx), void remove(int indx), int size()).

public class CopyOnWriteArrayList {

    private volatile Object[] array = new Object[0];

    public void add(int index, T item) {
        if (index < 0) {             throw new IllegalArgumentException();         }         boolean needsModification = index > array.length - 1;
        if (!needsModification) {
            if (item == null) {
                needsModification = array[index] != null;
            } else {
                needsModification = item.equals(array[index]);
            }
        }

        if (needsModification) {
            final Object[] newArray = Arrays.copyOf(array, Math.max(array.length, index + 1));
            newArray[index] = item;
            array = newArray;
        }
    }

    public void remove(int index) {
        if (index < 0 || index >= array.length) {
            throw new IllegalArgumentException();
        }
        int newSize = array.length - 1;
        if (newSize < 0) {
            newSize = 0;
        }
        final T[] newArray = (T[]) new Object[newSize];
        System.arraycopy(array, 0, newArray, 0, index);
        if (index + 1 < newSize) {
            System.arraycopy(array, index + 1, newArray, index, newSize - index);
        }
        array = newArray;
    }

    public T get(int index) {
        return (T)array[index];
    }

    public int size() {
        return array.length;
    }
}

7) Различя между Thread.isInterrupded() и Thread.interrupted().

interrupted() проверяет флаг того что поток прерван и сбрасывает его, isInterrupded() ничего не трогает и можно вызывать несколько раз

8) Что происходит при вызове Thread.interrupt()?

Если поток

  • заблокирован на мониторе, ждет другой поток итп (wait, notify) — вылетит InterruptedException но флаг interrupted потока не поставится
  • Если поток ждет ввод-вывод на InterruptableChannel то тоже вылетит exception
  • Ecли поток ждет ввод-вывод то он тут же прекратится и выставится флаг Interrupted
  • Либо просто ставится статус Interrupted

9) Некоторые из следующих методов deprecated а некоторые и не были никогда реализованы. Какие? Thread.interrupt(), Thread.stop(), Thread.yield(), Thread.suspend(), Thread.resume(), Thread.join(), Thread.start(), Thread.run(), Thread.sleep().

  • stop(), suspend(), resume() — deprecated т.к. могут убить\остановить поток оставив его ресурсы в неизвестном\промежуточном состоянии которое не является валидным
  • interrupt() — прерывает поток если он занимается I/O и выставляет флаг interrupted
  • yield() — принудительно передает квант времени следующему потоку
  • join() — текущий поток ждет другой поток
  • start() — стартует нитку
  • run() — стартует код в текущей нитке без порождения отдельного потока
  • sleep() — усыпить поток на некоторое время, не отпуская захваченные локи\мониторы\ресурсы

10) Что Вы знаете об асинхронных вызовах методов? Есть ли это в самом языке Java? Если есть, то как реализовано? Если нет, то как бы Вы реализовали?

Есть ExecutorService который принимает Callable, и возвращает интерфейс Future; это позволяет

  • заблокироваться и подождать завершения вычислений
  • следить за тем выполнен Callable или нет
  • отменить вычисление если оно не закончислось

11) Перечислите ВСЕ причины по которым может выскочить InterruptedException.

Поток ждет в wait, sleep(…), join() или заблокирован на длительное время аналогичным вызовом.. и из соседнего потока дернули interrupt()

12) Что изменилось между JMM до Java 5 и NewJMM после Java 5?

volatile дает более внятные happens before гарантии, не только на порядок присвоения самих volatile переменных но и на side-эффекты.

int a = 0;
int volatile b = 0;

a = 100;
b = 1;

//в другом потоке
if(b==1) {
   // тут гарантируется что a == 100
}

Плюс стало возможным писать код вида

public volatile MyObject obj = new MyObject();

и быть уверенным что в момент когда из другого потока обратятся к полю obj, ссылка будет присвоена уже проинициализированному объекту и конструктор полностью отработает. В старой Memory model ссылка могла присвоиться не полностью собранному объекту из за эффекта переупорядочивания инструкции VM и CPU.

13) В классе String все поля финальные. Можно ли убрать ключевое слово финал? Ведь сеттеров все равно нет — следовательно поля нельзя переустановить.

Нет, т.к. final поля нужны для безопасной «публикации» объектов между потоками.

14) Что такое ordering, visibility, atomicity, happens-before, mutual exclusion. И показать на примерах volatile, AtomicInteger, synchronize{} — что из вышеперечисленного списка присутствует и при каких вариантах использования.

  • ordering — определяет когда один поток может увидеть out-of-order (т.е. неправильный) порядок исполнения инструкций другого потока. CPU может переупорядочивать и выполнять x86 инструкции в произвольном порядке для повышения пифоманса до тех пор пока для потока внутри не видно никаких отличий. Называется такая гарантия as-if-serial semantics. Проблемы же появляются когда нужно получать доступ из нескольких потоков к общей памяти — все эти side-эффекты вылезают. Решается с помощью механизмов публикации\синхронизации\гарантий Java Memory Model
  • visibility — определяет когда действия в одном потоке станут видны в другом потоке
  • atomicity — атомарность операций, операция выглядит как единая и неделимая операция которая либо выполнилась либо еще нет
  • Как правило все ошибки в многопоточном приложении попадают из за несоблюдения одного из 3х — visibility, atomicity, ordering
  • happens-before — логическое ограничение на порядок выполнения программы, термин используется в спеке по Java Memory Model. Например если мы говорим что запись в переменную A и последующее ее чтение связаны чз эту зависимость — то как бы не переупорядочивались инструкции в момент чтения мы должны видеть все size-эффекты от выполненной ранее операции записи.
  • volatile — дает гарантии happens-before на все присвоения переменных до текущего момента (так называемый read memory barrier)/li>
  • AtomicInteger — позволяет выполнять атомарные Compare-and-swap операции реализованные аппаратно в CPU. Основная выгода от CAS операций появляется только при условии что переключать контекст процессора с потока на поток менее выгодно чем немного покрутиться в цикле while пытаясь выполнить апдейт вида boolean compareAndSwap(oldValue, newValue). Если время потраченное в таком цикле превышает 1 квант потока то atomic переменные может быть невыгодно использовать с точки зрения производительности..
  • synchronize — создает mutex (взаимоисключающую блокировку) некоторого объекта, каждый поток которые не может захватить объект блокируется на неопределенное время. mutex — частный случай semaphore с единственным состоянием.

15) Назовите отличия synchronize{} и ReentrantLock.

  • synchronize — более примитивная конструкция которая обязывает нас отпустить monitor по окончании секции. Таким образом захват\освобождение всегда идут парами и всегда связаны с некоторым блоком кода
  • ReentrantLock — можно захватывать и освобождать мониторы в произвольном порядке, дает гибкость но сложнее сделать все правильно. Также есть опция fair — следить ли за «честным» порядком предоставления доступа\времени ожидания потоков на мониторе.
  • ReentrantLock — лучше масштабируется при росте числа потоков

16) Что из данных вызовов создает happend-before: Thread.sleep(), Thread.join(), Thread.yield(), Thread.start(), Thread.run(), Thread.isAlive(), Thread.getState()?

Только join(), start(), isAlive()

17) Перечислите известные Вам способы борьбы с priority inversion, назовите классы систем где они особенно опасны.

Опасны в real time системах, возникают из за особенностей планировщиков задач\прерываний. Способов предотвращения несколько -

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

18) Перечислите известные Вам способы 1)избежать 2)побороть возникшие deadlock-и (представьте, что вы пишете ядро RDBMS).

Чтобы избежать дедлоков -

  • Захватывать везде ресурсы в одинаковом порядке
  • или знать заранее какие ресурсы в каком порядке будут захвачены — строить граф переходов м-ду состояниями

Чтобы побороть дедлок

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

19) Расскажите о паттернах Reactor/Proactor?

Оба паттерна используются для высокопроизводительной обработки данных и разделения потока данных по worker-потокам. Основное отличие в том вычитывает ли listener сам данные или ждет пока это сделает callback, во многом на производительность и предпочтительный шаблон влияет наличие в ОС асинхронного ввода вывода и насколько хорошо он реализован. В Win — выигрывает Proactor, в *nix — Reactor.

  • Reactor — получил нотификацию что данные пришли, уведомил user callback, callback сам вычитал нужные данные, callback их обрабатывает
  • Proactor — полчил нотификацию что данные пришли, сам вычитал нужные данные в буфер, уведомил user callback чтобы тот забрал данные, callback их обрабатывает

20) Что такое «monitor»?

Объект для синхронизации. Используется для безопасного разделения ресурсов между потоками (Mutex).

21) Что такое «private mutex»?

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

22) Что такое «priority inheritance»?

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

23) Что такое «backoff protocol (exponential backoff)»?

Некоторая договоренность (алгоритм) между потоками (или нодами) что делать в случае конфликта. Например после неудачной попытки захватить ресурс интервал повторной попытки на каждом ноде должен вычисляться так чтобы минимизировать вероятность повторного конфликта\совпадения по времени с другими нодами.

24) Что такое «task stealing»?

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

25) Что такое «ABA problem»?

Возникает при compare-and-swap вызовах если значение переменной переполнилось (или совершило цикл\вернулось к старому значению). В этом случае механизм compare-and-swap перестает быть надежным.

26) Что такое «test-and-set»?

Аналогично compare-and-swap но при сравнении значение проверяется на 0.

27) Что такое «test-and-test-and-set»?

Перед использованием test-and-set стараемся предварительно проверить занят ли лок кем либо другим (например выполняем цикл пока некоторая shared-переменная не покажет что ресурс свободен)

28) Что такое «spin lock»?

Поток ждет освобождения lock’a проверяя в цикле условие\ресурс.

39) Что такое «sequential consistency»?

То же что и as-if-serial semantics, гарантии что в рамках одного потока побочные эффекты от всех операций будут такие, как будто все операции выполняются последовательно.

30) Что такое «sense-reversing barrier»?

Способ повторного использования для Barrier. В барьере хранится флаг sence, и в каждом потоке его использующем хранится аналогичный флаг в ThreadLocal переменной. Идея в том чтобы меняя эти флаги при вызове await() использовать барьер для поочередной разблокировки то одного потока то другого.

31) Что такое «safe publication»?

Показ объектов другим потокам из текущего, не нарушая ограничений visibility. Способы публикации в Java:

  • static{} инициализатор
  • volatile переменные
  • atomic переменные
  • сохранение в shared переменной, корректно защищенной с использованием synchronized()или Lock’и и другие конструкции создающие read/write memory barrier
  • final переменные в shared-объекте который был корректно проинициализирован

32) Что это за свойство — «reentrancy»?

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

33) Что такое «recursive parallelism»?

Разбиение задачи на подзадачи по методу разделяй-и-властвуй (divide-and-conqueer). Каждая задача решается в отдельном потоке.

34) Что такое «iterative parallelism»?

Разбиение задачи на независимые итерации, каждая итерация может считаться независимо в своем потоке.

35) Что это за вариант архитектуры «pipeline»?

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

36) Что такое «poison message»?

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

37) Что такое «mutual exclusion»? Примеры как добиться в Java.

Критическая секция, семафор с одним состоянием; простейший пример — synchronized(obj) { … }

38) Что такое «condition waiting»? Примеры как добиться в Java.

Вообще это называется guarded lock. Если поток захватил монитор и позвал wait() на нем чтобы дождаться некоторого состояния, проверка наступления этого события должна быть завернута в цикл вида

    public synchronized guardedJoy() {
    while(!joy) {
        try {
            wait();
        } catch (InterruptedException e) {}
    }
    System.out.println("Joy and efficiency have been achieved!");
}

39) Преимущества ScheduledThreadPool перед java.util.Timer.

  • Timer имеет только один фоновый поток для исполнения, т.е. если задач много или они долгие — поток не справляется, время запуска других задачек сдвигается
  • Timer может умереть из за неожиданного RuntimeException полученного на выходе любой из TimerTask’ов
  • Timer криво работает если меняется системное время, т.к. он использует object.wait() чтобы дожидаться следующего момента исполнения
  • ScheduledThreadPool использует System.nanotime() который глючит на старых OC (winxp) и сильно зависит от версии OS и CPU

40) Различия между java.util.concurrent.Atomic*.compareAndSwap() и java.util.concurrent.Atomic*.weakCompareAndSwap().

  • weak не создает memory barrier и не дает гарантии happens-before
  • weak сильно зависит от нижележащего кеша\CPU, и может возвращать false без видимых причин и делать это часто
  • weak, как следствие, более легкая операция, но поддерживаемая далеко не всеми архитектурами и не всегда эффективная

41) Что в SynchronousQueue уникально для BlockingQueue.

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

42) Что такое «рандеву»? При помощи каких классов в Java его можно организовать?

Способ собрать запущенные потоки в «одном месте». Можно использовать Barrier, Latch, spin lock..

43) Что такое «false sharing». Может ли происходит в Java. Если есть, то приведите примеры и как бороться. Если нет, то как побороли разработчики JVM.

Эффект который происходит если данные\переменные нескольких потоков попадают в один cache line процессора. В этом случае на многопроцессорной системе контроллеру кеша приходится делать много лишних действий чтобы каждый раз удостовериться что линии кеша на различных CPU не рассинхронизовались. Решить проблему вроде как можно изменив layout конечной памяти\добавив данных для padding’a. В JVM вроде никак не решено.

44) Thread.getState() возвращает экземпляр Thread.State. Какие возможны значения?

new, runnable, waiting, time_wait, terminated, blocked

45) Напишите простейший ограниченный буфер для многих производителей/многих потребителей с использованием synchronize{}. С использованием ReentrantLock.

вариант с synchronized()

public class QueueUsingSynchronized {
    private final Object[] array;
    private final int maxSize;
    // [Tail (cyclic adds)->] [.] [.] [Head (cyclic adds)->]
    private int tail; // read from here (points to a filled element, if not null)
    private int head; // add nodes here (points to a filled element, if not null)
    private volatile int size = 0;
    private Object isEmpty = new Object();
    private Object isFull = new Object();

    public QueueUsingSynchronized(int capacity) {
        this.maxSize = capacity;
        array = new Object[maxSize];
        tail = 0;
        head = 0;
        size = 0; // publish
    }

    final int cycleInc(int index) {
        return ++index == maxSize ? 0 : index;
    }

    public T get() throws InterruptedException {
        if (size == 0) {
            // get() sleeps until size becomes positive
            synchronized (isEmpty) {
                while (size < 1) {                     isEmpty.wait();                 }             }         }         try {             synchronized (this) {                 final Object value = array[tail];                   array[tail] = null;                 if (size > 1) {
                    tail = cycleInc(tail);
                }
                size--;
                return (T) value;
            }
        } finally {
            // now we have some place for adding new values (size decreased by 1), wake up put()
            synchronized (isFull) {
                isFull.notify();
            }
        }

    }

    public void put(T value) throws InterruptedException {
        if (value == null) {
            throw new NullPointerException("Cannot add null value");
        }
        if (size == maxSize) {
            // put sleeps until size < maxSize
            synchronized (isFull) {
                while (size == maxSize) {
                    isFull.wait();
                }
            }
        }
        synchronized (this) {
            if (size == 0) {
                //head == tail == null element , assign new value to head element
                array[head] = value;
            } else {
                // increment then assign new value to head element
                head = cycleInc(head);
                array[head] = value;
            }
            size++;
        }
        // now we have some objects which can be retrieved by get (size increased by 1), wake up get()
        synchronized (isEmpty) {
            isEmpty.notify();
        }
    }

}

вариант с ReentrantLock

public class QueueUsingLock {
    private volatile int size = 0;
    private final Object[] array;
    private final int maxSize;
    // [Tail (cyclic adds)->] [.] [.] [Head (cyclic adds)->]
    private int tail; // read from here (points to a filled element, if not null)
    private int head; // add nodes here (points to a filled element, if not null)
    private ReentrantLock lock = new ReentrantLock();
    private Condition isEmpty = lock.newCondition();
    private Condition isFull = lock.newCondition();

    public QueueUsingLock(int capacity) {
        try {
            lock.lock();
            this.maxSize = capacity;
            array = new Object[maxSize];
            tail = 0;
            head = 0;
        } finally {
            lock.unlock(); // publish
        }
    }

    final int cycleInc(int index) {
        return ++index == maxSize ? 0 : index;
    }

    public T get() throws InterruptedException {
        try {
            lock.lockInterruptibly();
            if (size == 0) {
                // get() sleeps until size becomes positive
                while (size < 1) {                     isEmpty.await(); // release lock and sleep                 }             }             final Object value = array[tail];               array[tail] = null;             if (size > 1) {
                tail = cycleInc(tail);
            }
            size--;
            // now we have some place for adding new values (size decreased by 1), wake up put()
            isFull.signal();
            return (T) value;
        } finally {
            lock.unlock();
        }
    }

    public void put(T value) throws InterruptedException {
        try {
            lock.lockInterruptibly();
            if (value == null) {
                throw new NullPointerException("Cannot add null value");
            }
            if (size == maxSize) {
                // put sleeps until size < maxSize
                while (size == maxSize) {
                    isFull.await(); // release lock and wait
                }
            }
            if (size == 0) {
                //head == tail == null element , assign new value to head element
                array[head] = value;
            } else {
                // increment then assign new value to head element
                head = cycleInc(head);
                array[head] = value;
            }
            size++;
            // now we have some objects which can be retrieved by get (size increased by 1), wake up get()
            isEmpty.signal();
        } finally {
            lock.unlock();
        }
    }
}

46) Напишите реализацию класса с неблокирующим методом BigInteger next(), который возвращает элементы последовательности: [1, 2, 4, 8, 16, ...]. Код должен корректно работать в многопоточной среде.

public class Counter {

    private AtomicReference refCounter = new AtomicReference(null);

    public BigInteger next() {
        BigInteger oldValue;
        BigInteger newValue;
        do {
            oldValue = refCounter.get();
            newValue = oldValue==null ? BigInteger.valueOf(1) : oldValue.shiftLeft(1);
        } while(!refCounter.compareAndSet(oldValue, newValue));
        return newValue;
    }

}

47) У ReentrantLock созданного с аргументом true все же один из методов захвата блоктровки — не fair. Какой? Как это обойти?

tryLock(), использовать tryLock(long,TimeUnit)

48) Приведите наиболее существенное отличие между CountDownLatch и Barrier.

Barrier накапливает потоке в точке вызова await() пока их количество не превысит заданное. CountDownLatch ждет пока количество вызовов countDown() не превысит нужное, и тогда разблокирует await().

49) Что Вы знаете о Erlang? Что в нем есть существенного связанного с многопоточностью такого, чего нет в Java?

Функциональный язык программирования, с неизменяемыми переменными, легковесными потоками, заточенный на параллелизм и многопоточность. Все «потоки» общаются посредством посылки сообщений, разделяемые переменные\память отсутствуют как класс. Разработан для коммутаторов в Ericsson. Имеет внутреннюю встроенную базу, довольно быструю и устойчивую.

50) Что Вы знаете о CSP? Что в нем есть существенного связанного с многопоточностью такого, чего нет в Java?

Это язык для описания паттернов взаимодействия потоков исполнения в параллельных вычислениях. В CSP есть формальный мат аппарат для описания и проверки\доказательства непротиворечивости свойств задуманной «системы» параллельной разработки .

51) Отличие Thread.start() и Thread.run()?

start() порождает новый поток в котором исполняет код run().

Книжки по теме

The Art of Multiprocessor Programming

the-art

Java Concurrency in Practice

java_conc_thumb

Pattern-Oriented Software Architecture Volume 2: Patterns for Concurrent and Networked Objects

java_patterns

The Little Book of Semaphores

  bookofsemaphores

Другие записи...

1 Ответ

  1. Алексей:

    Добрый день. Спасибо за подборку. Замечание:

    >> Если поток заблокирован на мониторе, ждет другой поток итп (wait, notify) — вылетит InterruptedException

    InterruptedException не выбрасывается, когда поток находится в состоянии BLOCKED.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

* Please Enter the Output

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>