LINUX.ORG.RU

такой опасный java.u.c.l.ReentrantLock

 


3

5

Берегите ноги господа:

package wayerr.testlock;

import java.util.concurrent.locks.ReentrantLock;

public class TestLock {

    private static final ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) throws Exception {
        Thread thread = new Thread(() -> {
            try {
                recursion();
            } catch(java.lang.StackOverflowError e) {
                // it expected
            }
        });
        thread.start();
        thread.join();
        System.out.println("Lock is locked: " + lock.isLocked());
        if(lock.tryLock()) {
            try {
                System.out.println("OK");
            } finally {
                lock.unlock();
            }
        } else {
            System.out.println("FAIL");
        }
    }

    static final void recursion() {
        lock.lock();
        try {
            recursion();
        } finally {
            lock.unlock();
        }
    }
}

В _этом_ тесте всегда выдает:

Lock is locked: true
FAIL

Чсх, на продакшене искать причину крайне весело, особенно с учетом того что в реальном коде он проявляться будет не всегда. Если метод recursion - не пустой то вероятность что стек кончится внутри lock - уменьшается.

зы. с каждым таким фокусом все больше любишь старый добрый synchronized .

ps. это починили в 9-ке: http://bugs.java.com/view_bug.do?bug_id=8046936 хотя известно с 11 года

Deleted

Последнее исправление: Deleted (всего исправлений: 1)

Ну да, если тебе стека на вызов recursion не хватило, то и на вызов lock.unlock() не хватит. А ты уверен, что synchronized поможет? Он под капотом не вызывает похожие методы?

Legioner ★★★★★
()
Ответ на: комментарий от Legioner

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

Deleted
()
Ответ на: комментарий от tailgunner

что не придется перезапускать все приложение 8)

Deleted
()

An Error is a subclass of Throwable that indicates serious problems that a reasonable application should not try to catch. Most such errors are abnormal conditions.

This.

asaw ★★★★★
()

В _этом_ тесте всегда выдает:

Нет, не всегда. Запустил раз 15 - иногда выдает true/FAIL, иногда false/OK.
JVM не отвечает за thread scheduling - этим занимается OS:

The operating system is responsible for scheduling all threads and dispatching to any available CPU.

