Почему создание темы считается дорогой? - PullRequest
166 голосов
/ 30 марта 2011

В руководствах по Java написано, что создание потока стоит дорого. Но почему именно это дорого? Что именно происходит при создании потока Java, что делает его создание дорогим? Я принимаю это утверждение как правду, но мне просто интересна механика создания потоков в JVM.

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

С Java-параллелизм на практике
Брайан Гетц, Тим Пайерлс, Джошуа Блох, Джозеф Боубер, Дэвид Холмс, Даг Ли
Печать ISBN-10: 0-321-34960-1

Ответы [ 6 ]

142 голосов
/ 30 марта 2011

Создание Java-потока стоит дорого, потому что в нем задействовано немало работы:

  • Для стека потоков должен быть выделен и инициализирован большой блок памяти.
  • Для создания / регистрации собственного потока в хост-системе необходимо выполнить системные вызовы.
  • Дескрипторы должны быть созданы, инициализированы и добавлены во внутренние структуры данных JVM.

Это также дорого в том смысле, что поток связывает ресурсы, пока он жив; например стек потоков, любые объекты, доступные из стека, дескрипторы потоков JVM, дескрипторы собственных потоков ОС.

Стоимость всех этих вещей зависит от платформы, но они недешевы для любой платформы Java, с которой я когда-либо сталкивался.


Поиск в Google нашел мне старый эталонный тест , который сообщает о скорости создания потоков ~ 4000 в секунду на Sun Java 1.4.1 на двухпроцессорном Xeon 2002 года выпуска, работающем под управлением винтажного Linux 2002 года. Более современная платформа даст лучшие цифры ... и я не могу комментировать методологию ... но, по крайней мере, она дает приблизительный показатель для , насколько дорогой может быть создание потоков.

Сравнительный анализ Питера Лоури показывает, что создание потоков в наши дни в абсолютном выражении происходит значительно быстрее, но неясно, насколько это связано с улучшениями в Java и / или ОС ... или более высокими скоростями процессора. Но его числа все же указывают на 150-кратное улучшение, если вы используете пул потоков вместо создания / запуска нового потока каждый раз. (И он подчеркивает, что все это относительно ...)


(Выше предполагается, что "родные потоки", а не "зеленые потоки", но все современные JVM используют собственные потоки по соображениям производительности. Возможно, создание зеленых потоков дешевле, но вы платите за это в других областях.)


Я немного покопался, чтобы увидеть, как реально выделяется стек потока Java. В случае OpenJDK 6 в Linux стек потоков выделяется путем вызова pthread_create, который создает собственный поток. (JVM не передает pthread_create предварительно выделенный стек.)

Затем в пределах pthread_create стек выделяется посредством вызова mmap следующим образом:

mmap(0, attr.__stacksize, 
     PROT_READ|PROT_WRITE|PROT_EXEC, 
     MAP_PRIVATE|MAP_ANONYMOUS, -1, 0)

Согласно man mmap, флаг MAP_ANONYMOUS приводит к инициализации памяти в ноль.

Таким образом, даже если может не иметь значения, что новые стеки потоков Java обнуляются (в соответствии со спецификацией JVM), на практике (по крайней мере, в OpenJDK 6 в Linux) они обнуляются.

71 голосов
/ 30 марта 2011

Другие обсуждали, откуда берутся потоки. В этом ответе объясняется, почему создание потока не так дорого по сравнению со многими операциями, но относительно дороже по сравнению с альтернативами выполнения задач, которые относительно дешевле.

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

Другой альтернативой является использование пула потоков. Пул потоков может быть более эффективным по двум причинам. 1) повторно использует уже созданные темы. 2) вы можете настроить / контролировать количество потоков, чтобы обеспечить оптимальную производительность.

Следующая программа печатает ....

