简体   繁体   English

即使主线程已完成执行,Java 应用程序如何继续运行?

[英]How does a Java application keeps on running even when main thread has finished execution?

When an application is started via main method in Java and it spins other user threads(not daemon) for tasks such as:当应用程序通过 Java 中的 main 方法启动并为以下任务旋转其他用户线程(不是守护程序)时:

  • User Thread-01: Loading Cache From Database用户线程 01:从数据库加载缓存
  • User Thread-02: Perform application startup tasks like running diagnostics用户线程 02:执行应用程序启动任务,例如运行诊断
  • User Thread-03: Miscellaneous tasks用户线程 03:杂项任务

The user threads started by main thread will complete execution at some point in time and terminate.主线程启动的用户线程会在某个时间点完成执行并终止。 This will cause the main thread to terminate eventually and the application will stop running.这将导致主线程最终终止,应用程序将停止运行。 But, as we see so many applications which once started keep on running and if we take a thread dump, we can see the main thread at the very bottom.但是,当我们看到如此多的应用程序一旦开始继续运行,如果我们进行线程转储,我们可以在最底部看到主线程。 This means the main thread is not terminated which means the user threads started are not terminated.这意味着主线程没有终止,这意味着启动的用户线程没有终止。

How is this achieved?这是如何实现的? I mean what are standard programming constructs and logics to keep threads alive without running them through an infinite for or while loop?我的意思是什么是标准的编程结构和逻辑来保持线程活着而不通过无限的 for 或 while 循环运行它们?

Thanks for going through the question.感谢您回答这个问题。 Every helpful reply will add to our knowledge.每一个有用的回复都会增加我们的知识。

Executors framework执行者框架

You said:你说:

spins other user threads旋转其他用户线程

Hopefully you are not addressing Thread objects directly.希望您不是直接寻址Thread对象。 Since Java 5, for most purposes we can be using the Executors framework to manage work on background threads.从 Java 5 开始,对于大多数目的,我们可以使用 Executors 框架来管理后台线程上的工作。 See tutorial by Oracle.请参阅 Oracle 的教程

ExecutorService es = Executors. … ;
es.submit( yourRunnableOrCallableHere ) ;  // Optional: Capture the returned `Future` object to track success/failure of your task.
…
es.shutdown() ;

Background threads ending has no effect on main thread后台线程结束对主线程没有影响

You said:你说:

The user threads started by main thread will complete execution at some point in time and terminate.主线程启动的用户线程会在某个时间点完成执行并终止。 This will cause the main thread to terminate eventually and the application will stop running.这将导致主线程最终终止,应用程序将停止运行。

Not correct.不正确。

The main thread ends when it has no more work to do.主线程在没有更多工作要做时结束。 Background threads may end before the main thread, or after the main thread.后台线程可以在主线程之前结束,也可以在主线程之后结束。 The background threads termination does not cause the main thread to end.后台线程终止不会导致主线程结束。

Here is some example code to demonstrate this behavior.下面是一些示例代码来演示此行为。 This app performs a thread-dump and then runs a task in the background which also performs a thread dump.这个应用程序执行一个线程转储,然后在后台运行一个任务,该任务也执行一个线程转储。 Both main thread and background thread sleep a few seconds.主线程和后台线程都会休眠几秒钟。

package work.basil.example;

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class Threading
{
    public static void main ( String[] args )
    {
        Threading app = new Threading();
        app.demo();
    }

    private void demo ( )
    {
        System.out.println( "---------------|  main thread  |------------------------------------" );
        System.out.println( "Bonjour. " + Instant.now() );
        System.out.println( Threading.threadDump( true , true ) );

        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.submit(
                ( ) -> {
                    System.out.println( "---------------|  background thread  |------------------------------------" );
                    System.out.println( "DEBUG - Starting background thread. " + Instant.now() );
                    System.out.println( "DEBUG - Sleeping background thread. " + Instant.now() );
                    try {Thread.sleep( Duration.ofSeconds( 2 ).toMillis() );} catch ( InterruptedException e ) {e.printStackTrace();}
                    System.out.println( Threading.threadDump( true , true ) );
                    System.out.println( "DEBUG - Ending background thread. " + Instant.now() );
                }
        );

        executorService.shutdown();  // Always be sure to shutdown  your executor services.
        try {Thread.sleep( Duration.ofSeconds( 5 ).toMillis() );} catch ( InterruptedException e ) {e.printStackTrace();}
        System.out.println( "---------------|  main thread  |------------------------------------" );
        System.out.println( Threading.threadDump( true , true ) );
        System.out.println( "DEBUG - Main thread ending. " + Instant.now() );
    }

    // `threadDump` method taken from: https://www.baeldung.com/java-thread-dump
    private static String threadDump ( boolean lockedMonitors , boolean lockedSynchronizers )
    {
        StringBuffer threadDump = new StringBuffer( System.lineSeparator() );
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        for ( ThreadInfo threadInfo : threadMXBean.dumpAllThreads( lockedMonitors , lockedSynchronizers ) )
        {
            String message = "Thread: " + threadInfo.getThreadId() + " | " + threadInfo.getThreadName();
            threadDump.append( message ).append( System.lineSeparator() );
//            threadDump.append( threadInfo.toString() );
        }
        return threadDump.toString();
    }
}

