云计算百科
云计算领域专业知识百科平台

【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 中添加:

    <application-bnd>
    <managed-executor-service name="concurrent/executorSvc" />
    </application-bnd>

  • 两种方案对比

    特性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)的现代化替代品,代表了应用服务器技术的未来发展方向。

    🚀 核心特性

    特性传统 WebSphereWebSphere Liberty
    架构 单体式,重量级 模块化,轻量级
    启动时间 分钟级(30s-5min) 秒级(<3s)
    内存占用 高(GB级) 极低(<100MB)
    配置方式 XML + 控制台 声明式配置(server.xml)
    运行时模型 全功能运行 按需加载特性
    云支持 有限 原生云集成(K8s,OpenShift)

    ===

    🏢 与传统 WebSphere 的关系

    维度WebSphere TraditionalWebSphere 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%\\plugins\\com.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 中添加:

    <resource-ref>
    <res-ref-name>wm/default</res-ref-name>
    <res-type>com.ibm.websphere.asynchbeans.WorkManager</res-type>
    </resource-ref>

    在 ibm-web-bnd.xml 中添加绑定:

    <resource-ref name="wm/default"
    binding-name="java:comp/env/wm/default"/>

    ===

    3.实际WebSphere画面

    ==

    ====

    ==

    ===

    ===

    ==

    4.关于【resource references】需要在安装时指定。

    ==

    安装前的web.xml

    使用下面的ear再改造一下

    hello-world-ear/EnterpriseHelloWorld.ear at master · imago-storm/hello-world-ear · GitHub

    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1">

    <session-config>
    <session-timeout> 30 </session-timeout>
    </session-config>
    <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    </welcome-file-list>

    <resource-ref>
    <res-ref-name>wm/default</res-ref-name>
    <res-type>com.ibm.websphere.asynchbeans.WorkManager</res-type>
    </resource-ref>

    <resource-ref>
    <description>Resource reference for database</description>
    <res-ref-name>jdbc/dbname</res-ref-name>
    <res-type>javax.sql.DataSource</res-type>
    <res-auth>Container</res-auth>
    </resource-ref>

    </web-app>

    ===

    ==

    ===

    5.WorkManager相关的JNDI【wm/default】设定后的效果

    ==

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 【java】【服务器】线程上下文丢失 是指什么,【WebSphere中如何解决】
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!