When the main thread in a single-threaded application throws an unhandled exception, as the console prints the stack trace (also because the program stops), you are likely to notice. However, in multithreaded applications, especially in applications that run as servers and are not connected to the console, thread death can become a less compelling event, causing local systems to fail, resulting in chaotic application behavior.
In the Java theory and Practice October column, we studied the thread pool and examined how the incorrectly written thread pool would "leak" threads until all threads were eventually lost. Most thread pooling implementations prevent this by capturing thrown exceptions or restarting dead threads, but the problem of thread leaks is not limited to the thread pool-a server application that uses threads to service a work queue can also have this problem. When a server application loses a worker thread (worker thread), the application may still appear to be all right over a longer period of time, making it difficult to determine the true cause of the problem.
Many applications use threads to provide background services-processing tasks from the event queue, reading commands from sockets, or performing long-term tasks outside of the UI thread. What happens when an runtimeexception or Error is thrown, or just stops to wait for blocked I/O operations (originally not expected to be blocked), causing these threads to die?
Sometimes, for example, when a thread performs a long-running task, such as a spell check, that is initiated by a user, the user notices that the task is not progressing and may terminate the operation or program abnormally. But other times, background threads perform "cleanup maintenance" tasks that may disappear for a long time without being detected.
Sample Server Application
Consider a hypothetical middleware server application that aggregates messages from various input sources and then submits them to external server applications, receives responses from external applications, and routes responses back to the appropriate input sources. For each input source, there is a plug-in that accepts its input messages in its own way (by scanning the file directory, waiting for socket connections, polling database tables, and so on). Plug-ins can be written by third parties, even if they are running on the server JVM. This application has (at least) two internal work queues-messages that are waiting to be sent to the server from the plug-in (the "Outbound message" queue), and the response received from the server waiting to be passed to the appropriate plug-in ("Inbound response" queue). The message is routed to the plug-in that originally made the request by calling the service routine incomingresponse () on the plug-in object.
When a message is received from a plug-in, it is queued to the outbound message queue. One or more threads that read messages from the queue process the messages in the outbound message queue, record their source, and submit it to the remote server application (assuming through the Web service interface). The remote application eventually returns the response through the Web service interface, and then our server arranges the received responses into the inbound response queue. One or more response threads read the message from the inbound response queue and route it to the appropriate plug-in to complete a roundtrip "journey".
In this application, there are two message queues for outbound requests and inbound responses, and there may be additional queues within different plug-ins. We also have several service threads, one that reads the request from the outbound message queue and submits it to the external server, one that reads the response from the inbound response queue and routes it to the plug-in, and may also have some threads in the plug-in used to service the socket or other external request source.
It's not always obvious when a thread fails
What happens if one of these threads disappears, such as responding to an assign thread? Because Plug-ins are still able to commit new messages, they may not immediately notice certain aspects of the error. Messages will still arrive through a variety of input sources and submitted to external services through our application. Because the plug-in does not expect to get its response immediately, it is still unaware of the problem. Finally, the received response will be filled with queues. If they are stored in memory, they will eventually run out of memory. Even if you don't run out of memory, someone will find that the response is not delivered at some point--but it may take some time because other aspects of the system still work.
When the main task processing is handled by a thread pool rather than a single thread, there is some degree of protection for the consequences of accidental thread leaks, because a thread pool of eight threads that perform well, and the efficiency of working with seven threads, may still be acceptable. At first, there may not be any significant difference. However, system performance will eventually fall, although this decline is not easy to detect.
The problem with threading leaks in server applications is that it is not always easy to detect from the outside. Because most threads handle only a portion of the server's workload, or may only handle a specific type of background task, when the program actually encounters a serious failure, it still works for the user. This, coupled with the factors that cause thread leaks, does not always leave a clear mark, causing surprising or confusing application behavior.