When we sleep the background thread for less time than the main thread (2 seconds versus 5), notice that the main thread continues.当我们让后台线程的睡眠时间少于主线程时(2 秒对 5 秒),请注意主线程仍在继续。 The background thread ending has no effect on the main thread continuing or ending.后台线程结束对主线程继续或结束没有影响。

When run, notice how launching the executor service with a submitted task results in two more threads with IDs 14 & 15 here.运行时,请注意使用提交的任务启动执行器服务如何在此处产生两个 ID 为 14 和 15 的线程。 Then after the background task ends and the executor service is closed, the thread with ID 14 disappears.然后后台任务结束并关闭executor服务后,ID为14的线程就消失了。 Notice how the main thread does not end , continuing to do work — contradicting your statement in the Question.请注意主线程如何没有结束,继续工作 - 与您在问题中的陈述相矛盾。

/Library/Java/JavaVirtualMachines/jdk-16.jdk/Contents/Home/bin/java -javaagent:/Users/basilbourque/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/203.5981.155/IntelliJ IDEA 2020.3 EAP.app/Contents/lib/idea_rt.jar=49389:/Users/basilbourque/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/203.5981.155/IntelliJ IDEA 2020.3 EAP.app/Contents/bin -Dfile.encoding=UTF-8 -classpath /Users/basilbourque/IdeaProjects/Loom/target/classes work.basil.example.Threading
---------------|  main thread  |------------------------------------
Bonjour. 2020-12-18T07:30:21.107455Z

Thread: 1 | main
Thread: 2 | Reference Handler
Thread: 3 | Finalizer
Thread: 4 | Signal Dispatcher
Thread: 11 | Common-Cleaner
Thread: 12 | Monitor Ctrl-Break
Thread: 13 | Notification Thread

---------------|  background thread  |------------------------------------
DEBUG - Starting background thread. 2020-12-18T07:30:21.268025Z
DEBUG - Sleeping background thread. 2020-12-18T07:30:21.268225Z

Thread: 1 | main
Thread: 2 | Reference Handler
Thread: 3 | Finalizer
Thread: 4 | Signal Dispatcher
Thread: 11 | Common-Cleaner
Thread: 12 | Monitor Ctrl-Break
Thread: 13 | Notification Thread
Thread: 14 | pool-1-thread-1
Thread: 15 | Attach Listener

DEBUG - Ending background thread. 2020-12-18T07:30:23.275729Z
---------------|  main thread  |------------------------------------

Thread: 1 | main
Thread: 2 | Reference Handler
Thread: 3 | Finalizer
Thread: 4 | Signal Dispatcher
Thread: 11 | Common-Cleaner
Thread: 12 | Monitor Ctrl-Break
Thread: 13 | Notification Thread
Thread: 15 | Attach Listener

DEBUG - Main thread ending. 2020-12-18T07:30:26.271499Z

Process finished with exit code 0

For fun, try that code but reverse the durations.为了好玩,请尝试该代码,但颠倒持续时间。 Use 5 seconds versus 2 seconds to see the background threads outlive the main thread.使用 5 秒与 2 秒来查看后台线程的寿命比主线程长。

A web server is busy listening, all the time A web 服务器一直忙于监听

You said in a comment :你在评论中说:

visualize it like this... we have a website which is alive even when no one is opening the web page in the browser... which means the application is running even when no one is interacting with it... if we say that it is the web server which is running in background and not the actual web application.... but, then how is the web application is running indefinitely even when no one is interacting with it.像这样想象它......我们有一个网站,即使没有人在浏览器中打开 web 页面......这意味着即使没有人与之交互,应用程序也在运行......如果我们说它是在后台运行的 web 服务器,而不是实际的 web 应用程序....但是,即使没有人与之交互,web 应用程序如何无限期地运行。

Regarding "we have a website which is alive even when no one is opening the web page in the browser", no your web site is not "alive".关于“我们有一个网站,即使没有人在浏览器中打开 web 页面”,您的 web 网站也不“活着”。 Without any pending HTTP request to handle, your Java Servlet is not executing.如果没有任何待处理的 HTTP 请求来处理,您的 Java Servlet不会执行。 Your servlet is loaded and initialized, but is not executing until a request arrives.您的 servlet 已加载并初始化,但在请求到达之前不会执行。

