Ответ
Работа с потоками в Java осуществляется через класс Thread и интерфейс Runnable (или Callable). Современные подходы рекомендуют использование высокоуровневых API, таких как ExecutorService.
1. Создание и запуск потока
Способ 1: Наследование от Thread (не рекомендуется из-за ограничения наследования)
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread is running: " + Thread.currentThread().getName());
}
}
// Запуск
new MyThread().start();
Способ 2: Реализация Runnable (предпочтительнее)
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Runnable is running: " + Thread.currentThread().getName());
}
}
// Запуск
Thread thread = new Thread(new MyRunnable());
thread.start();
2. Использование Executor Framework (Java 5+)
Этот фреймворк управляет пулом потоков, избавляя от ручного создания Thread.
import java.util.concurrent.*;
// Создание пула с фиксированным количеством потоков
ExecutorService executor = Executors.newFixedThreadPool(4);
// Задача без возвращаемого значения (Runnable)
executor.execute(() -> {
System.out.println("Async task executed by: " + Thread.currentThread().getName());
});
// Задача с возвращаемым значением (Callable)
Future<Integer> future = executor.submit(() -> {
Thread.sleep(1000);
return 42;
});
// Получение результата (блокирующий вызов)
try {
Integer result = future.get(); // Ждёт завершения задачи
System.out.println("Result: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
// Корректное завершение пула
executor.shutdown();
try {
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
}
3. Критические секции и синхронизация
Для предотвращения состояния гонки (race condition) используется синхронизация.
Синхронизированный метод:
public class Counter {
private int count = 0;
public synchronized void increment() { count++; } // Монитор — this
public synchronized int getCount() { return count; }
}
Синхронизированный блок:
public void addItem(List<String> list, String item) {
synchronized(list) { // Монитор — объект list
list.add(item);
}
}
4. Современные подходы (Java 8+)
- CompletableFuture: Для асинхронного программирования и комбинирования задач.
CompletableFuture.supplyAsync(() -> fetchDataFromAPI()) .thenApply(data -> processData(data)) .thenAccept(result -> System.out.println(result)) .exceptionally(ex -> { ex.printStackTrace(); return null; }); - Параллельные стримы (Parallel Streams): Для параллельной обработки коллекций.
List<Integer> numbers = List.of(1, 2, 3, 4, 5); int sum = numbers.parallelStream() .mapToInt(Integer::intValue) .sum();
Ключевые принципы:
- Не создавайте потоки вручную (
new Thread()) для каждой задачи, используйтеExecutorService. - Всегда обрабатывайте
InterruptedExceptionкорректно. - Для совместного доступа к изменяемым данным используйте потокобезопасные коллекции из
java.util.concurrent(например,ConcurrentHashMap,CopyOnWriteArrayList).
Ответ 18+ 🔞
А, потоки в Java, говоришь? Ну, это как история про Герасима и Муму, только вместо немого мужика у тебя процессор, а вместо собачки — твоя программа, которая пытается не утонуть в конкурентном дерьме. Слушай сюда, я тебе сейчас разложу, как не наступить на эти грабли, блядь.
Вот смотри, раньше, до революции, в Java 1.0, все делали потоки по-простому, по-деревенски: наследуешься от Thread и поехали. Типа так:
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread is running: " + Thread.currentThread().getName());
}
}
// Запуск
new MyThread().start();
Выглядит вроде норм, да? А на деле это как прибивать картину кувалдой — вроде работает, но стена вся в пизду. Почему? Да потому что наследование — штука одна, а если твой класс уже от кого-то унаследован? Всё, пиши пропало, в рот меня чих-пых! Поэтому умные дядьки придумали интерфейс Runnable. Это уже лучше, гибче.
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Runnable is running: " + Thread.currentThread().getName());
}
}
// Запуск
Thread thread = new Thread(new MyRunnable());
thread.start();
Но и это, скажу я тебе, уже каменный век, ёпта. Потому что если ты начнёшь вручную плодить потоки на каждую чих-пых операцию, твоё приложение сожрёт всю память и процессор, как здоровый немой Герасим сожрал бы твой обед, не поперхнувшись. Поток — он тяжёлый, создавать его — дорого.
И тут, на тебе, в Java 5 подъезжает целый Executor Framework на белом коне! Это, блядь, как управляющий для твоих потоков-работяг. Ты ему задачи кидаешь, а он уже сам решает, кому и когда их выполнять, используя пул готовых работяг. Красота!
import java.util.concurrent.*;
// Создаём пул, скажем, из 4 потоков. Четыре Герасима, которые будут молча и эффективно работать.
ExecutorService executor = Executors.newFixedThreadPool(4);
// Кидаем задачу без ответа (Runnable). Просто сделай что-то, и всё.
executor.execute(() -> {
System.out.println("Async task executed by: " + Thread.currentThread().getName());
});
// А вот если нужен ответ, то это Callable. Он возвращает Future — это как расписка "должен 42".
Future<Integer> future = executor.submit(() -> {
Thread.sleep(1000); // Поток поспит, а пул-то не простаивает!
return 42;
});
// Чтобы получить эти 42, надо по расписке прийти. get() — это блокирующий вызов, он будет ждать, пока Герасим не принесёт ответ.
try {
Integer result = future.get();
System.out.println("Result: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace(); // А тут может быть всякое, не забывай!
}
// И главное — после работы пул надо грамотно закрыть, а не бросить как есть.
executor.shutdown();
try {
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow(); // Если за 60 секунд не закончили, посылаем всех нахуй.
}
} catch (InterruptedException e) {
executor.shutdownNow();
}
А теперь, внимание, самый сок, блядь! Синхронизация. Это когда твои потоки-Герасимы начинают драться за одну и ту же миску с едой (общие данные). Если не контролировать — будет race condition, состояние гонки, а по-простому — пиздец. Один считает, другой в это же время меняет, и в итоге у тебя счётчик показывает ебушки-воробушки.
Чтоб такого не было, есть критические секции. Можно вешать замок на весь метод:
public class Counter {
private int count = 0;
public synchronized void increment() { count++; } // Монитор — this. Вход только для одного.
public synchronized int getCount() { return count; }
}
А можно точечно, только вокруг опасного куска:
public void addItem(List<String> list, String item) {
synchronized(list) { // Монитор — объект list. Защищаем именно его.
list.add(item);
}
}
Ну и современная магия, Java 8+: CompletableFuture. Это уже не просто пул, это целый оркестр, где ты можешь строить цепочки: сделай это, потом обработай то, потом выведи результат, а если ошибка — обработай и её. Красота, ядрёна вошь!
CompletableFuture.supplyAsync(() -> fetchDataFromAPI())
.thenApply(data -> processData(data))
.thenAccept(result -> System.out.println(result))
.exceptionally(ex -> { ex.printStackTrace(); return null; });
Или параллельные стримы, если надо быстро прогнать кучу данных:
List<Integer> numbers = List.of(1, 2, 3, 4, 5);
int sum = numbers.parallelStream()
.mapToInt(Integer::intValue)
.sum();
Запомни, чувак, три заповеди:
- Не плоди потоки вручную. Используй
ExecutorService, не будь дикарём. InterruptedException— не игнорируй его. Это сигнал "эй, тебя хотят прервать, будь добр, отреагируй". Просто проглатывать его — моветон.- Для общих данных — потоко-безопасные инструменты. Бери
ConcurrentHashMap,CopyOnWriteArrayListиз пакетаjava.util.concurrent. Не изобретай велосипед с синхронизацией на коленке.
Вот так-то. А то начнёшь делать new Thread() на каждый чих, а потом удивляешься, почему всё виснет и падает. Думай, бошка!