(см. Threading Model: http://openjdk.java.net/groups/hotspot/docs/RuntimeOverview.html#Thread Manag...)

Например, даже тут (https://docs.oracle.com/javase/tutorial/essential/concurrency/deadlock.html) они явно пишут:

When Deadlock runs, it's __extremely likely__ that both threads will block when they attempt to invoke bowBack.

'extremely likely', но не 100%.

kovrik ★★★★★
()
Ответ на: комментарий от kovrik

Сколько ни запускал — всегда одинаково отвечает (macOS). Странно, если у тебя не так. Ты текст программы не менял? Thread scheduling тут не при чём.

Legioner ★★★★★
()
Ответ на: комментарий от Legioner

Не знаю как в macOS, нет возможности проверить. На работе у нас Windows. Если запускаю из Идеи, то в 10% случаев выскакивает OK. Если запускаю из CLI (тупо java Test), то всегда FAIL (по крайней мере не удалось поймать OK).

kovrik ★★★★★
()
Ответ на: комментарий от Legioner

Окей, я уже начал было грешить на Идею (мало ли как она компеляет и билдит проект), но после кучи попыток удалось получить OK и в CLI (используя только javac и java):

Windows 7 SP1

> java -version
java version "1.8.0_91"
Java(TM) SE Runtime Environment (build 1.8.0_91-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.91-b15, mixed mode)

> cat TestLock.java
import java.util.concurrent.locks.ReentrantLock;

public class TestLock {

    private static final ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) throws Exception {
        Thread thread = new Thread(() -> {
            try {
                recursion();
            } catch(java.lang.StackOverflowError e) {
                // it expected
            }
        });
        thread.start();
        thread.join();
        System.out.println("Lock is locked: " + lock.isLocked());
        if(lock.tryLock()) {
            try {
                System.out.println("OK");
            } finally {
                lock.unlock();
            }
        } else {
            System.out.println("FAIL");
        }
    }

    static final void recursion() {
        lock.lock();
        try {
            recursion();
        } finally {
            lock.unlock();
        }
    }
}

> javac TestLock.java; for i in `seq 1 100`; do java TestLock; done
Lock is locked: true
FAIL
Lock is locked: true
FAIL
Lock is locked: true
FAIL
Lock is locked: true
FAIL
Lock is locked: false
OK
...
Lock is locked: true
FAIL
Lock is locked: true
FAIL
Lock is locked: false
OK
Lock is locked: true
FAIL
Lock is locked: true
FAIL
Lock is locked: true
FAIL

Итого: целых 2 OK.
Иногда раза по 3-4 подряд запускаю - ни одного ОК. Иногда сразу 2-3.

kovrik ★★★★★
()
Ответ на: комментарий от Legioner

Дома проверю на macOS'и.

Я думаю, что это всё из-за шедулера.

1. JVM шедулингом не занимается, это делает ОС

2. thread.start() не означает, что тред мгновенно запустится и начнет выполнять run(). Он просто «отдает» тред шедулеру и просит его запустить. JVM не гарантирует порядок запуска.

3. join() вызывает join(0), который тупо блокирует текущий тред и ждет пока isAlive() другого треда не вернет false:

    public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;
...
        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
...

Вангую, что иногда тред просто не успевает стартануть (ибо шедулер; следовательно, isAlive() возвращает false; lock еще не залочен, tryLock проходит успешно) и join() выходит.

kovrik ★★★★★
()
Последнее исправление: kovrik (всего исправлений: 1)
Ответ на: комментарий от kovrik

тред просто не успевает стартануть

это пахнет нарушением «контракта» Thread, более вероятно что у тебя SOE вылетает до блокировки или после разблокировки.

Проверить легко - если добавил sleep и все исчезло - значит ты прав. В противном случае убери catch и сравни стектрейсы (у него должна быть разной верхушка).

Deleted
()
Ответ на: комментарий от kovrik

Не, так не бывает. После start() поток находится в состоянии «запущен» и планировщик тут не при чём. Управление ему вполне может передаться и потом, но статус уже будет вполне определённый. У меня единственное предположение — иногда включается JIT и инлайнит все вызовы функций в lock.unlock();, соответственно после этого вызова функции не происходит и исключение блокировка корректно снимается. Хотя как проверить — хз.

Legioner ★★★★★
()

Как дошел до такой жизни? Точнее, что пришлось писать, что в 2017 приходится вот таким заниматься?

cdshines ★★★★★
()
Ответ на: комментарий от Deleted

поведение не изменится (ну в консоли будет стектрейс)

Это уже две большие разницы. Конечно, было бы хорошо, если бы жабка уже по умолчанию вела бы себя в этом случае как-то так:

import java.util.concurrent.locks.ReentrantLock;

public class TestLock {

    private static final ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) throws Exception {
        Thread thread = new Thread(() -> {
            recursion();
        });
        thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            public void uncaughtException(Thread t, Throwable e) {
                e.printStackTrace(System.out);
                System.exit(1);
            }
        });
        thread.start();
        thread.join();
        System.out.println("Lock is locked: " + lock.isLocked());
        if(lock.tryLock()) {
            try {
                System.out.println("OK");
            } finally {
                lock.unlock();
            }
        } else {
            System.out.println("FAIL");
        }
    }

    static final void recursion() {
        lock.lock();
        try {
            recursion();
        } finally {
            lock.unlock();
        }
    }
}
asaw ★★★★★
()
Ответ на: комментарий от asaw

поведение не изменится

Это уже две большие разницы.

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

Deleted
()
Ответ на: комментарий от Deleted

на продакшене искать причину крайне весело
Таки я был прав.

Ага, поспорь сам с собой ещё немного.

asaw ★★★★★
()

Не совсем понял, при завершении потока мы обнаруживаем ReentrantLock неразлоченным, если это было в рекурсии?