Regarding "which means the application is running even when no one is interacting with it", no your web app is not running, as I said above.关于“这意味着应用程序正在运行,即使没有人与之交互”,正如我上面所说,您的 web 应用程序没有运行。 Your Java servlet code is not executing.您的 Java servlet 代码未执行。 When a request arrives, the web container automatically invokes your servlet's code in a thread.当请求到达时,web 容器会自动在线程中调用您的 servlet 代码。 Ultimately that servlet code results in the generation of content sent back to the web browser in a response.最终,该 servlet 代码将生成内容作为响应发送回 web 浏览器。 Your servlet code's execution ends.您的 servlet 代码的执行结束。 The thread used for that execution either ends or is returned to a thread pool (an internal decision for your web container to make).用于该执行的线程要么结束,要么返回到线程池(web 容器的内部决定)。 I am ignoring Push technology and WebSockets for the sake of simplicity.为了简单起见,我忽略了Push 技术WebSockets

Regarding: "if we say that it is the web server which is running in background and not the actual web application", the web server is always running an extra thread to listen for incoming requests.关于:“如果我们说它是 web 服务器在后台运行,而不是实际的 web 应用程序”,那么 web 服务器总是运行一个额外的线程来监听传入的请求。

➥ This may be the root of your confusion: A web server is always busy , busy listening for incoming connections, whether or not executing your Java servlet code. ➥ 这可能是您困惑的根源: web 服务器总是忙,忙于侦听传入连接,无论是否执行您的 Java servlet 代码。

  • A web server has one thread dedicated to working with the host OS to listen on the network for incoming connections. web 服务器有一个线程专门用于与主机操作系统一起在网络上侦听传入连接。
  • A web server launches other threads as needed to respond to a request by formulating and sending a response. web 服务器根据需要启动其他线程,以通过制定和发送响应来响应请求。

Regarding: "how is the web application is running indefinitely even when no one is interacting with it", you are forgetting that the host OS is interacting with the web container, whether or not users are interacting with your web app.关于:“web 应用程序如何在没有人与之交互的情况下无限期运行”,您忘记了主机操作系统正在与 web 容器交互,无论用户是否与您的 Z2567A5EC9705EB189DZ 容器交互。 The web container maintains a thread listening for incoming connections. web 容器维护一个线程监听传入连接。 That thread sits blocked, waiting on a notification by the host operating system's networking stack of an incoming request.该线程处于阻塞状态,等待主机操作系统的网络堆栈通知传入请求。 (My description here is generalized and simplified — I'm no networking expert — but suffices for the point being made here.) (我在这里的描述是概括和简化的——我不是网络专家——但足以说明这里的观点。)

When a request comes in over the network, the host OS informs the web container.当请求通过网络进入时,主机操作系统会通知 web 容器。 This notification unblocks the listening thread.此通知解除对侦听线程的阻塞。 The listening thread dispatches the request to a new thread, resulting in your Java servlet's code executing.侦听线程将请求分派到新线程,从而执行您的 Java servlet 的代码。 Meanwhile the request-listening thread of the web container goes back to being blocked to await another notification from the host OS' network stack about yet another incoming request.同时,web 容器的请求监听线程返回被阻塞状态,等待来自主机操作系统的网络堆栈的另一个关于另一个传入请求的通知。

That blocked listening thread is what explains/enables the web server running continuously and indefinitely.该阻塞的侦听线程是解释/启用 web 服务器连续无限期运行的原因。 Your web app, in contrast, runs in spurts, only in response to a request.相比之下,您的 web 应用程序会突然运行,仅响应请求。

Your question is a credit to the genius and success of Java Servlet technology.您的问题归功于 Java Servlet 技术的天才和成功。 The very purpose of Java Servlet is to abstract away all these details about listening for network activity, translating packets to text to decipher a request, parsing that request, mapping the request's contents to being the responsibility of a particular servlet, ensuring that particular servlet is loaded and initialized, and eventually calling a specific method in your servlet code defined by the Servlet spec. Java Servlet 的真正目的是抽象出所有这些关于监听网络活动、将数据包转换为文本以解密请求、解析请求、将请求的内容映射到特定 servlet 的责任的细节,确保特定 servlet 是加载和初始化,并最终调用 Servlet 规范定义的 servlet 代码中的特定方法。

A user app is busy waiting for input, all the time用户应用程序一直忙于等待输入

Similar to a web server being always busy listening for incoming requests, console apps and gui apps are both always busy listening for user input.类似于 web 服务器总是忙于监听传入的请求,控制台应用程序gui 应用程序都总是忙于监听用户输入。 They may seem idle at times, but are not.他们有时可能看起来很闲,但实际上并非如此。

While user apps do not spin continuously on the CPU, they do maintain a thread that works with the host OS to be informed of user events such as keyboard typing and mouse motion/clicks.虽然用户应用程序不会在 CPU 上连续旋转,但它们确实维护了一个与主机操作系统一起工作的线程,以通知用户事件,例如键盘输入和鼠标移动/点击。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM