Типично ли для синхронизированных методов Java отдавать предпочтение самому последнему вызывающему потоку?

Из того, что я читал, порядок, в котором потоки получают доступ к синхронизированному методу, не указан и, следовательно, зависит от реализации, но похоже, что в Oracle Java 8 (1.8.0_101-b13, 64-разрядная версия на Ubuntu) наиболее обычно отдается предпочтение недавно запущенному потоку.

Например, когда я запускаю следующий класс:

class C
{

    synchronized void go()
    {
        try
        {
           Thread.sleep(100);
           System.out.println(Thread.currentThread().getName());
           System.out.flush();
        } 
        catch (Exception e)
        {
            System.out.println(e);
        }
    }

    public static void main(String[] args)
    {

        C c = new C();
        for (int i = 0; i < 100; i++)
        {
            Thread t = new Thread(((Runnable) c::go));
            t.setName(Integer.toString(i));
            t.start();
        }
    }
}

Обычно я получаю такие результаты:

0 98 99 97 96 95 94 91 93 ... 4 3 2 1

Может ли кто-нибудь подтвердить, что Oracle JVM имеет тенденцию делать это? А есть ли какая-то особая причина?

В большинстве систем потоки реализуются с помощью процессов. Так что это будет зависеть от базовой реализации вашей ОС. Возможно, вы захотите спросить разработчиков Ubuntu, показывают ли их семафоры / система блокировки какие-либо предпочтения в этом отношении. Может быть какой-то список / очередь для заблокированных процессов.

markspace 30.04.2018 17:44

Что вы имеете в виду под «последним запущенным потоком»? Каждый из потоков в вашем примере вызывает go() и блокирует мьютекс ровно один раз. Вы имеете в виду последнюю ветку начал?

Solomon Slow 30.04.2018 18:59

Ваш пример не показывает хорошее использование synchronized, потому что он выполняет ввод-вывод, удерживая блокировку. Вам следует избегать держать блокировку заблокированной дольше, чем требуется вашей программе для присвоения нескольких переменных. Если вы будете следовать этому правилу, то блокировка редко будет вызывать конкуренцию, и вам будет все равно, является она «справедливой» или нет.

Solomon Slow 30.04.2018 19:04

@markspace, что значит "используя процессы ...?" В нескольких известных мне операционных системах поток - это исполняемый контекст (поток - это то, что планирует планировщик), а процесс - это совокупность ресурсов (открытые файлы, виртуальное адресное пространство и т. д.), И каждый поток "принадлежит "один процесс. В операционной системе Linux, в которой с самого начала не было потоков, идея «потока» лишь постепенно отделялась от идеи «процесса»; но есть и другие операционные системы, в которых с самого начала потоки были отдельными объектами.

Solomon Slow 30.04.2018 19:16

@jameslarge Я никогда не слышал о процессе, называемом сбором ресурсов. Процесс - это исполняемый контекст, подобный потоку. То, что он содержит открытые файлы и т. д., Просто необходимая реализация обслуживания ОС. Разница между потоками и процессами заключается в том, что потоки совместно используют пространство кучи, а процессы - нет.

markspace 30.04.2018 19:21

@markspace, мы с тобой разговариваем на разных уровнях. Мне кажется, вы говорите о выборе, который сделает разработчик программного обеспечения: структурировать ли какое-либо приложение как совокупность взаимодействующих процессов или как совокупность взаимодействующих потоков в рамках одного процесса. Я говорю о структурах данных в ядре операционной системы. В операционных системах некоторый, которые я использовал (и в некоторых, над кодом которых я работал), объект, называемый «процесс», никоим образом не похож на объект, называемый «поток».

Solomon Slow 30.04.2018 19:50

@jameslarge Я говорю о дизайне ОС и ее ядра. Некоторые системы поддерживают виртуальную память, некоторые - нет. Некоторые поддерживают отслеживание файлов для каждого процесса, некоторые - нет. Дизайн потоков, побуждение к созданию чего-то другого, отличного от процесса, было наблюдением, что было бы преимуществом, если бы процессы могли совместно использовать динамическую память. Эти более легкие процессы были названы потоками. Прошло некоторое время с тех пор, как я копался во внутренностях Linux, но то, что блок управления процессом имеет дополнительные поля, не означает, что эти объекты являются неотъемлемой частью концепции процесса.

markspace 30.04.2018 20:00
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
7
70
3

Ответы 3

Нет, просто совпадение и зависит от ОС, увеличьте номер петли до 10000 или более высокого числа,

for (int i = 0; i < 1000; i++)

вы получите совершенно другой результат.

synchronized нечестен в этом смысле, что означает, что нет никаких гарантий, какой поток войдет в синхронизированный раздел следующим, согласно спецификации.

Копнув глубже, вы обнаружите, что это поведение зависит от ОС, однако в любом случае вы должны полагаться на него, это может быть оптимизация, которая будет изменена в будущих версиях Oracle VM без какого-либо уведомления, поскольку она не указана, или для Например, это поведение может измениться из-за некоторых оптимизаций, выполненных во время выполнения, таких как lock coarsening.

Если вам нужен строгий порядок для доступа к критическому разделу, вам, вероятно, следует взглянуть на пакет java.util.concurrent, который содержит некоторые примитивы с опцией fair, например ReentrantLock.

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

Переписывание теста с 10 потоками каждые 100 мс и ожиданием 2000 мс, чтобы убедиться, что они прибывают в порядке 2..10 на заблокированном мониторе, вероятно, покажет что-то более или менее стабильное на данной ОС, но, вероятно, не переносимое поведение через ОС.

Другие вопросы по теме