多线程—即使主线程已经执行完毕,java应用程序如何继续运行?

iyzzxitl  于 2021-06-29  发布在  Java
关注(0)|答案(1)|浏览(350)

当应用程序通过java中的main方法启动时,它会旋转其他用户线程(不是守护进程)执行以下任务:
用户thread-01:从数据库加载缓存
用户thread-02:执行应用程序启动任务,比如运行诊断
用户线程03:杂项任务
由主线程启动的用户线程将在某个时间点完成执行并终止。这将导致主线程最终终止,应用程序将停止运行。但是,由于我们看到很多应用程序一旦启动就会继续运行,如果我们进行线程转储,我们可以看到最底层的主线程。这意味着主线程没有终止,也就是说启动的用户线程没有终止。
这是如何实现的?我的意思是,什么是标准的编程结构和逻辑来保持线程的活动性,而不必在无限for或while循环中运行它们?
谢谢你回答这个问题。每一个有用的答复都会增加我们的知识。

tez616oj

tez616oj1#

执行者框架

你说:
旋转其他用户线程
希望你不是在说 Thread 对象。从Java5开始,在大多数情况下,我们可以使用executors框架来管理后台线程上的工作。请参见oracle的教程。

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

后台线程结束对主线程没有影响

你说:
由主线程启动的用户线程将在某个时间点完成执行并终止。这将导致主线程最终终止,应用程序将停止运行。
不正确。
主线程在没有更多工作要做时结束。后台线程可以在主线程之前结束,也可以在主线程之后结束。后台线程终止不会导致主线程终止。
下面是一些示例代码来演示这种行为。此应用程序执行线程转储,然后在后台运行任务,该任务也执行线程转储。主线程和后台线程都会休眠几秒钟。

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

当我们休眠后台线程的时间比主线程少(2秒对5秒)时,请注意主线程将继续。后台线程结束对主线程继续或结束没有影响。
在运行时,请注意如何使用提交的任务启动executor服务,从而在这里产生另外两个ID为14和15的线程。然后在后台任务结束并且executor服务关闭之后,id为14的线程消失。注意主线程如何不结束,继续工作- 反驳你在这个问题上的陈述。

/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

为了好玩,试一下那个代码,但是把持续时间倒过来。使用5秒和2秒来查看背景线程比主线程长。

web服务器一直忙于侦听

你在评论中说:
想象一下。。。我们有一个网站是活的,即使没有人打开浏览器中的网页。。。这意味着应用程序正在运行,即使没有人与之交互。。。如果我们说是在后台运行的web服务器,而不是实际的web应用程序。。。。但是,web应用程序是如何无限期运行的,即使没有人与之交互。
关于“我们有一个网站是活的,即使没有人在浏览器中打开网页”,没有你的网站是不是“活着”。如果没有任何待处理的http请求,javaservlet就不会执行。servlet已加载并初始化,但在请求到达之前不会执行。
关于“这意味着应用程序正在运行,即使没有人与之交互”,不,你的web应用程序没有运行,正如我上面所说的。您的java servlet代码没有执行。当请求到达时,web容器会自动在线程中调用servlet的代码。最终,servlet代码会生成响应中发送回web浏览器的内容。servlet代码的执行结束。用于执行的线程要么结束,要么返回到线程池(web容器要做的内部决定)。为了简单起见,我忽略了推送技术和websockets。
关于:“如果我们说它是在后台运行的web服务器,而不是实际的web应用程序”,web服务器总是运行一个额外的线程来侦听传入的请求。
➥ 这可能是您困惑的根源:web服务器总是很忙,忙着监听传入的连接,不管是否执行javaservlet代码。
web服务器有一个线程专门用于与主机操作系统协作,以便在网络上侦听传入的连接。
web服务器根据需要启动其他线程,通过制定和发送响应来响应请求。
关于:“web应用程序如何无限期运行,即使没有人与之交互”,您忘记了主机操作系统正在与web容器交互,无论用户是否与您的web应用程序交互。web容器维护一个线程来监听传入的连接。该线程被阻塞,等待主机操作系统的网络堆栈通知传入的请求(我在这里的描述是概括和简化的-我不是网络Maven-但足以说明这一点。)
当请求通过网络传入时,主机操作系统会通知web容器。此通知取消阻止侦听线程。侦听线程将请求分派给一个新线程,从而执行javaservlet的代码。同时,web容器的请求侦听线程返回到被阻塞状态,以等待来自主机操作系统的网络堆栈的关于另一个传入请求的另一个通知。
阻塞的侦听线程是解释/启用web服务器连续和无限期运行的原因。相比之下,你的web应用程序只是在响应一个请求时才突然运行。
你的问题归功于JavaServlet技术的天才和成功。JavaServlet的真正目的是抽象掉所有这些关于监听网络活动的细节,将数据包转换为文本以破译请求,解析请求,将请求的内容Map为特定servlet的责任,确保特定servlet被加载和初始化,并最终在servlet规范定义的servlet代码中调用特定方法。

用户应用程序一直忙于等待输入

类似于web服务器总是忙于侦听传入请求,控制台应用程序和gui应用程序都总是忙于侦听用户输入。他们有时看起来很懒散,但事实并非如此。
虽然用户应用程序不会在cpu上连续运行,但它们确实维护了一个线程,该线程与主机操作系统一起工作,以通知用户事件,如键盘键入和鼠标移动/单击。

相关问题