I-Love-Microsoft ★★★★★
()
Ответ на: комментарий от I-Love-Microsoft

если это было в рекурсии?

если был StackOverflowError, правда пишут что не всегда такой опасный java.u.c.l.ReentrantLock (комментарий)

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

Deleted
()

А уж j.u.c.a.AtomicInteger как опасен, не передать!

    private static final AtomicInteger counter = new AtomicInteger(0);

    private static Throwable error = null;

    public static void main(String[] args) throws Exception {
        final Thread thread = new Thread(() -> {
            try {
                recursion();
            } catch (java.lang.StackOverflowError e) {
                // it expected
                error = e;
            }
        });
        thread.start();
        thread.join();
        System.out.println("Count = " + counter.get());
        if (error != null) {
            System.out.println(error.getClass().getCanonicalName() + ": " + error.getMessage());
        }
    }

    static void recursion() {
        counter.incrementAndGet();
        try {
            recursion();
        } finally {
            counter.decrementAndGet();
        }
    }

(-XX:MaxInlineSize=0) =>

Count = 2
java.lang.StackOverflowError: null

с каждым таким фокусом все больше любишь старые добрые ++/--

korvin_ ★★★★★
()
Ответ на: комментарий от korvin_

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

Deleted
()

ктото не знает что такое потоки и как они реализованы

тыб еще удивлялся что присвоил <thread_A>.arg=123
и на следущей строчки попыталсябы читать а тамбы был 0 и только через
рандомное кол-во мсек тамбы появилось 123

лок это переключатель bool проверяя его знаешь состояние потока и ждеш пока поток дойдет до лока

добавь паузу в основной поток на ожидание труе от лока-и будет работать как ты хочешь

поломанную Си логику притащил в нормальный язык-противно смотреть

lwx51033
()
Ответ на: комментарий от Deleted

по твоей ссылке написан какойто бред не относящийся к твоему вопросу

челик по ссылке предлагает «оставлять немного(што?) стека на критические(ват?) случаи»

ктото на первом курсе или в школе такой баг мог описать

lwx51033
()
Ответ на: комментарий от Deleted

очевидный трабл в том что «упавший» поток может сломать все приложение и ничего не поделать.

очередной бред из Цпп логики

упавший поток ломает приложение потому что у тебя программа спроектированна так что Упавший_поток_ломает_все

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

это и будет проектом на java

а не гнусным «портом из с++»

lwx51033
()
Ответ на: комментарий от cdshines

Как дошел до такой жизни? Точнее, что пришлось писать, что в 2017 приходится вот таким заниматься?

цпп головного мозга, вот что

lwx51033
()
Ответ на: комментарий от Legioner

После start() поток находится в состоянии «запущен» и планировщик тут не при чём.

Да, ты прав. Оба метода Thread.start()/join() synchronized, так что да, join() всегда будет дожидаться start'а.
Тогда не знаю - либо JIT, либо что-нибудь еще.
Ах, да, дома в Макоси воспроизвести не удалось.

kovrik ★★★★★
()
Ответ на: комментарий от Deleted

Так себе затычка.

Aleksey: We can set the reserved space such that all direct uses of ReentrantLock in core library code will not encounter StackOverflow. So this does help to make the core libraries more robust - which was the motivation for this. It can't help general user code that uses ReentrantLock.

И судя по описанию, «пользователи» либо также будут ловить проблемы со своим кодом, либо будут обмазывать его jdk.internal.vm.annotation.ReservedStackAccess во всех хоть сколько-нибудь сомнительных местах, что приведёт к исчерпанию уже этого самого ReservedStack.

korvin_ ★★★★★
()
Ответ на: комментарий от Deleted

И?

Both privileged code and non-privileged code can be annotated with this annotation

-> получаем кучу пользовательского кода, тратящего ReservedStack.

but by default the JVM will ignore it for non-privileged code

-> получаем кучу пользовательского кода, фейлящего выполнить собственные методы в finally в описаной тобой ситуации.

korvin_ ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.