【java】【服务器】线程上下文丢失 是指什么,【WebSphere中如何解决】
目录
■前言
■正文开始
线程上下文的核心组成部分
为什么会出现上下文丢失?
直观示例说明
为什么上下文如此重要?
解决上下文丢失的关键
总结
■如果我想在servlet中使用线程,代码应该如何实现
推荐方案:使用 ManagedExecutorService(WebSphere 托管线程池)
备选方案:手动管理线程上下文(如果无法使用 ManagedExecutorService)
关键配置步骤(WebSphere 控制台)
两种方案对比
最佳实践建议
完整示例(生产级代码)
■ManagedExecutorService 这个类,在那个jar中
规范定义包(通用)
总结
注意:不适用WebSphere 8.5.5,没有【com.ibm.ws.concurrent.jar】这个jar
■各个WebSphere版本中,线程(线程池)相关的jar
■WebSphere Liberty 是什么
🚀 核心特性
🏢 与传统 WebSphere 的关系
■我在WebSphere 8.5.5中,找到了runtime.jar,但是里面没有【com.ibm.ws.threading.WSManagedExecutorService】这个类
🔍 根本原因分析
类【com.ibm.ws.threading.ThreadPoolManager】也没有找到,而且根本不存在 com.ibm.ws.threading 这个包。
■WorkManager (【JNDI】wm/default)(这个有,问题90%以上可用解决)
0. 代码
1. 启用异步 Beans 支持
2. 配置资源引用
3.实际WebSphere画面
4.关于【resource references】需要在安装时指定。
5.WorkManager相关的JNDI【wm/default】设定后的效果
■前言
Web应用中,为了提高效率,某段和主业务无关的处理,使用异步处理来处理。
(使用的服务器是WebSphere)
结果报如下错误
WebContextsService getStandardContext Failed to retrieve application name
这个错误的原因是线程上下文丢失造成的,
因此,整理解释一下什么是线程上下文丢失
========================================
■正文开始
线程上下文的核心组成部分
-
类加载器(ClassLoader)
-
Web 应用有独立的类加载器(隔离其他应用)
-
负责加载应用中的类、资源和库
-
丢失后果:
ClassNotFoundException
、NoClassDefFoundError
-
-
JNDI(Java Naming and Directory Interface)上下文
-
提供对应用服务器资源的访问(如数据源、JMS 队列)
-
丢失后果:
NamingException
、无法查找java:comp/env
资源
-
-
Web 应用上下文(ServletContext)
-
包含 Web 应用元数据:应用名称、上下文路径、初始化参数
-
丢失后果:
getStandardContext failed to retrieve application name
(我遇到的错误)
-
-
安全上下文(Security Context)
-
包含用户认证/授权信息(如 Principal、角色)
-
丢失后果:
NullPointerException
或权限检查失败
-
-
事务上下文(Transaction Context)
-
管理数据库事务边界
-
丢失后果:事务无法提交/回滚
-
为什么会出现上下文丢失?
-
线程创建方式
// 自定义线程不会继承上下文 new Thread(() -> { // 此处丢失所有上下文! }).start();
-
Web 容器管理的线程 vs 自定义线程
特性 Web 容器线程 (如 HTTP 请求线程) 自定义线程 类加载器 自动设置正确 默认使用系统类加载器 JNDI 上下文 自动可用 InitialContext()
失败ServletContext 通过 getServletContext()
获取返回 null
或抛出异常事务传播 支持 事务边界中断 -
WebSphere 的上下文隔离机制
-
为每个应用创建独立的沙箱环境
-
自定义线程被视为"外部线程",无权访问应用沙箱
-
直观示例说明
假设在 Servlet 中启动线程:
public class MyServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
// 正确环境 (有上下文)
String appName = getServletContext().getContextPath(); // 成功获取
new Thread(() -> {
// 危险区域 (上下文丢失)!
try {
// 尝试获取相同信息
Context ctx = new InitialContext();
String name = (String) ctx.lookup("java:app/AppName"); // 抛出异常!
} catch (NamingException e) {
// 报错: getStandardContext failed to retrieve application name
}
}).start();
}
}
为什么上下文如此重要?
当调用以下 API 时会依赖上下文:
// 依赖 Web 上下文
servletContext.getResource("/WEB-INF/config.xml");
// 依赖 JNDI 上下文
dataSource = (DataSource) new InitialContext().lookup("jdbc/myDB");
// 依赖类加载器
Class.forName("com.myapp.CustomClass");
// 依赖安全上下文
request.isUserInRole("Admin");
解决上下文丢失的关键
必须通过以下方式之一传递上下文:
-
显式传递数据
// 在 Web 线程中预先获取值 String appName = getServletContext().getContextPath(); executor.submit(new MyTask(appName)); // 传递值而非依赖上下文
-
使用托管线程服务(推荐)
@Resource private ManagedExecutorService managedExecutor; // WebSphere 托管 managedExecutor.submit(() -> { // WebSphere 自动注入上下文 Context ctx = new InitialContext(); // 现在正常工作了! });
-
手动恢复上下文(复杂)
// 在 Web 线程中捕获当前类加载器 ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); new Thread(() -> { // 手动恢复类加载器 Thread.currentThread().setContextClassLoader(originalClassLoader); // 注意:这无法恢复 JNDI 或 ServletContext! }).start();
总结
线程上下文丢失本质是:当代码脱离 Web 容器管控的线程时,应用服务器无法自动提供运行所需的环境信息。解决的关键在于:
-
避免在自定义线程中直接访问 Web API
-
使用服务器托管线程池(
ManagedExecutorService
) -
在 Web 线程中预先获取数据并传递值
这正是您遇到的 getStandardContext failed to retrieve application name
的根本原因——自定义线程中无法关联到 Web 应用的 ServletContext
。
===
■如果我想在servlet中使用线程,代码应该如何实现
在 Servlet 中使用自定义线程时,为了正确处理线程上下文问题(特别是在 WebSphere 环境中),强烈推荐使用 Java EE 标准的 ManagedExecutorService
。以下是完整的实现方案:
推荐方案:使用 ManagedExecutorService(WebSphere 托管线程池)
import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.enterprise.concurrent.ManagedExecutorService;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet("/asyncTask")
public class ThreadedServlet extends HttpServlet {
// 注入 WebSphere 托管的线程池服务
@Resource
private ManagedExecutorService managedExecutor;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/plain");
PrintWriter out = response.getWriter();
// 提交任务到托管线程池(自动传递线程上下文)
managedExecutor.submit(() -> {
try {
// 在托管线程中安全访问上下文
String appName = (String) new InitialContext().lookup("java:app/AppName");
String contextPath = request.getServletContext().getContextPath();
// 执行实际业务逻辑
processTask(appName, contextPath);
out.println("Task completed successfully in managed thread!");
} catch (Exception e) {
out.println("Error in managed thread: " + e.getMessage());
e.printStackTrace();
}
});
out.println("Background task started using ManagedExecutorService...");
}
private void processTask(String appName, String contextPath) {
// 这里是实际的业务逻辑
System.out.println("Processing task for application: " + appName);
System.out.println("Context path: " + contextPath);
// 模拟耗时操作
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
备选方案:手动管理线程上下文(如果无法使用 ManagedExecutorService)
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.naming.InitialContext;
@WebServlet("/manualThread")
public class ManualThreadServlet extends HttpServlet {
// 创建普通线程池(不推荐,仅作演示)
private final ExecutorService executor = Executors.newFixedThreadPool(5);
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/plain");
PrintWriter out = response.getWriter();
// 在Web线程中预先获取所需上下文信息
final String appName = getPredefinedAppName();
final String contextPath = request.getServletContext().getContextPath();
final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
// 提交任务到普通线程池
executor.submit(() -> {
// 保存原始类加载器(用于恢复)
ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
try {
// 手动设置上下文类加载器
Thread.currentThread().setContextClassLoader(contextClassLoader);
// 使用预先获取的上下文信息
processTask(appName, contextPath);
out.println("Task completed in manual thread!");
} catch (Exception e) {
out.println("Error in manual thread: " + e.getMessage());
e.printStackTrace();
} finally {
// 恢复原始类加载器
Thread.currentThread().setContextClassLoader(originalClassLoader);
}
});
out.println("Background task started using manual thread...");
}
private String getPredefinedAppName() {
try {
// 在Web线程中预先获取应用名称
return (String) new InitialContext().lookup("java:app/AppName");
} catch (Exception e) {
return "default-app";
}
}
private void processTask(String appName, String contextPath) {
// 业务逻辑同上
}
@Override
public void destroy() {
// 关闭线程池
executor.shutdown();
super.destroy();
}
}
关键配置步骤(WebSphere 控制台)
-
启用并发策略:
-
登录 WebSphere 管理控制台
-
导航到:资源 > 并发策略
-
创建或使用默认的并发策略
-
-
绑定到应用(可选,通常自动注入即可工作):
在ibm-application-bnd.xml
中添加:
两种方案对比
特性 | ManagedExecutorService | 手动线程管理 |
---|---|---|
上下文传播 | 自动完整传播(类加载器、JNDI、安全等) | 仅能手动传递类加载器 |
资源管理 | WebSphere 自动管理生命周期 | 需手动关闭线程池 |
事务支持 | 支持事务上下文传播 | 不支持事务传播 |
Servlet API 访问 | 可直接访问(如 request 、response ) | 只能访问预先获取的数据 |
WebSphere 兼容性 | 完全兼容 | 可能仍有上下文问题 |
代码复杂度 | 简单(声明式注入) | 复杂(需手动管理上下文) |
推荐度 | ⭐⭐⭐⭐⭐(首选方案) | ⭐⭐(备选方案) |
最佳实践建议
-
首选托管线程池:
@Resource private ManagedExecutorService executor; // 始终使用这个
-
避免在子线程中直接使用 Servlet API:
// 错误做法(可能引发问题): managedExecutor.submit(() -> { request.getSession(); // 可能不安全 }); // 正确做法(预先获取所需数据): String sessionId = request.getSession().getId(); managedExecutor.submit(() -> processSession(sessionId));
-
处理线程中的异常:
managedExecutor.submit(() -> { try { // 业务逻辑 } catch (Exception e) { // 1. 记录日志 // 2. 不要吞掉异常 // 3. 考虑通知主线程 } });
-
生命周期管理:
-
不需要手动关闭
ManagedExecutorService
-
对于普通线程池,在
Servlet.destroy()
中关闭:@Override public void destroy() { executor.shutdown(); try { if (!executor.awaitTermination(10, TimeUnit.SECONDS)) { executor.shutdownNow(); } } catch (InterruptedException e) { executor.shutdownNow(); Thread.currentThread().interrupt(); } }
-
完整示例(生产级代码)
@WebServlet("/task")
public class ProductionReadyServlet extends HttpServlet {
@Resource
private ManagedExecutorService managedExecutor;
private static final Logger logger = LoggerFactory.getLogger(ProductionReadyServlet.class);
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
// 预先获取所需上下文信息
String appName = (String) req.getServletContext().getAttribute("app.name");
String clientIP = req.getRemoteAddr();
// 提交任务
managedExecutor.submit(new TrackedTask(appName, clientIP));
resp.getWriter().write("Task submitted successfully");
}
// 封装任务的内部类
private class TrackedTask implements Runnable {
private final String appName;
private final String clientIP;
TrackedTask(String appName, String clientIP) {
this.appName = appName;
this.clientIP = clientIP;
}
@Override
public void run() {
MDC.put("app", appName); // 日志上下文
MDC.put("client", clientIP);
try {
logger.info("Starting background task");
// 业务逻辑...
logger.info("Task completed");
} catch (Exception e) {
logger.error("Task failed", e);
} finally {
MDC.clear();
}
}
}
}
使用这种模式,您可以:
-
安全地在后台线程执行任务
-
保留必要的上下文信息
-
获得完善的错误处理和日志跟踪
-
完全兼容 WebSphere 环境
-
避免
getStandardContext failed
错误
对于大多数场景,使用 ManagedExecutorService
并遵循上述模式即可解决线程上下文问题。
==
■ManagedExecutorService 这个类,在那个jar中
规范定义包(通用)
-
包名:
javax.enterprise.concurrent
(Java EE 7/8)或jakarta.enterprise.concurrent
(Jakarta EE 9+) -
API JAR:
-
Java EE 7/8:
javax.enterprise.concurrent-api.jar
(例如 WildFly/JBoss 提供) -
Jakarta EE 9+:
jakarta.enterprise.concurrent-api.jar
-
-
说明:
此 JAR 仅包含接口定义(如ManagedExecutorService
),不包含实现。实现由应用服务器提供
总结
-
开发阶段:依赖
javax.enterprise.concurrent-api
或jakarta.enterprise.concurrent-api
编写接口代码。 -
运行时:实现类由应用服务器提供(如 WebSphere 的
com.ibm.ws.concurrent.jar
)。 -
配置建议:通过
@Resource
注入或 JNDI 获取实例,避免手动创建线程池
注意:不适用WebSphere 8.5.5,没有【com.ibm.ws.concurrent.jar
】这个jar
WebSphere 8.5.5 里面没有
WebSphere 8.5.5
在8.5.5版本中,并发工具的实现通常在以下jar包中:
- `com.ibm.ws.concurrent_1.0.0.jar` (在传统版本中可能是`com.ibm.ws.concurrent.jar`)
- 或者合并到`com.ibm.ws.runtime.jar`中(因为并发工具是运行时的一部分)有
WebSphere 9.0及以上
在9.0及更高版本中,通常有独立的并发jar包,例如:
- `com.ibm.ws.concurrent_1.0.0.jar`
- `com.ibm.ws.concurrent.cdi_1.0.0.jar`(用于CDI集成)
■各个WebSphere版本中,线程(线程池)相关的jar
===
我在WebSphere 8.5.5中,找到了runtime.jar,
但是
里面没有【com.ibm.ws.threading.WSManagedExecutorService】这个类
- 确保版本至少为8.5.5.0(初始版本)
- 推荐升级到8.5.5.20或更高
2. 考虑功能缺失:
- 某些精简安装可能未包含并发工具
- 重新安装时选择“完整功能”
3. 使用传统WorkManager:
===
WebSphere 版本 | 实现位置 | 实现类名 |
---|---|---|
8.5.x 及更早 | lib/com.ibm.ws.runtime.jar | com.ibm.ws.threading.WSManagedExecutorService |
9.x+ | plugins/com.ibm.ws.concurrent_*.jar | com.ibm.ws.concurrent.WSManagedExecutorService |
Liberty | wlp/lib/com.ibm.websphere.concurrent_*.jar | com.ibm.ws.concurrent.WSManagedExecutorServiceImpl |
===
■WebSphere Liberty 是什么
===
WebSphere Liberty 是 IBM 推出的新一代轻量级、高性能应用服务器,专为云原生和微服务架构设计。它是传统 WebSphere Application Server(WAS)的现代化替代品,代表了应用服务器技术的未来发展方向。
🚀 核心特性
特性 | 传统 WebSphere | WebSphere Liberty |
---|---|---|
架构 | 单体式,重量级 | 模块化,轻量级 |
启动时间 | 分钟级(30s-5min) | 秒级(<3s) |
内存占用 | 高(GB级) | 极低(<100MB) |
配置方式 | XML + 控制台 | 声明式配置(server.xml) |
运行时模型 | 全功能运行 | 按需加载特性 |
云支持 | 有限 | 原生云集成(K8s,OpenShift) |
===
🏢 与传统 WebSphere 的关系
维度 | WebSphere Traditional | WebSphere Liberty |
---|---|---|
定位 | 企业级关键业务系统 | 云原生/微服务/敏捷开发 |
许可证 | 需单独购买 | 免费开发者版 + 商业版 |
迁移路径 | - | 提供 传统到Liberty迁移工具 |
管理方式 | 集中式管理控制台 | REST API + 命令行 + 图形界面 |
版本更新 | 年度大版本 | 季度滚动更新 |
===
■我在WebSphere 8.5.5中,找到了runtime.jar,但是里面没有【com.ibm.ws.threading.WSManagedExecutorService】这个类
===
com.ibm.ws.threading.ThreadPoolManager 这里类也没有呀,根本就不存在 com.ibm.ws.threading 这个包。
===
🔍 根本原因分析
在 WebSphere 8.5.5 中:
-
ManagedExecutorService
的实现不是通过单独的类
而是通过动态代理机制实现 -
实际工作类:
com.ibm.ws.threading.ThreadPoolManager
-
位置:
plugins/com.ibm.ws.runtime.jar
(不是lib/
目录)
===
# Linux/Unix
jar -tvf $WAS_INSTALL_ROOT/plugins/com.ibm.ws.runtime.jar | grep ThreadPoolManager
# Windows
jar -tvf "%WAS_INSTALL_ROOT%pluginscom.ibm.ws.runtime.jar" | findstr ThreadPoolManager
类
【com.ibm.ws.threading.ThreadPoolManager】也没有找到,而且根本不存在 com.ibm.ws.threading 这个包。
===
虽然找到了runtime.jar,但是,jar里面没有【com.ibm.ws.threading
】这个包
===
■WorkManager (【JNDI】wm/default)(这个有,问题90%以上可用解决)
===
ls $WAS_INSTALL_ROOT/AppServer/lib/asynchbeans.jar
====
0. 代码
==
import com.ibm.websphere.asynchbeans.Work;
import com.ibm.websphere.asynchbeans.WorkManager;
import javax.naming.InitialContext;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
@WebServlet("/workManager")
public class WorkManagerServlet extends HttpServlet {
protected void doGet(javax.servlet.http.HttpServletRequest request,
javax.servlet.http.HttpServletResponse response) {
try {
WorkManager wm = (WorkManager) new InitialContext()
.lookup("java:comp/env/wm/default");
wm.startWork(new Work() {
@Override
public void run() {
System.out.println("Task running via WorkManager");
performTask();
}
@Override
public void release() {
// 清理资源
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
private void performTask() {
// 同上
}
}
===
1. 启用异步 Beans 支持
在 WebSphere 控制台中: (实际WebSphere画面,和下面不太相符)
-
导航到:应用程序服务器 > [您的服务器] > 容器服务 > 异步 Beans
-
勾选 启用异步 Beans 支持
-
保存并重启服务器
==
2. 配置资源引用
在 web.xml
中添加:
wm/default
com.ibm.websphere.asynchbeans.WorkManager
在 ibm-web-bnd.xml
中添加绑定:
===
3.实际WebSphere画面
==
====
==
===
===
==
4.关于【resource references】需要在安装时指定。
==
安装前的web.xml
使用下面的ear再改造一下
hello-world-ear/EnterpriseHelloWorld.ear at master · imago-storm/hello-world-ear · GitHub
30
index.html
wm/default
com.ibm.websphere.asynchbeans.WorkManager
Resource reference for database
jdbc/dbname
javax.sql.DataSource
Container
===
==
===
5.WorkManager相关的JNDI【wm/default】设定后的效果
==