Современные приложения все чаще требуют выполнения нескольких задач одновременно, и для этого Java предоставляет мощный инструмент — Concurrency API (набор классов и интерфейсов). Этот API позволяет разработчикам легко реализовывать многопоточность, управлять потоками и синхронизировать действия между ними, что значительно увеличивает производительность и отзывчивость приложений.
Основные классы:
1. Thread
- Класс для создания и управления потоками. Вы можете создать новый поток, реализовав интерфейс
Runnable или расширив класс Thread.2. Executor
- Интерфейс для управления потоками и выполнения задач. Позволяет абстрагироваться от управления потоками, сосредоточившись на логике приложения.
- ExecutorService: расширение Executor, управляющее жизненным циклом потоков.
3. Future
- Позволяет получать результаты из асинхронных задач. Используется в связке с
ExecutorService для выполнения задач в фоновом режиме.4. CountDownLatch
- Синхронизирует потоки, позволяя одному или нескольким потокам ждать завершения других потоков перед продолжением работы.
5. CyclicBarrier
- Используется для синхронизации группы потоков. Позволяет потоку ждать, пока все другие не достигнут определенной точки.
6. Semaphore
- Контролирует доступ к ресурсу, предоставляя определенное количество разрешений для потоков.
7. BlockingQueue
- Интерфейс, предоставляющий безопасный способ обмена данными между потоками при помощи очередей. Реализации включают
ArrayBlockingQueue, LinkedBlockingQueue и другие.#java #ConcurrencyAPI #Thread
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5👍3🔥2
До Java 13 работа с многострочными строками была болью. JSON? HTML? SQL? Только через
"\n" + и кучу экранирования.Но с Text Blocks всё стало проще, понятнее и читаемо.
Сравни:
String html = "<html>\n" +
" <body>Hello</body>\n" +
"</html>";
String html = """
<html>
<body>Hello</body>
</html>
""";
String, просто оформленная красиво.Text Blocks:1.
Код больше не превращается в кашу. Особенно полезно для:
SQL-запросов:
String query = """
SELECT id, name
FROM users
WHERE active = true
ORDER BY created_at DESC
""";
HTML/JSON шаблонов:
String json = """
{
"name": "Alice",
"role": "admin"
}
""";
2.
Забудь про
\", \\n, \\t — теперь можно писать почти как в блокноте.3.
Java сама уберёт начальные отступы на основе самой "узкой" строки.
Пример:
String msg = """
Line 1
Line 2
Line 3
""";
Line 1
Line 2
Line 3
4.
.formatted() Нельзя вставить переменные прямо в Text Block?
Используем .formatted() — коротко и читабельно:
String user = "Bob";
String template = """
{
"user": "%s",
"access": "granted"
}
""".formatted(user);
````
```java
String s = """
Hello""";
System.out.println(s.length()); // 6, а не 5 (есть \n)
3.
Нельзя писать так:
String name = "Eve";
String wrong = """
Hello, $name!
"""; // ❌ не сработает
Вместо этого:
String right = """
Hello, %s!
""".formatted(name);
+ "\n".Please open Telegram to view this post
VIEW IN TELEGRAM
👍11❤1🔥1
Java-приложение в Docker
В современном мире разработки программного обеспечения Docker становится важным инструментом, помогающим разрабатывать, тестировать и развертывать приложения в изолированной среде. Использование Docker с Java-приложениями позволяет значительно упростить процесс развертывания, гарантируя, что ваше приложение будет работать одинаково на всех средах.
Docker — это платформа для создания, развертывания и управления контейнерами. Контейнеры изолируют приложения и все их зависимости, что делает возможным запуск кода в любом окружении без конфликта с другими приложениями.
Шаг 1: Установка Docker
Перед тем как начать, убедитесь, что Docker установлен на вашем компьютере. Вы можете следовать [официальной документации](https://docs.docker.com/get-docker/) для установки на вашу операционную систему.
Шаг 2: Создание Java-приложения
Для примера создадим простое приложение. Предположим, у вас есть простая программа, которая выводит "Hello, World!".
Создайте файл
Скомпилируйте её с помощью:
Шаг 3: Создание Dockerfile
Dockerfile — это текстовый файл, позволяющий автоматически собирать образ Docker. Создайте файл с именем
Шаг 4: Сборка Docker-образа
Теперь создайте Docker-образ, используя следующую команду в каталоге вашего проекта:
Эта команда соберет образ и назовет его
Шаг 5: Запуск контейнера
Чтобы запустить контейнер, выполните команду:
Вы должны увидеть вывод:
Флаг
#java #docker
В современном мире разработки программного обеспечения Docker становится важным инструментом, помогающим разрабатывать, тестировать и развертывать приложения в изолированной среде. Использование Docker с Java-приложениями позволяет значительно упростить процесс развертывания, гарантируя, что ваше приложение будет работать одинаково на всех средах.
Docker — это платформа для создания, развертывания и управления контейнерами. Контейнеры изолируют приложения и все их зависимости, что делает возможным запуск кода в любом окружении без конфликта с другими приложениями.
Шаг 1: Установка Docker
Перед тем как начать, убедитесь, что Docker установлен на вашем компьютере. Вы можете следовать [официальной документации](https://docs.docker.com/get-docker/) для установки на вашу операционную систему.
Шаг 2: Создание Java-приложения
Для примера создадим простое приложение. Предположим, у вас есть простая программа, которая выводит "Hello, World!".
Создайте файл
HelloWorld.java:
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
Скомпилируйте её с помощью:
javac HelloWorld.java
Шаг 3: Создание Dockerfile
Dockerfile — это текстовый файл, позволяющий автоматически собирать образ Docker. Создайте файл с именем
Dockerfile в каталоге вашего проекта:
# Используем официальный образ OpenJDK
FROM openjdk:17-jdk-slim
# Устанавливаем рабочий каталог
WORKDIR /app
# Копируем скомпилированный класс в контейнер
COPY HelloWorld.class ./
# Определяем команду запуска
CMD ["java", "HelloWorld"]
Шаг 4: Сборка Docker-образа
Теперь создайте Docker-образ, используя следующую команду в каталоге вашего проекта:
docker build -t helloworld .
Эта команда соберет образ и назовет его
helloworld.Шаг 5: Запуск контейнера
Чтобы запустить контейнер, выполните команду:
docker run --rm helloworld
Вы должны увидеть вывод:
Hello, World!
Флаг
--rm автоматически удаляет контейнер после его остановки.#java #docker
👍5👏1
Лямбда-выражения могут захватывать переменные из внешнего окружения, делая их доступными внутри лямбда-функции. При этом переменные могут быть:
1️⃣ Неизменяемыми (effectively final) – Переменная из внешнего контекста, используемая в лямбде, должна быть объявлена как
final или фактически быть неизменяемой (то есть не изменяться после первого присваивания). Например:
int x = 10;
Runnable r = () -> System.out.println(x); // x захвачен в лямбде
2️⃣ Свободными от изменения в лямбде – Лямбда не может изменять захваченные переменные. Это ограничение гарантирует, что нет неоднозначного состояния, когда переменная изменяется из нескольких мест (например, из основного потока и из лямбда-функции одновременно).
3️⃣ Статическими или полями класса – В отличие от локальных переменных, статические поля класса или поля экземпляра могут свободно изменяться внутри лямбда-выражений, поскольку их значения хранятся в куче (heap) и доступны по ссылке.
Пример:
public class Main {
private static int staticVar = 20;
public static void main(String[] args) {
int localVar = 10;
Runnable r = () -> System.out.println(localVar + staticVar);
r.run();
}
}
В этом примере
localVar захватывается, так как он effectively final, а staticVar доступен, так как это статическое поле.#java #lambda #capturing
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7❤2
public class CommandLine {
public static void main(String[] args) throws IOException {
Runtime rt = Runtime.getRuntime();
String[] commands = {"ping", "-c 5", "google.com"};
Process proc = rt.exec(commands);
BufferedReader stdInput = new BufferedReader(new
InputStreamReader(proc.getInputStream()));
BufferedReader stdError = new BufferedReader(new
InputStreamReader(proc.getErrorStream()));
// Read the output from the command
System.out.println("Here is the standard output of the command:\n");
String s = null;
while ((s = stdInput.readLine()) != null) {
System.out.println(s);
}
// Read any errors from the attempted command
System.out.println("Here is the standard error of the command (if any):\n");
while ((s = stdError.readLine()) != null) {
System.out.println(s);
}
}
}
Метод
Runtime.getRuntime() возвращает объект, представляющий текущую среду выполнения Java. Этот объект позволяет запускать команды системы.Метод
exec() запускает системную команду и возвращает объект Process, который представляет запущенный процесс. Команда, которую нужно выполнить, передается как массив строк.Далее получаем поток для чтения данных из стандартного вывода процесса, который был запущен и выводим на консоль. Так же поступаем и с потоком вывода ошибок.
Вывод:
Here is the standard output of the command:
PING google.com (173.194.222.138): 56 data bytes
64 bytes from 173.194.222.138: icmp_seq=0 ttl=60 time=39.479 ms
64 bytes from 173.194.222.138: icmp_seq=1 ttl=60 time=39.753 ms
64 bytes from 173.194.222.138: icmp_seq=2 ttl=60 time=47.982 ms
64 bytes from 173.194.222.138: icmp_seq=3 ttl=60 time=39.569 ms
64 bytes from 173.194.222.138: icmp_seq=4 ttl=60 time=39.850 ms
--- google.com ping statistics ---
5 packets transmitted, 5 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 39.479/41.327/47.982/3.330 ms
Here is the standard error of the command (if any):
#java #Runtime #exec
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7❤1
Есть несколько способов добавить элемент в середину массива. Однако, поскольку массивы имеют фиксированную длину, напрямую добавить элемент в существующий массив нельзя — нужно создавать новый массив с увеличенной длиной и копировать данные. Рассмотрим несколько способов:
1️⃣ Копирование вручную
Суть в том что бы создать новый массив на 1 элемент больше оригинального и перенести элементы из старого, не забыв при этом вставить новый элемент.
int[] array = {1, 2, 4, 5};
int index = 2; // индекс, куда вставить новый элемент
int newElement = 3;
// Создаем новый массив на 1 больше
int[] newArray = new int[array.length + 1];
// Копируем элементы до позиции вставки
for (int i = 0; i < index; i++) {
newArray[i] = array[i];
}
// Вставляем новый элемент
newArray[index] = newElement;
// Копируем оставшиеся элементы
for (int i = index + 1; i < newArray.length; i++) {
newArray[i] = array[i - 1];
}
// Печатаем новый массив
System.out.println(Arrays.toString(newArray)); // [1, 2, 3, 4, 5]
2️⃣ Использование коллекций (например, ArrayList)
ArrayList динамический по размеру, и можно легко вставить элемент в любое место с помощью метода add(index, element).
Integer[] array = {1, 2, 4, 5};
int index = 2; // индекс, куда вставить новый элемент
int newElement = 3;
List<Integer> list = new ArrayList<>(Arrays.asList(array));
// Вставляем элемент
list.add(index, newElement);
// Создаем новый массив на 1 больше
Integer[] newArray = list.toArray(array);
// Печатаем новый массив
System.out.println(Arrays.toString(newArray)); // [1, 2, 3, 4, 5]
3️⃣ Использование метода System.arraycopy
Метод
System.arraycopy позволяет эффективно копировать части массива.
int[] array = {1, 2, 4, 5};
int index = 2; // индекс, куда вставить новый элемент
int newElement = 3;
// Создаем новый массив на 1 больше
int[] newArray = new int[array.length + 1];
// Копируем элементы до позиции вставки
System.arraycopy(array, 0, newArray, 0, index);
// Вставляем новый элемент
newArray[index] = newElement;
// Копируем оставшиеся элементы
System.arraycopy(array, index, newArray, index + 1, array.length - index);
// Печатаем новый массив
System.out.println(Arrays.toString(newArray)); // [1, 2, 3, 4, 5]
#java #array
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
🔄 Бесконечные потоки
Интерфейс Stream имеет два статических метода для генерации бесконечных потоков:
iterate
generate
📌 При работе с бесконечными потоками, крайне важно вызвать метод limit() перед вызовом терминальной операции, иначе наша программа будет работать бесконечно.
Интерфейс Stream имеет два статических метода для генерации бесконечных потоков:
iterate() и generate().iterate
(final T seed, final UnaryOperator<T> f) возвращает бесконечный последовательный упорядоченный поток, созданный путем итеративного применения функции f к исходному элементу начального значения, создавая поток, состоящий из начального числа, f(начальное число), f(f(начальное число)) и т. д.generate
(Supplier<? extends T> s) возвращает бесконечный последовательный неупорядоченный поток, в котором каждый элемент создается предоставленным поставщиком (Supplier). Это подходит для генерации константных потоков, потоков случайных элементов и т. д.📌 При работе с бесконечными потоками, крайне важно вызвать метод limit() перед вызовом терминальной операции, иначе наша программа будет работать бесконечно.
👍2❤1
Пример POJO:
public class Person {
private String name;
private int age;
// Конструктор
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// Геттеры и сеттеры
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
POJO используется для создания простых объектов без привязки к какой-либо специфической архитектуре или фреймворку. Например, в JPA объекты-сущности часто являются POJO, что позволяет их использовать независимо от платформы.
#java #pojo
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9
Когда требуется конкатенировать строки с использованием
Stream, можно выбрать один из двух методов: Stream.reduce() или Stream.collect(Collectors.joining()).Пример с
Stream.reduce(): List<String> list = List.of("Str1", "Str2", "Str3");
String result = list.stream().reduce("", (a, b) -> a + b);
System.out.println(result); // Str1Str2Str3Пример с
Collectors.joining(): List<String> list = List.of("Str1", "Str2", "Str3");
String result = list.stream().collect(Collectors.joining());
System.out.println(result); // Str1Str2Str3Использование
reduce() для конкатенации строк не является оптимальным с точки зрения производительности. При каждом вызове операции +, создается новая строка, так как строки в Java неизменяемы. Это приводит к увеличению нагрузки на память из-за создания множества промежуточных объектов.В свою очередь, метод
Collectors.joining() использует StringBuilder для сборки строк, что значительно эффективнее. Он избегает создания лишних объектов и снижает потребление памяти.#java #Stream #reduce #joining
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8
java.util.concurrent.Использует подход "разделяй и властвуй", где задача разбивается на подзадачи до тех пор, пока они не станут достаточно маленькими для последовательного решения. Для этого используются классы
RecursiveTask<V> (возвращает результат) и RecursiveAction (без результата).ForkJoinPool динамически управляет количеством потоков, при необходимости создавая новые. Обычно это количество соответствует числу процессоров, доступных в системе.
Использует технику work-stealing, где потоки, завершившие свои задачи, могут "красть" задачи у других потоков, чтобы эффективно использовать ресурсы процессора.
ForkJoinPool обладает высокой производительностью для задач, которые можно разбить на независимые подзадачи.
Основные методы:
invoke(): синхронно запускает задачу и ждет её завершения.
submit(): запускает задачу асинхронно.
execute(): также запускает задачу асинхронно, но не возвращает результат.
Пример использования:
import java.util.concurrent.RecursiveTask;
import java.util.concurrent.ForkJoinPool;
class SumTask extends RecursiveTask<Integer> {
private final int[] array;
private final int start, end;
private final int threshold = 10;
public SumTask(int[] array, int start, int end) {
this.array = array;
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
if (end - start <= threshold) {
// Базовый случай: небольшая задача
int sum = 0;
for (int i = start; i < end; i++) {
sum += array[i];
}
return sum;
} else {
// Разделяем задачу
int mid = (start + end) / 2;
SumTask leftTask = new SumTask(array, start, mid);
SumTask rightTask = new SumTask(array, mid, end);
leftTask.fork(); // Асинхронно запускаем левую подзадачу
int rightResult = rightTask.compute(); // Синхронно вычисляем правую подзадачу
int leftResult = leftTask.join(); // Ждем завершения левой подзадачи
return leftResult + rightResult;
}
}
}
public class ForkJoinExample {
public static void main(String[] args) {
ForkJoinPool pool = new ForkJoinPool();
int[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
SumTask task = new SumTask(array, 0, array.length);
int result = pool.invoke(task);
System.out.println("Сумма: " + result);
}
}
Этот код создает задачу для суммирования массива, разбивая его на подзадачи.
#java #ForkJoinPool
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
Последовательные потоки (Sequential Streams)
✔️ Обрабатывают элементы последовательно, один за другим, на одном потоке, следовательно производительность ограничена возможностями одного ядра процессора.
✔️ Используют основной поток программы (main thread) для выполнения операций.
✔️ Метод stream() создаёт последовательный поток.
Пример последовательного потока:
List<String> list = List.of("Hello ", "w", "o", "r", "l", "d!");
list.stream().forEach(System.out::print);
// Hello world!
Параллельные потоки (Parallel Streams)
✔️ Обрабатывают элементы параллельно, распределяя их между несколькими потоками (threads), что позволяет использовать многопоточность.
✔️ Используют ForkJoinPool для распределения задач между потоками.
✔️ Эффективны для больших наборов данных, поскольку могут улучшить производительность на многоядерных процессорах.
✔️ Параллельный поток можно создать, вызвав метод parallelStream() или применив метод parallel() к уже существующему потоку.
Пример параллельного потока:
List<String> list = List.of("Hello ", "w", "o", "r", "l", "d!");
list.parallelStream().forEach(System.out::print);
// rlHello d!wo
#java #Stream #parallelStream
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3❤1
Привет, на связи Таня Коровкина из ШОРТКАТ. Ментор по алгоритмам и backend-разработчик
Каждый месяц тысячи разработчиков совершают одни и те же ошибки на алгоритмических интервью 🚩
И продолжают готовиться... не к тому.
6 июля(понедельник) в 19:00 (МСК) проведу вебинар и покажу, что на самом деле оценивает интервьюер и какие ошибки чаще всего приводят к отказу
• дам практические советы, которые можно использовать уже на следующем собеседовании
• расскажу про специфику российского BigTech
🤘 Это бесплатно. Эфир проходит в рамках менторской программы от ШОРТКАТ для разработчиков, которые хотят повысить свой грейд, ЗП и прокачать скиллы.
Переходи в нашего бота, чтобы получить ссылку на эфир → @shortcut_sh_bot
Реклама.
О рекламодателе.
Please open Telegram to view this post
VIEW IN TELEGRAM
Дан список людей с именем и городом проживания. Нужно сгруппировать их по городам.
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
record Person(String name, String city) {}
public class StreamExample {
public static void main(String[] args) {
List<Person> people = List.of(
new Person("Alice", "New York"),
new Person("Bob", "Los Angeles"),
new Person("Charlie", "New York"),
new Person("David", "Los Angeles"),
new Person("Edward", "San Francisco")
);
Map<String, List<Person>> peopleByCity = people.stream()
.collect(Collectors.groupingBy(Person::city));
peopleByCity.forEach((city, peopleInCity) -> {
System.out.println(city + ": " + peopleInCity.stream()
.map(Person::name)
.collect(Collectors.joining(", ")));
});
// Вывод:
// San Francisco: Edward
// New York: Alice, Charlie
// Los Angeles: Bob, David
}
}
#java #stream #grouping
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1