Существует много разных ответов на этот вопрос, но лучшее правило, которое я могу придумать, это то, что вам нужен один поток пользовательского интерфейса (вы не сказали, что вы используете для графического интерфейса, но вы упомянули invokeLater
, поэтому Я думаю, Swing), а затем один или несколько потоков для обработки клиентов. Поток для каждого клиента не нужен; вместо этого используйте java.nio
классы для асинхронного ввода-вывода. Возможно, вы захотите сделать общее количество потоков обработки клиента тем, что вы можете настроить во время выполнения; диапазон будет довольно мал, например, от одного до четырех.
Машина, на которой вы запускаете приложение, если это действительно сервер, вероятно, сможет обрабатывать от четырех (например, двухъядерный компьютер) до шестнадцати (четырехъядерных) реальных одновременных потоков выполнения. (очевидно, есть машины серверного класса, у которых даже больше ядер, но вы понимаете), и, конечно, вы делитесь ими со всеми другими службами, работающими в архитектуре. Таким образом, наличие множества потоков приводит к переключению контекста. Переключение контекста дешево, но далеко не бесплатно, и если этого можно избежать, то ЦП может с большей пользой заняться чем-то другим.
В качестве примера серверного приложения, написанного для обработки большого количества клиентов с минимумом потоков, используя NIO, вы можете посмотреть исходный код для Netty . Фактически, вы можете даже взглянуть на простое использование Netty и построение логики приложения на основе обработки ввода-вывода.
Примечание:
Приложение может продолжаться или не запускаться, в зависимости от того, есть ли видимый графический интерфейс или нет, но я, хотя основной метод не должен был возвращаться до тех пор, пока программа не завершится ...
main
закончится, как только вы дадите ему закончиться. JVM будет продолжать работать до тех пор, пока не будет запущенных потоков. Если вы хотите, чтобы main
ожидал других потоков перед выходом, используйте Thread#join
, чтобы присоединиться к ним. join
заставляет текущий поток ждать, пока не завершится поток, который вы вызываете join
on (некоторые перегрузки join
предлагают тайм-аут, поэтому вызывающий поток может возобновить работу, если вызываемый поток не завершается в течение заданного периода времени) , Сравните вывод следующего, когда вы запускаете его без аргументов и запускаете его с аргументом (любой аргумент, содержание аргумента не имеет значения):
public class JoinExample implements Runnable {
public static final void main(String[] args) {
Thread t = new Thread(new JoinExample());
System.out.println("Starting thread");
t.start();
if (args.length > 0) {
System.out.println("Joining thread");
while (t.isAlive()) {
try {
t.join();
}
catch (InterruptedException ie) {
}
}
}
System.out.println("main exiting");
}
public void run() {
long stop = System.currentTimeMillis() + 2000;
System.out.println("Thread starting");
while (System.currentTimeMillis() < stop) {
// Sleep a mo
try {
Thread.currentThread().sleep(250);
}
catch (InterruptedException ie) {
}
System.out.println("Thread still running");
}
System.out.println("Thread stopping");
}
}
Все это говорит о том, что вы вполне можете позволить потоку main
умереть, поскольку поток пользовательского интерфейса будет потоком диспетчера событий, созданным Swing. Больше информации о темах и качелях здесь и здесь .