JavaRush /Blog Java /Random-VI /Cấp độ 27. Trả lời các câu hỏi phỏng vấn về chủ đề cấp độ...
DefNeo
Mức độ

Cấp độ 27. Trả lời các câu hỏi phỏng vấn về chủ đề cấp độ

Xuất bản trong nhóm
Cấp độ 27. Trả lời câu hỏi phỏng vấn về chủ đề cấp độ - 1
  1. Bế tắc là gì?

    Bế tắc là tình trạng hai hoặc nhiều luồng bị chặn, chờ đợi lẫn nhau. Bế tắc còn được gọi là bế tắc.

    Bế tắc là tình huống trong đó hai hoặc nhiều quy trình chiếm một số tài nguyên đang cố gắng lấy một số tài nguyên khác bị chiếm bởi các quy trình khác và không có quy trình nào có thể chiếm tài nguyên mà chúng cần và theo đó, giải phóng tài nguyên đã chiếm.

    Có bế tắc về thứ tự đồng bộ hóa (giải quyết bằng cách gán thứ tự);

    Bế tắc giữa các đối tượng (các đối tượng khác nhau cố gắng truy cập vào cùng các khối được đồng bộ hóa);

    Bế tắc tài nguyên (khi cố gắng truy cập một số tài nguyên mà mỗi lần chỉ có một luồng có thể sử dụng).

  2. Bạn biết những chiến lược nào để ngăn chặn bế tắc xảy ra?

    Tất nhiên, nếu mã được viết mà không có bất kỳ lỗi nào thì sẽ không có bế tắc trong đó. Nhưng ai có thể đảm bảo rằng mã của mình được viết không có lỗi? Tất nhiên, kiểm tra giúp xác định một phần lỗi đáng kể, nhưng như chúng ta đã thấy trước đó, lỗi trong mã đa luồng không dễ chẩn đoán và ngay cả sau khi kiểm tra, bạn cũng không thể chắc chắn rằng không có tình huống bế tắc. Bằng cách nào đó chúng ta có thể tự bảo vệ mình khỏi bị chặn không? Câu trả lời là có. Các kỹ thuật tương tự được sử dụng trong các công cụ cơ sở dữ liệu, thường cần phục hồi từ các bế tắc (liên quan đến cơ chế giao dịch trong cơ sở dữ liệu). Giao diện Lockvà các triển khai của nó có sẵn trong gói java.util.concurrent.lockscho phép bạn cố gắng chiếm màn hình được liên kết với một phiên bản của lớp này bằng phương thức tryLock(trả về true nếu có thể chiếm màn hình).

    Ngoài ra còn có một chiến lược sử dụng các lệnh gọi mở, tức là gọi các phương thức của các đối tượng khác bên ngoài khối được đồng bộ hóa.

    Link tới bài viết: Deadlock trong Java và cách khắc phục

  3. Sự bế tắc có thể xảy ra khi sử dụng các phương thức không wait-notify?

    Cá nhân tôi rất khó trả lời câu hỏi này, nhưng sau khi đọc nhiều cuộc thảo luận khác nhau về chủ đề này trên Internet, chúng ta có thể nói như sau:

    Có thể tránh được bế tắc thông qua việc sử dụng hợp lý synchronized, volatile, theo dõi ( wait(), notify(), notifyAll()) và nếu bạn tìm hiểu sâu hơn thì hãy sử dụng các lớp java.utils.concurrent: thay vì các bộ sưu tập thông thường - các tùy chọn đa luồng ( ConcurrentHashMap, chẳng hạn); nếu bạn cần một cách phức tạp hơn để đồng bộ hóa các chủ đề - khác CyclicBarrier, CountDownLatch.

    waitNếu bạn sử dụng - chính xác notifythì bế tắc sẽ không xảy ra.)))

    Đây là liên kết: Bế tắc hoặc Bế tắc.

  4. Cái nào thường được sử dụng hơn: notifyhoặc notifyAll?

    The java.lang.Object.notify() wakes up a single thread that is waiting on this object's monitor. If any threads are waiting on this object, one of them is chosen to be awakened. The choice is arbitrary and occurs at the discretion of the implementation. A thread waits on an object's monitor by calling one of the wait methods.

    This method should only be called by a thread that is the owner of this object's monitor. A thread becomes the owner of the object's monitor in one of three ways:

    • By executing a synchronized instance method of that object.
    • By executing the body of a synchronized statement that synchronizes on the object.
    • For objects of type Class, by executing a synchronized static method of that class.

    Only one thread at a time can own an object's monitor.

    The java.lang.Object.notifyAll() wakes up all threads that are waiting on this object's monitor. A thread waits on an object's monitor by calling one of the wait methods.

    The awakened threads will not be able to proceed until the current thread relinquishes the lock on this object. The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object.

    This method should only be called by a thread that is the owner of this object's monitor.

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

  5. Метод wait рекомендуется использовать с конструкциями if or while?

    Здесь отвечу просто цитатой из сайта: Синхронизация потоков

    По поводу вызова метода wait. Это уже из разряда чистой техники. Рекомендуется вызывать wait изнутри цикла while. Т.е., писать не

    if (some condition){
        obj.wait()
    }

    ..., а

    while (some condition){
        obj.wait()
    }

    Зачем это надо. Дело в том, что notify может вызвать кто угодно. Просто по ошибке, от которой никто не застрахован. В том случае из опыта, о котором я рассказывал выше, мы взялись за переделку именно для того, чтобы избежать такой возможности. Просто спрятали an object, на котором происходит синхронизация. И доступ к нему имел только наш code. Это хорошая практика, но не всегда возможно, к сожалению. Так вот, если поток ждет выполнения некоторого условия – вариант с while надежнее. Если поток пустor по ошибке – он опять проверит condition и, если надо, будет ждать дальше.

    Кроме того, не исключена возможность и простого выхода из ожидания без вызова notify. Я честно признаюсь, что не видел этого в спецификации виртуальной машины, хотя специально искал. Но некоторые «гуру» утверждают, что VM может выйти из состояния ожидания самопроизвольно. И более того, периодически это наблюдается. Если кто-нибудь даст ссылку на соответствующую спецификацию – буду благодарен!

  6. What происходит после вызова метода notifyAll?

    The java.lang.Object.notifyAll() wakes up all threads that are waiting on this object's monitor. A thread waits on an object's monitor by calling one of the wait methods.

    Пробуждает все нити, которые ждали на этом мониторе.

  7. Какие выгоды получает an object, если он immutable?

    Нашел комментарий на: immutable-an objectы и многопоточность

    Immutable an object — это an object, состояние которого после создания невозможно изменить. В случае Java это значит что все поля экземпляра у класс отмечены How final и являются примитивами or тоже immutable типами.

    Пример:

    public class ImmutablePoint {
        private final int x;
        private final int y;
        private final String description;
    
        public ImmutablePoint(int x, int y, String description) {
            this.x = x;
            this.y = y;
            this.description = description;
        }
    }

    После создания экземпляра ImmutablePoint его модификация невозможна.

    Простейший пример immutable класса из JDK это String. Любые методы, которые вы вызовите на строке (например description.toLowerCase()) вернут новую строку, а не модифицируют исходную.

    Пример mutable класс из JDK — Date. Например myDate.setHours(x) модифицирует сам экземпляр myDate!

    Есть разница между immutable-an objectом (то есть, неизменяемым), и final-ссылкой.

    Ключевое слово final для an objectных типов гарантирует неизменяемость лишь ссылки, но не самого an object. Например, если у вас есть final-link на ArrayList<T>, вы тем не менее можете добавлять в него новые элементы or изменять существующие.

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

    Использование неизменяемых an objectов даёт много выгод. Например, о таком an objectе намного легче судить в ситуации, когда во многих частях программы есть link на него (для изменяемого an object, любая часть программы может вызвать мутирующую функцию в практически любой момент времени и из любого потока).

    Но то, что для нас важно в контексте вопроса — неизменяемые an objectы не требуют синхронизации при многопоточном доступе. Вот собственно и вся рекомендация: используйте неизменяемые an objectы, и вам не придётся думать о том, что нужно, а что не нужно синхронизировать. Единственная возможная проблема — если вы внутри ещё не отработавшего конструктора публикуете ссылку на an object, через которую к нему может получить доступ кто-нибудь ещё, и увидеть an object в изменяющемся состоянии! (Это бывает не так уж и редко. Например, иногда программист хочет добавить an object в конструкторе в коллекцию всех an objectов данного типа.)


    Следует различать действительно неизменяемые an objectы, и an objectы, имеющие лишь интерфейс «только для чтения». При чтении an object тем не менее может менять свою внутреннюю структуру (например, кэшировать самый свежий request данных). Такие an objectы не являются в строгом смысле неизменяемыми, и не могут быть использованы из разных потоков без предосторожностей. (Поэтому, если ваш an object включает другие an objectы, убедитесь, что documentация гарантирует их неизменяемость!)


    Обратите внимание, что для полей неизменяемого an object вы практически обязаны использовать final! Дело в так называемой безопасной публикации. Смотрите. Инструкции в Java-программе могут быть переставлены How оптимизатором, так и процессором (у Java достаточно слабая модель памяти). Поэтому, если не предпринимать специальных действий, окончание работы конструктора и присвоение значений полям может быть переставлено (но невидимо в рамках текущего потока)! Использование final гарантирует, что такого не произойдёт.

    В случае многопоточного программирования преимущества immutable классов очевидны: после создания an objectы можно передавать другим потокам и они всегда будут в актуальном состоянии. Т.е. вам не надо проверять не устарело ли состояние вашего экземпляра и не модифицировал ли его другой поток пока вы с ним работаете. Например, у вас есть метод bill(Date endDate), в нём вы наивно проверяете соответствие endDate Howим-то предварительным условиям и начинаете с ней работать. В этот момент другой поток может изменить endDate, например установит её глубоко в прошлое. Последствия могут быть самыми удивительными.

  8. What такое «thread-safe»?

    Опять же: What is thread Safe in java? [duplicate]

    Thread safe means that a method or class instance can be used by multiple threads at the same time without any problems occuring.

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

    Consider the following method:

    private int myInt = 0;
    public int AddOne()
    {
        int tmp = myInt;
        tmp = tmp + 1;
        myInt = tmp;
        return tmp;
    }

    Now thread A and thread B both would like to execute AddOne(). but A starts first and reads the value of myInt (0) into tmp. Now for some reason the scheduler decides to halt thread A and defer execution to thread B. Thread B now also reads the value of myInt (still 0) into it's own variable tmp. Thread B finishes the entire method, so in the end myInt = 1. And 1 is returned. Now it's Thread A's turn again. Thread A continues. And adds 1 to tmp (tmp was 0 for thread A). And then saves this value in myInt. myInt is again 1.

    Здесь и нить А и нить B хотят выполнить AddOne (). но А начинается первой и считывает meaning myInt (0) в TMP. Теперь по некоторым причинам планировщик решает остановить поток А и отложить выполнение нити B. Поток В настоящее время также считывает meaning myInt (0) в его собственной переменной TMP. Нить B завершает весь метод так, что в конце концов myInt = 1. И 1 возвращается. Поток А продолжается. И добавляет 1 к TMP (TMP 0 для нити A). А затем сохраняет это meaning в myInt. myInt снова 1.

    So in this case the method AddOne was called two times, but because the method was not implemented in a thread safe way the value of myInt is not 2, as expected, but 1 because the second thread read the variable myInt before the first thread finished updating it.

    Так что в этом случае метод AddOne был вызван два раза, но так How этот метод не был реализован в потоке безопасным способом величина myInt не 2, How ожидалось, а 1, потому что второй поток чтения переменной myInt закончился перед первой нитью до его обновления.

    Creating thread safe methods is very hard in non trivial cases. And there are quite a few techniques. In Java you can mark a method as synchronized, this means that only one thread can execute that method at a given time. The other threads wait in line. This makes a method thread safe, but if there is a lot of work to be done in a method, then this wastes a lot of time. Another technique is to 'mark only a small part of a method as synchronized' by creating a lock or semaphore, and locking this small part (usually called the critical section). There are even some methods that are implemented as lockless thread safe, which means that they are built in such a way that multiple threads can race through them at the same time without ever causing problems, this can be the case when a method only executes one atomic call. Atomic calls are calls that can't be interrupted and can only be done by one thread at a time.

    Creation потоко-безопасных методов очень трудно. В Java вы можете пометить метод How синхронизированный, это будет означать, что только один поток может выполнить этот метод в данный момент времени. Другие нити, будут ждать в очереди. Это делает метод потоко-безопасным, но если много работы предстоит сделать в методе, то на это будет уходить много времени. Другой метод заключается в разметке лишь малой части метода, How синхронизированного 'путем создания локов(locks) or семафоров, и запирании этой небольшой части (обычно называемый критический раздел (critical section)). Есть даже некоторые методы, которые реализуются How беззамочные потокобезопасные (lockless thread safe), это означает, что они построены таким образом, что несколько потоков могут проходить через них в одно время и никогда не вызывают проблем, это может быть в случае, когда метод выполняет только один атомарный вызов. Атомарные вызовы это вызовы, которые не могут быть прерваны, и может быть реализованы только одним потоком.

  9. What такое "happens-before"?

    Есть статья на википедии, она не конкретно про "happens-before", но все – таки.

    А так-то:
    «Выполняется прежде» (англ. happens before) — отношение строгого частичного порядка (арефлексивное, антисимметричное, транзитивное), введённое между атомарными командами (++ и -- не атомарны!), придуманное Лесли Лэмпортом и не означающее «физически прежде». Оно значит, что вторая команда будет «в курсе» изменений, проведённых первой.

    Модель памяти Java

    В частности, одно выполняется прежде другого для таких операций (список не исчерпывающий):

    • Синхронизация и мониторы:
    • Захват монитора (начало synchronized, метод lock) и всё, что после него в том же потоке.
    • Возврат монитора (конец synchronized, метод unlock) и всё, что перед ним в том же потоке.
    • Таким образом, оптимизатор может заносить строки в синхроблок, но не наружу.
    • Возврат монитора и последующий захват другим потоком.
    • Запись и чтение:
    • Любые зависимости по данным (то есть запись в любую переменную и последующее чтение её же) в одном потоке.
    • Всё, что в том же потоке перед записью в volatile-переменную, и сама запись.
    • volatile-чтение и всё, что после него в том же потоке.
    • Запись в volatile-переменную и последующее считывание её же.[4][2] Таким образом, volatile-запись делает с памятью то же, что возврат монитора, а чтение — то же, что захват.[5] А значит: если один поток записал в volatile-переменную, а второй обнаружил это, всё, что предшествует записи, выполняется раньше всего, что идёт после чтения; см. иллюстрацию.
    • Для an objectных переменных (например, volatile List x;) столь сильные гарантии выполняются для ссылки на an object, но не для его содержимого.
    • Обслуживание an object:
    • Статическая инициализация и любые действия с любыми экземплярами an objectов.
    • Запись в final-поля в конструкторе[6] и всё, что после конструктора. Как исключение из всеобщей транзитивности, это соотношение happens-before не соединяется транзитивно с другими правилами и поэтому может вызвать межпоточную гонку.[7]
    • Любая работа с an objectом и finalize().
    • Обслуживание потока:
    • Запуск потока и любой code в потоке.
    • Зануление переменных, относящихся к потоку, и любой code в потоке.
    • Код в потоке и join(); code в потоке и isAlive() == false.
    • interrupt() потока и обнаружение факта останова.
  10. What такое JMM?

    Java Memory Model

    Вот link: Chapter 17. Threads and Locks

    И вот выдержка из нее:

    A memory model describes, given a program and an execution trace of that program, whether the execution trace is a legal execution of the program. The Java programming language memory model works by examining each read in an execution trace and checking that the write observed by that read is valid according to certain rules.

    Я понял на своем уровне что это набор правил:

    Правило № 1: однопоточные программы исполняются псевдопоследовательно. Это значит: в реальности процессор может выполнять несколько операций за такт, заодно изменив их порядок, однако все зависимости по данным остаются, так что поведение не отличается от последовательного. Правило № 2: нет невесть откуда взявшихся значений. Чтение любой переменной (кроме не-volatile long и double, для которых это правило может не выполняться) выдаст либо meaning по умолчанию (ноль), либо что-то, записанное туда другой командой.br /> И правило № 3: остальные события выполняются по порядку, если связаны отношением строгого частичного порядка «выполняется прежде» (англ. happens before).

  11. Какое исключение вылетит, если вызвать wait не в блоке synchronized?

    Вот link: Ответы на вопросы на собеседование Multithreading (часть 2)

    Основная причина вызова wait и notify из статического блока or метода в том, что Java API обязательно требует этого. Если вы вызовете их не из синхронизированного блока, ваш code выбросит IllegalMonitorStateException. Более хитрая причина в том, чтобы избежать состояния гонки между вызовами wait и notify.

Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION