JavaRush /Java блог /Random /Кофе-брейк #113. 5 вещей, которые вы, вероятно, не знали ...

Кофе-брейк #113. 5 вещей, которые вы, вероятно, не знали о многопоточности в Java. 10 расширений JetBrains для борьбы с техническим долгом

Статья из группы Random

5 вещей, которые вы, вероятно, не знали о многопоточности в Java

Источник: DZone Поток — это сердце языка программирования Java. Даже запуск программы Hello World нуждается в основном потоке. При необходимости мы можем добавлять в программу другие потоки, если хотим, чтобы код нашего приложения был более функциональным и производительным. Если же речь идет о веб-сервере, то он одновременно обрабатывает сотни запросов одновременно. Для этого используется несколько потоков. Кофе-брейк #113. 5 вещей, которые вы, вероятно, не знали о многопоточности в Java. 10 расширений JetBrains для борьбы с техническим долгом - 1Потоки, несомненно, полезны, но работа с ними может быть трудна для многих разработчиков. В этой статье я поделюсь пятью концепциями многопоточности, о которых начинающие и опытные разработчики могут не знать.

1. Порядок программы и порядок выполнения не совпадают

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

package ca.bazlur.playground;

import java.util.concurrent.Phaser;

public class ExecutionOrderDemo {
    private static class A {
        int x = 0;
    }

    private static final A sharedData1 = new A();
    private static final A sharedData2 = new A();

    public static void main(String[] args) {
        var phaser = new Phaser(3);
        var t1 = new Thread(() -> {
            phaser.arriveAndAwaitAdvance();
            var l1 = sharedData1;
            var l2 = l1.x;
            var l3 = sharedData2;
            var l4 = l3.x;
            var l5 = l1.x;
            System.out.println("Thread 1: " + l2 + "," + l4 + "," + l5);
        });
        var t2 = new Thread(() -> {
            phaser.arriveAndAwaitAdvance();
            var l6 = sharedData1;
            l6.x = 3;
            System.out.println("Thread 2: " + l6.x);
        });
        t1.start();
        t2.start();
        phaser.arriveAndDeregister();
    }
}
Этот код кажется простым. У нас есть два общих экземпляра данных (sharedData1 и sharedData2), которые используют два потока. Когда мы выполняем код, мы предполагаем, что вывод будет таким:
Thread 2: 3 Thread 1: 0,0,0
Но если вы запустите код несколько раз, то увидите другой результат:
Thread 2: 3 Thread 1: 3,0,3 Thread 2: 3 Thread 1: 0,0,3 Thread 2: 3 Thread 1: 3,3,3 Thread 2: 3 Thread 1: 0,3,0 Thread 2: 3 Thread 1: 0,3,3
Я не утверждаю, что все эти потоки будут воспроизводиться на вашей машине именно так, но это вполне возможно.

2. Количество Java-потоков ограничено

Создать поток в Java легко. Однако это не означает, что мы можем создавать их сколько угодно. Количество потоков ограничено. Мы можем легко узнать, сколько потоков мы можем создать на конкретной машине с помощью следующей программы:

package ca.bazlur.playground;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.LockSupport;

public class Playground {
    public static void main(String[] args) {
        var counter = new AtomicInteger();
        while (true) {
            new Thread(() -> {
                int count = counter.incrementAndGet();
                System.out.println("thread count = " + count);
                LockSupport.park();
            }).start();
        }
    }
}
Вышеупомянутая программа очень простая. Она создает поток в цикле, а затем паркует его, что означает, что поток отключается для дальнейшего использования, но выполняет системный вызов и выделяет память. Программа продолжает создавать потоки до тех пор, пока больше не сможет создавать, а затем выдает исключение. Нас интересует число, которое мы получим, пока программа не выкинет исключение. На своем компьютере я смог создать только 4065 потоков.

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

