← Назад к вопросам

Можно ли создать многопоточную программу, работающую на одном процессе?

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();
                }
            });
        }
    }
}

Вывод

Да, многопоточная программа работает в ОДНОМ процессе:

  1. Процесс создаётся ОС один раз
  2. Потоки создаются внутри этого процесса
  3. Память общая для всех потоков (Heap)
  4. Стеки отдельные для каждого потока (Stack)
  5. Синхронизация необходима для безопасного доступа к общей памяти
  6. ExecutorService — лучший способ управлять потоками

Это стандартная практика в Java (веб-серверы, приложения UI, обработка события).