← Назад к вопросам
Можно ли создать многопоточную программу, работающую на одном процессе?
1.0 Junior🔥 221 комментариев
#Docker, Kubernetes и DevOps
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Да, можно и это стандартная практика. Многопоточность работает внутри одного процесса
Многопоточность в Java — это способность одного процесса запускать несколько потоков параллельно (логически) или конкурентно. Все потоки работают в едином адресном пространстве процесса.
Базовые концепции
Процесс: Независимая программа с собственным адресным пространством (выделяется ОС) Поток: Единица выполнения внутри процесса, разделяет память с другими потоками
public class ThreadBasics {
public static void main(String[] args) {
// В этом мэйне уже работает Main thread
System.out.println("Текущий поток: " + Thread.currentThread().getName());
// Вывод: Текущий поток: main
// Создаём второй поток
Thread thread1 = new Thread(() -> {
System.out.println("Поток 1: " + Thread.currentThread().getName());
});
// Создаём третий поток
Thread thread2 = new Thread(() -> {
System.out.println("Поток 2: " + Thread.currentThread().getName());
});
// Запускаем оба потока
thread1.start(); // Начинает работать параллельно
thread2.start(); // Начинает работать параллельно
System.out.println("Main завершился");
}
}
// Возможный вывод (порядок непредсказуем):
// Текущий поток: main
// Main завершился
// Поток 1: Thread-0
// Поток 2: Thread-1
Создание потоков: Два способа
Способ 1: Наследование Thread
public class MyThread extends Thread {
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println("Поток " + name + " начался");
for (int i = 0; i < 5; i++) {
System.out.println(name + ": " + i);
try {
Thread.sleep(1000); // Спим 1 секунду
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Поток " + name + " завершился");
}
}
public class Main {
public static void main(String[] args) {
MyThread t1 = new MyThread("Worker-1");
MyThread t2 = new MyThread("Worker-2");
t1.start(); // Запускаем в отдельном потоке
t2.start(); // Запускаем в отдельном потоке
System.out.println("Main закончился, потоки работают");
}
}
Способ 2: Реализация Runnable (лучше)
public class Task implements Runnable {
private String name;
public Task(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(name + " начался");
for (int i = 0; i < 5; i++) {
System.out.println(name + ": итерация " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
System.out.println(name + " завершился");
}
}
public class Main {
public static void main(String[] args) {
// Способ 1: Через лямбду
Thread t1 = new Thread(() -> {
System.out.println("Лямбда-поток");
});
// Способ 2: Через Runnable
Thread t2 = new Thread(new Task("Обработчик-1"));
Thread t3 = new Thread(new Task("Обработчик-2"));
t1.start();
t2.start();
t3.start();
}
}
Архитектура памяти в многопоточности
ОДИН ПРОЦЕСС (адресное пространство)
┌────────────────────────────────────┐
│ Heap (общая память) │
│ (все потоки видят одни объекты) │
├────────────────────────────────────┤
│ Thread 1 │ Thread 2 │
│ ┌─────────────┐ │ ┌──────────────┐ │
│ │ Stack │ │ │ Stack │ │
│ │ (локальные │ │ │ (локальные │ │
│ │ переменные) │ │ │ переменные) │ │
│ └─────────────┘ │ └──────────────┘ │
└────────────────────────────────────┘
Проблема: Race Condition
Потокам нужна синхронизация для безопасного доступа к общей памяти:
public class Counter {
private int count = 0; // Общая переменная
// ❌ Небезопасно: race condition
public void increment() {
count++; // Читай → Увеличь → Запиши (не атомарно!)
}
// ✅ Безопасно: synchronized
public synchronized void incrementSafe() {
count++;
}
public int getCount() {
return count;
}
}
public class RaceConditionDemo {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
// 10 потоков, каждый увеличивает 1000 раз
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
counter.increment();
}
});
threads[i].start();
}
// Ждём завершения всех потоков
for (Thread t : threads) {
t.join();
}
// Ожидаем 10 * 1000 = 10000
// Получаем случайное число < 10000 (race condition)
System.out.println("Результат: " + counter.getCount());
}
}
Синхронизация: Основные инструменты
1. Synchronized методы
public class SyncExample {
private int counter = 0;
// Синхронизируем весь метод
public synchronized void increment() {
counter++;
}
// Синхронизируем только часть метода
public void incrementPartial() {
System.out.println("До синхронизации");
synchronized (this) { // Блокируем текущий объект
counter++;
}
System.out.println("После синхронизации");
}
}
2. Lock (java.util.concurrent)
import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
private int counter = 0;
private final ReentrantLock lock = new ReentrantLock();
public void increment() {
lock.lock(); // Захватываем лок
try {
counter++;
} finally {
lock.unlock(); // Освобождаем лок (даже при исключении)
}
}
public void incrementTryLock() {
if (lock.tryLock()) { // Неблокирующая попытка
try {
counter++;
} finally {
lock.unlock();
}
} else {
System.out.println("Лок занят, пропускаем");
}
}
}
3. Atomic классы
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicExample {
private AtomicInteger counter = new AtomicInteger(0);
public void increment() {
counter.incrementAndGet(); // Атомарная операция
}
public int getCount() {
return counter.get();
}
}
ExecutorService: Управление потоками
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class ExecutorServiceExample {
public static void main(String[] args) throws InterruptedException {
// Создаём пул из 5 потоков
ExecutorService executor = Executors.newFixedThreadPool(5);
// Отправляем задачи
for (int i = 0; i < 10; i++) {
int taskNumber = i;
executor.submit(() -> {
System.out.println("Задача " + taskNumber + " выполняется в " +
Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// Стоп принимать новые задачи
executor.shutdown();
// Ждём завершения всех задач (макс 10 секунд)
if (executor.awaitTermination(10, TimeUnit.SECONDS)) {
System.out.println("Все задачи завершены");
} else {
System.out.println("Timeout! Некоторые задачи ещё работают");
executor.shutdownNow(); // Принудительное завершение
}
}
}
Взаимодействие между потоками: wait/notify
public class ProducerConsumer {
private int data;
private boolean available = false;
// Производитель
public synchronized void produce(int value) throws InterruptedException {
while (available) {
wait(); // Ждём, пока потребитель прочитает
}
data = value;
available = true;
System.out.println("Произведено: " + value);
notifyAll(); // Уведомляем потребителя
}
// Потребитель
public synchronized int consume() throws InterruptedException {
while (!available) {
wait(); // Ждём, пока производитель создаст данные
}
available = false;
System.out.println("Потреблено: " + data);
notifyAll(); // Уведомляем производителя
return data;
}
}
Практический пример: Многопоточный вебсервер
public class SimpleWebServer {
private static final int PORT = 8080;
private final ExecutorService executor = Executors.newFixedThreadPool(10);
public void start() throws IOException {
ServerSocket server = new ServerSocket(PORT);
System.out.println("Сервер запущен на порту " + PORT);
while (true) {
Socket client = server.accept(); // Ждём подключения
// Обрабатываем каждого клиента в отдельном потоке
executor.submit(() -> {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(client.getInputStream()))) {
String request = reader.readLine();
System.out.println("Запрос: " + request);
// Отправляем ответ
client.getOutputStream().write("HTTP/1.1 200 OK\n".getBytes());
} catch (IOException e) {
e.printStackTrace();
}
});
}
}
}
Вывод
Да, многопоточная программа работает в ОДНОМ процессе:
- Процесс создаётся ОС один раз
- Потоки создаются внутри этого процесса
- Память общая для всех потоков (Heap)
- Стеки отдельные для каждого потока (Stack)
- Синхронизация необходима для безопасного доступа к общей памяти
- ExecutorService — лучший способ управлять потоками
Это стандартная практика в Java (веб-серверы, приложения UI, обработка события).