Is ThreadLocal safe to use with Tomcat NIO Connector

前端 未结 3 996
一整个雨季
一整个雨季 2021-02-09 07:43

This just came to mind when testing the Tomcat NIO connector during my load tests. I make use of ThreadLocal\'s additionally I use Spring, which I know in several places it als

3条回答
  •  死守一世寂寞
    2021-02-09 08:03

    To add to the accepted answer from Tim and the follow up question from pacman, you do need to be careful when using the AsyncResponse or similar feature together with the NIO connector. I'm not sure what Tim means by, "your [async] servlet might get called multiple times for a single request" ... but if a "request" refers to a single "GET", "PUT", "POST", or "DELETE" then AFAIK that will result in a single call to the corresponding resource method in your servlet.

    One issue you could run into with ThreadLocals and async resources is if the processing Thread in the async resource needs a copy of the ThreadLocal variable from the NIO event loop Thread. In other words, the NIO event loop Thread accepts a request then passes control to your async resource ... then that resource passes control to a child Thread ... then the NIO event loop Thread is free to handle another request ... so any ThreadLocal variables in the NIO event loop Thread might be stomped on by the subsequent request.

    Note that it's also possible for each new request to make a new instance of the Object stored in the ThreadLocal ... in which case each new request will not stomp on the old instances that had been stored in the same ThreadLocal during previous requests ... but you need to be sure which case you are dealing with ... let's look at some examples.

    The original question refers to Spring so a good example is the RequestContextHolder which has a ThreadLocal. Let's say the NIO event loop Thread is named, "http-nio-8080-exec-1" and it passes control to an AsyncResponse resource that then launches a new Thread (named "pool-2-thread-3") via an Executor. The new Thread has code that needs something from the RequestAttributes to get the answer to pass back via AsyncResponse.resume(). Since the code executing in Thread "pool-2-thread-3" needs to access the RequestAttributes from "http-nio-8080-exec-1" then you need to make sure of two things:

    1) Your resource grabs a reference to the RequestAttributes from "http-nio-8080-exec-1" and passes it into "pool-2-thread-3"

    2) When "http-nio-8080-exec-1" accepts a new request it will make a new copy of RequestAttributes and set that into it's ThreadLocal copy for RequestContextHolder for the new request (note, Spring code does work this way, so it's safe).

    A contrary example is the log4j MDC ThreadLocal copy of the Map. In this case each new request reuses the same Map ... so it's not safe to pass the reference of the Map from the NIO event loop Thread to the AsyncResponse Thread ... you need to make a copy of the Map and pass that. See MDCAwareThreadPoolExectutor for an example of how to do that.

    Basically, you'll need to check each ThreadLocal variable that you need to pass from the NIO event loop Thread into your AsyncResponse Thread ... and see if it is safe to just pass a reference to the original Object, or if you need to make a copy of the Object before setting the copy into the worker Thread's ThreadLocal variable.

    BTW, Here's some code that combines the two examples above:

    public class RequestContextAwareThreadPoolExecutor extends MDCAwareThreadPoolExecutor {
        /* ... constructors left out ... */
    
        @Override
        public void execute(Runnable runnable) {
            super.execute(wrap(runnable, RequestContextHolder.currentRequestAttributes()));
        }
    
        Runnable wrap(final Runnable runnable, final RequestAttributes requestAttributes) {
            return () -> {
                RequestContextHolder.setRequestAttributes(requestAttributes);
                try {
                    runnable.run();
                } finally {
                    RequestContextHolder.resetRequestAttributes();
                }
            };
        }
    }
    

    From your AsyncResponse resource simply make a call like this:

    executor.execute(() -> {
        // veryLongOperation() needs to access the RequestAttributes and the MDC
        asyncResponse.resume(veryLongOperation());
    });
    

提交回复
热议问题