Наивно полагать, что если в Java легко создаются потоки, то это повышает производительность приложения. К сожалению, это предположение ошибочно в случае с нашей традиционной моделью многопоточности, которую сегодня предоставляет Java. На самом деле слишком большое количество потоков может снизить производительность приложения. Давайте сначала зададим этот вопрос: какое оптимальное максимальное количество потоков мы можем создать, чтобы максимизировать производительность приложения? Что ж, ответ не так прост. Он очень сильно зависит от типа работы, которую мы делаем. Если у нас есть несколько независимых задач, и все они вычислительные и не блокируют какие-либо внешние ресурсы, то наличие большого количества потоков не сильно улучшит производительность. С другой стороны, если у нас есть 8-ядерный процессор, оптимальное количество потоков может быть (8 + 1). В таком случае мы можем полагаться на параллельный поток, представленный в Java 8. По умолчанию параллельный поток использует общий пул Fork/Join. Он создает потоки, равные количеству доступных процессоров, что достаточно для их интенсивной работы. Добавление большего количества потоков к работе с интенсивным использованием процессоров, где ничего не блокируется, не приведет к повышению производительности. Скорее, мы просто будем тратить ресурсы. Примечание. Причина наличия дополнительного потока заключается в том, что даже поток с интенсивными вычислениями иногда вызывает ошибку страницы или приостанавливается по какой-либо другой причине. (См.: Параллелизм Java на практике, Брайан Гетц, стр. 170) Однако предположим, например, что задачи связаны с вводом-выводом. В этом случае они зависят от внешней связи (например, базы данных, остальных API), поэтому имеет смысл большее количество потоков. Причина в том, что когда поток ожидает в Rest API, другие потоки могут продолжить работу. Теперь мы можем снова спросить, сколько потоков слишком много для такого случая? Смотря как. Не существует идеальных чисел, подходящих для всех случаев. Поэтому мы должны провести адекватное тестирование, чтобы выяснить, что лучше всего подходит для нашей конкретной рабочей нагрузки и приложения. В наиболее типичном сценарии мы обычно имеем смешанный набор задач. И дела в таких случаях идут до конца. В своей книге “Java Concurrency in Practice” Брайан Гетц предложил формулу, которую мы можем использовать в большинстве случаев. Number of threads = Number of Available Cores * (1 + Wait time / Service time) Waiting time (Время ожидания) может быть IO, например, ожидание ответа HTTP, получение блокировки и так далее. Service Time (Время обслуживания) — это время вычислений, например, обработка HTTP-ответа, маршалинг/демаршалинг и так далее. Например, приложение вызывает API, а затем обрабатывает его. Если у нас есть 8 процессоров на сервере приложений, среднее время ответа API составляет 100 мс, а время обработки ответа — 20 мс, то идеальный размер потока будет следующим:
N = 8 * ( 1 + 100/20) = 48
Однако это чрезмерное упрощение; адекватное тестирование всегда имеет решающее значение для определения числа.

4. Многопоточность — это не параллелизм

Иногда мы используем многопоточность и параллелизм взаимозаменяемо, но это уже не совсем актуально. Хотя в Java мы достигаем того и другого с помощью потока, это две разные вещи. “В программировании многопоточность — это частный случай независимо от выполняемых процессов, а параллелизм — это одновременное выполнение (возможно, связанных) вычислений. Многопоточность — это взаимодействие с большим количеством вещей одновременно. Параллелизм — это выполнение множества вещей одновременно”. Приведенное выше определение, данное Робом Пайком, довольно точное. Допустим, у нас есть абсолютно независимые задачи, и их можно вычислить отдельно. В этом случае эти задачи называются параллельными и могут выполняться с пулом Fork/Join или параллельным потоком. С другой стороны, если у нас много задач, некоторые из них могут зависеть от других. То, как мы сочиняем и структурируем, называется многопоточностью. Она связана со структурой. Мы можем захотеть выполнять несколько задач одновременно для достижения определенного результата, не обязательно заканчивать одну быстрее.

5. Project Loom позволяет нам создавать миллионы потоков

В предыдущем пункте я утверждал, что наличие большого количества потоков не означает повышения производительности приложения. Однако в эпоху микросервисов мы взаимодействуем со слишком большим количеством сервисов, чтобы выполнять конкретную работу. В таком сценарии потоки большую часть времени остаются в заблокированном состоянии. В то время как современная ОС может обрабатывать миллионы открытых сокетов, мы не можем открыть много каналов связи, так как мы ограничены количеством потоков. Но что, если создать миллионы потоков, и каждый из них будет использовать открытый сокет для взаимодействия с внешним миром? Это, безусловно, улучшит нашу пропускную способность приложения. Чтобы поддержать эту идею, в Java существует инициатива под названием Project Loom. Используя ее, мы можем создать миллионы виртуальных потоков. Например, используя следующий фрагмент кода, я смог создать 4,5 миллиона потоков на своей машине.

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.LockSupport;

public class Main {
    public static void main(String[] args) {
        var counter = new AtomicInteger();       

        // 4_576_279 
        while (true) {
            Thread.startVirtualThread(() -> {
                int count = counter.incrementAndGet();
                System.out.println("thread count = " + count);
                LockSupport.park();
            });
        }
    }
}
Для запуска этой программы у вас должна быть установлена ​​Java 18, которую можно скачать здесь. Запустить код можно с помощью следующей команды: java --source 18 --enable-preview Main.java

10 расширений JetBrains для борьбы с техническим долгом

Источник: DZone Многие команды разработчиков испытывают огромное давление в вопросе соблюдения дедлайнов. Из-за этого им часто не хватает времени на исправление и очистку своей кодовой базы. Иногда в таких ситуациях быстро накапливается технический долг. Помочь в решении этой проблемы могут расширения редактора. Давайте взглянем на 10 лучших расширений JetBrains для борьбы с техническим долгом (с поддержкой Java). Кофе-брейк #113. 5 вещей, которые вы, вероятно, не знали о многопоточности в Java. 10 расширений JetBrains для борьбы с техническим долгом - 2

