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

В чем разница между submit в Executor и start в Thread?

2.0 Middle🔥 181 комментариев
#Многопоточность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# В чем разница между submit в Executor и start в Thread?

Краткий ответ

  • thread.start() - прямой запуск одного потока (низкоуровневый)
  • executor.submit() - отправка задачи в пул потоков для выполнения (высокоуровневый)

Executor лучше: переиспользует потоки, управляет ресурсами, обеспечивает Future.

Низкоуровневый способ: Thread

start() - создание нового потока

public class LowLevelExample {
    public static void main(String[] args) {
        // Каждый раз создаётся НОВЫЙ поток
        Thread thread1 = new Thread(() -> {
            System.out.println("Task 1 in " + Thread.currentThread().getName());
        });
        
        Thread thread2 = new Thread(() -> {
            System.out.println("Task 2 in " + Thread.currentThread().getName());
        });
        
        thread1.start();  // Создание Thread 1
        thread2.start();  // Создание Thread 2
        
        // Результат:
        // Task 1 in Thread-0
        // Task 2 in Thread-1
        // Два разных потока, каждый занимает ресурсы
    }
}

Проблемы

  1. Неэффективно - создание потока дорого (~1 MB памяти)
  2. Нет контроля - потоки создаются бесконтрольно
  3. Нет результата - сложно получить результат выполнения
  4. Нет управления - нельзя остановить или переиспользовать
public class ThreadProblem {
    public static void main(String[] args) throws Exception {
        // Создание 1000 потоков - OutOfMemory!
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                try { Thread.sleep(1000); }
                catch (InterruptedException e) {}
            }).start();
        }
    }
}

Высокоуровневый способ: Executor

submit() - отправка в пул потоков

public class ExecutorExample {
    public static void main(String[] args) throws Exception {
        // Создаём пул с 2 потоками
        ExecutorService executor = Executors.newFixedThreadPool(2);
        
        // Отправляем 5 задач
        for (int i = 1; i <= 5; i++) {
            final int taskId = i;
            executor.submit(() -> {
                System.out.println("Task " + taskId + " in " + 
                    Thread.currentThread().getName());
            });
        }
        
        executor.shutdown();
        
        // Результат:
        // Task 1 in pool-1-thread-1
        // Task 2 in pool-1-thread-2
        // Task 3 in pool-1-thread-1  ← Переиспользует thread-1
        // Task 4 in pool-1-thread-2  ← Переиспользует thread-2
        // Task 5 in pool-1-thread-1  ← Снова thread-1
    }
}

Преимущества

  1. Эффективно - переиспользует существующие потоки
  2. Контроль - точное количество потоков
  3. Future - результат и исключения
  4. Управление - shutdown, awaitTermination

Сравнение: start() vs submit()

Аспектstart()submit()
УровеньНизкий (создание потока)Высокий (пул потоков)
ЭффективностьНизкая (новый поток)Высокая (переиспользование)
РезультатНетFuture<T>
КонтрольМинимумПолный
Ресурсы~1 MB на потокШаринг ресурсов
МасштабируемостьПлохаяОтличная

Пример: 1000 задач

С Thread.start() - ПЛОХО

public class BadApproach {
    public static void main(String[] args) throws Exception {
        long start = System.currentTimeMillis();
        
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                System.out.println("Task in " + 
                    Thread.currentThread().getName());
            }).start();
        }
        
        Thread.sleep(1000);
        long elapsed = System.currentTimeMillis() - start;
        System.out.println("Время: " + elapsed + " ms");
        // Результат: создано 1000 потоков, медленно, OutOfMemory риск
    }
}

С Executor.submit() - ХОРОШО

public class GoodApproach {
    public static void main(String[] args) throws Exception {
        long start = System.currentTimeMillis();
        
        // 10 потоков на 1000 задач
        ExecutorService executor = Executors.newFixedThreadPool(10);
        
        for (int i = 0; i < 1000; i++) {
            executor.submit(() -> {
                System.out.println("Task in " + 
                    Thread.currentThread().getName());
            });
        }
        
        executor.shutdown();
        executor.awaitTermination(10, TimeUnit.SECONDS);
        long elapsed = System.currentTimeMillis() - start;
        System.out.println("Время: " + elapsed + " ms");
        // Результат: только 10 потоков, быстро, контролируемо
    }
}

Future - главное преимущество submit()

С Thread.start() - нет результата

Thread thread = new Thread(() -> {
    int result = 2 + 2;  // Вычислили
    // Как получить результат из main потока?
});
thread.start();
// Нет способа получить result!

С Executor.submit() - есть Future

ExecutorService executor = Executors.newSingleThreadExecutor();

Future<Integer> future = executor.submit(() -> {
    return 2 + 2;  // Вычислили
});

// Получаем результат
try {
    Integer result = future.get();  // 4
    System.out.println("Результат: " + result);
} catch (ExecutionException e) {
    System.out.println("Ошибка: " + e.getCause());
}

executor.shutdown();

Типы ExecutorService

newFixedThreadPool(n)

ExecutorService executor = Executors.newFixedThreadPool(5);
// 5 потоков всегда, очередь неограниченная

newSingleThreadExecutor()

ExecutorService executor = Executors.newSingleThreadExecutor();
// 1 поток, задачи выполняются последовательно

newCachedThreadPool()

ExecutorService executor = Executors.newCachedThreadPool();
// Динамическое количество потоков (0 до max)
// Хорошо для короткие задачи

newScheduledThreadPool(n)

ScheduledExecutorService executor = 
    Executors.newScheduledThreadPool(5);
// Для периодического выполнения
executor.scheduleAtFixedRate(() -> {
    System.out.println("Каждые 5 секунд");
}, 0, 5, TimeUnit.SECONDS);

Обработка результатов

Future.get() - блокирует

Future<String> future = executor.submit(() -> {
    Thread.sleep(1000);
    return "Result";
});

// get() ЖДЁТ завершения
String result = future.get();  // Блокирует до 1000 ms

Проверка isDone()

Future<String> future = executor.submit(() -> {
    return "Result";
});

while (!future.isDone()) {
    System.out.println("Ещё выполняется...");
    Thread.sleep(100);
}

String result = future.get();  // Уже готово

Обработка исключений

Future<Integer> future = executor.submit(() -> {
    throw new RuntimeException("Ошибка!");
});

try {
    Integer result = future.get();
} catch (ExecutionException e) {
    // e.getCause() - оригинальное исключение
    System.out.println("Ошибка: " + e.getCause());
}

Best Practices

❌ Плохо

// Создаём потоки каждый раз
for (int i = 0; i < tasks.size(); i++) {
    new Thread(tasks.get(i)).start();
}

✅ Хорошо

// Используем пул потоков
ExecutorService executor = 
    Executors.newFixedThreadPool(10);

for (Runnable task : tasks) {
    executor.submit(task);
}

executor.shutdown();

Выводы

  1. Thread.start() - для единичных потоков (редко)
  2. Executor.submit() - для множественных задач (всегда)
  3. Executor управляет пулом потоков эффективно
  4. Future позволяет получить результат
  5. ExecutorService лучше масштабируется
  6. Правило: Используй Executor для всего, кроме исключительных случаев
  7. В production почти всегда Executor, а не Thread
В чем разница между submit в Executor и start в Thread? | PrepBro