Time for a task to complete in a new Thread 71.3 us
Time for a task to complete in a thread pool 0.39 us
Time for a task to complete in the same thread 0.08 us
Time for a task to complete in a new Thread 65.4 us
Time for a task to complete in a thread pool 0.37 us
Time for a task to complete in the same thread 0.08 us
Time for a task to complete in a new Thread 61.4 us
Time for a task to complete in a thread pool 0.38 us
Time for a task to complete in the same thread 0.08 us

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

final BlockingQueue<Integer> queue = new LinkedBlockingQueue<Integer>();
Runnable task = new Runnable() {
    @Override
    public void run() {
        queue.add(1);
    }
};

for (int t = 0; t < 3; t++) {
    {
        long start = System.nanoTime();
        int runs = 20000;
        for (int i = 0; i < runs; i++)
            new Thread(task).start();
        for (int i = 0; i < runs; i++)
            queue.take();
        long time = System.nanoTime() - start;
        System.out.printf("Time for a task to complete in a new Thread %.1f us%n", time / runs / 1000.0);
    }
    {
        int threads = Runtime.getRuntime().availableProcessors();
        ExecutorService es = Executors.newFixedThreadPool(threads);
        long start = System.nanoTime();
        int runs = 200000;
        for (int i = 0; i < runs; i++)
            es.execute(task);
        for (int i = 0; i < runs; i++)
            queue.take();
        long time = System.nanoTime() - start;
        System.out.printf("Time for a task to complete in a thread pool %.2f us%n", time / runs / 1000.0);
        es.shutdown();
    }
    {
        long start = System.nanoTime();
        int runs = 200000;
        for (int i = 0; i < runs; i++)
            task.run();
        for (int i = 0; i < runs; i++)
            queue.take();
        long time = System.nanoTime() - start;
        System.out.printf("Time for a task to complete in the same thread %.2f us%n", time / runs / 1000.0);
    }
}
}

Как видите, создание нового потока стоит всего ~ 70 микросекунд. Это можно считать тривиальным во многих, если не в большинстве случаев использования. Условно говоря, это дороже, чем альтернативы, и для некоторых ситуаций лучшим решением является пул потоков или вообще не использующий потоки.

29 голосов
/ 30 марта 2011

Теоретически, это зависит от JVM.На практике каждый поток имеет относительно большой объем стековой памяти (я думаю, 256 КБ по умолчанию).Кроме того, потоки реализованы как потоки ОС, поэтому для их создания требуется вызов ОС, то есть переключение контекста.

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

8 голосов
/ 30 марта 2011

Существует два вида потоков:

  1. Правильные потоки : это абстракции, связанные с возможностями многопоточности операционной системы. Следовательно, создание потоков обходится так же дорого, как и создание системы - всегда есть издержки.

  2. «Зеленые» потоки : созданные и запланированные JVM, они дешевле, но никакого правильного паралеллизма не происходит. Они ведут себя как потоки, но выполняются в потоке JVM в ОС. Насколько мне известно, они не часто используются.

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

Кроме этого, создание потоков в основном зависит от ОС и даже от реализации виртуальной машины.

Теперь позвольте мне кое-что отметить: создание потоков стоит дорого, если вы планируете запускать 2000 потоков в секунду, каждую секунду вашего времени выполнения. JVM не предназначена для обработки . Если у вас будет пара стабильных рабочих, которых не будут увольнять и убивать снова и снова, расслабьтесь.

6 голосов
/ 30 марта 2011

Создание Threads требует выделения достаточного количества памяти, так как он должен создать не один, а два новых стека (один для кода Java, другой для собственного кода).Использование Executors / Пулы потоков позволяют избежать накладных расходов, повторно используя потоки для нескольких задач для Executor .

0 голосов
/ 30 марта 2011

Очевидно, суть вопроса в том, что означает «дорогой».

Потоку необходимо создать стек и инициализировать стек на основе метода run.

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

Вероятно, существует много возможностей для синхронизации этих настроек.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...