Инструменты рефакторинга и технического долга

1. RefactorInsight

RefactorInsight улучшает показ изменений кода в среде IDE, выводя информацию о рефакторинге.
  1. Расширение определяет рефакторинг в запросах на слияние.
  2. Помечает коммиты, содержащие рефакторинг.
  3. Помогает в просмотре рефакторинга любой конкретной фиксации, выбранной на вкладке Git Log.
  4. Показывает историю рефакторинга классов, методов и полей.

2. Stepsize Issue Tracker in IDE

Stepsize — отличное средство отслеживания проблем для разработчиков. Расширение помогает инженерам не только создавать более качественные TODO и комментарии к коду, но и расставлять приоритеты по техническому долгу, рефакторингу и тому подобному:
  1. Stepsize позволяет создавать и просматривать задачи в коде прямо в редакторе.
  2. Поиск проблем, влияющих на функции, над которыми вы работаете.
  3. Добавление задач в свои sprints с помощью интеграции Jira, Asana, Linear, Azure DevOps и GitHub.

3. New Relic CodeStream

New Relic CodeStream — это платформа для совместной работы разработчиков для обсуждения и проверки кода. Она поддерживает запросы на извлечение из GitHub, BitBucket и GitLab, управление проблемами из Jira, Trello, Asana и 9 других, а также обеспечивает обсуждение кода, связывая все это воедино.
  1. Создание, просмотр и объединение запросов на извлечение в GitHub.
  2. Получение отзывов о незавершенной работе с предварительными проверками кода.
  3. Обсуждение с коллегами по команде проблем в коде.

TODO и комментарии

4. Comments Highlighter

Этот плагин позволяет создавать пользовательское выделение строк комментариев и ключевых слов языка. Также плагин имеет возможность определять пользовательские токены для выделения строк комментариев.

5. Better Comments

Расширение Better Comments поможет вам создавать в коде более понятные комментарии. С помощью этого расширения вы сможете классифицировать свои аннотации на:
  1. Оповещения.
  2. Запросы.
  3. TODO.
  4. Основные моменты.

Ошибки и уязвимости безопасности

6. SonarLint

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

7. SpotBugs

Плагин SpotBugs обеспечивает статический анализ байт-кода для поиска ошибок в коде Java из IntelliJ IDEA. SpotBugs — это инструмент обнаружения дефектов для Java, который использует статический анализ для поиска более 400 шаблонов ошибок, таких как разыменование null pointer, бесконечные рекурсивные циклы, неправильное использование библиотек Java и взаимоблокировки. SpotBugs может выявлять сотни серьезных дефектов в крупных приложениях (обычно около 1 дефекта на 1000-2000 строк исходных утверждений без комментариев).

8. Snyk Vulnerability Scanner

Snyk's Vulnerability Scanner помогает находить и устранять уязвимости в системе безопасности и проблемы с качеством кода в ваших проектах.
  1. Поиск и устранение проблем с безопасностью.
  2. Просмотр списка различных типов проблем, разбитых на категории.
  3. Отображение советов по устранению неполадок.

9. Zero Width Characters Locator

Этот плагин усиливает проверку и поиск трудно обнаруживаемых ошибок, связанных с невидимыми символами нулевой ширины (invisible zero width characters) в исходном коде и ресурсах. При использовании убедитесь, что включена проверка “Zero width unicode character — Символ юникода нулевой ширины”.

10. CodeMR

CodeMR — это инструмент для анализа качества программного обеспечения и статического кода, который помогает компаниям-разработчикам программного обеспечения разрабатывать более качественный код и программы. CodeMR визуализирует метрики кода и высокоуровневые атрибуты качества (связь, сложность, связность и размер) в различных представлениях, таких как Package Structure, TreeMap, Sunburst, Dependency и Graph Views.
Комментарии (5)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
comrade_b Уровень 39
6 июля 2022
Вышеупомянутая программа очень простая. Она создает поток в цикле, а затем паркует его, что означает, что поток отключается для дальнейшего использования, но выполняет системный вызов и выделяет память. Программа продолжает создавать потоки до тех пор, пока больше не сможет создавать, а затем выдает исключение. Нас интересует число, которое мы получим, пока программа не выкинет исключение. На своем компьютере я смог создать только 4065 потоков. Что-то тут не так. Мне ришлось прервать выполнение программы на 70-ти тысячном потоке.

thread count = 69749
thread count = 69750
thread count = 69751
thread count = 69752

Process finished with exit code 130
При этом я не на серверной банке сижу.
Артем Уровень 26
1 апреля 2022
Почему нельзя оплатить подписку в России?
Павел Уровень 11
30 марта 2022
Да пока этот Project Loom в LTS версии Java дождешься, быстрее на Kotlin перейдешь. Уж который год этот лум мусолят