Unsafe.park vs Object.wait

У меня есть пара вопросов, касающихся Unsafe.park и Object.wait (и их соответствующих методов резюме):

  1. Какой из них следует использовать в целом?
  2. Какой из них имеет лучшую производительность?
  3. Есть ли преимущество использования Unsafe.park сравнению с Object.wait?

Ответ 1

Вы не должны использовать ни один из этих методов, если вы программист приложения.

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

Почему бы не попробовать использовать конструкцию более высокого уровня, такую как java.util.concurrent.locks?

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

Предполагается, что он работает быстрее, чем Object.wait(), который работает с абстракцией монитора, если вы знаете, какой поток нужно блокировать/разблокировать.

Кстати, распаковка не так уж и небезопасна, если ее использовать изнутри Java:

public native void unpark(Object thread)

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

Ответ 2

Наиболее эффективным ожиданием является LockSupport.park/unpark, который не требует грязного (прямого) использования Unsafe и не платит за повторную синхронизацию локального кэша памяти вашего потока.

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

В большинстве случаев это НЕ то, что вы хотите. В большинстве случаев вы хотите, чтобы ваш поток видел все обновления, которые произошли "до сих пор", поэтому вы должны использовать Object.wait() и .notify(), так как вы должны синхронизировать состояние памяти, чтобы использовать их.

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

Вы можете использовать его для эффективного "сна", без необходимости в другом потоке, чтобы разбудить вас через LockSupport.parkNanos или .parkUntil (для millis; оба метода просто вызывают Unsafe для вас).

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

Удачи и счастливого кодирования!

Ответ 3

LockSupport.park/unpark имеет лучшую производительность, но это слишком низкий уровень API.

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

    Object lockObject = new Object();
    Runnable task1 = () -> {
        synchronized (lockObject) {
            System.out.println("thread 1 blocked");
            try {
                lockObject.wait();
                System.out.println("thread 1 resumed");
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

        }
    };
    Thread thread1 = new Thread(task1);
    thread1.start();

    Runnable task2 = () -> {
        System.out.println("thread 2 running ");
        synchronized (lockObject) {
            System.out.println("thread 2 get lock");
            lockObject.notify();
        }
    };
    Thread thread2 = new Thread(task2);
    thread2.start();

В этом случае thread2 может получить блокировку и уведомить thread1 о возобновлении, потому что lockObject.wait(); снимет блокировку.

    Object lockObject = new Object();
    Runnable task1 = () -> {
        synchronized (lockObject) {
            System.out.println("thread 1 blocked");
            LockSupport.park();
            System.out.println("thread 1 resumed");

        }
    };
    Thread thread1 = new Thread(task1);
    thread1.start();

    Runnable task2 = () -> {
        System.out.println("thread 2 running ");
        synchronized (lockObject) {
            System.out.println("thread 2 get lock");
            LockSupport.unpark(thread1);
        }
    };
    Thread thread2 = new Thread(task2);
    thread2.start();

Однако, если вы используете LockSupport.park/unpark как это, это приведет к мертвой блокировке. потому что thread1 не снимет блокировку с помощью LockSupport.park. следовательно, thread1 не может быть возобновлено.

Так что будьте осторожны, у них другое поведение, кроме блокировки потока. И на самом деле, есть некоторый класс, который мы можем использовать для удобства координации в многопоточной среде, такой как CountDownLatch, Semaphore, ReentrantLock