线程上下文类加载器与普通类加载器的区别

2025/04/02

1. 概述

Java在程序执行期间使用不同类型的类加载器来加载资源,在本教程中,我们将探讨Java中当前类加载器和线程类加载器的行为差异。

2. 类加载器起什么作用?

Java类加载器定位并加载应用程序执行所需的类,如果请求的类依赖于任何其他资源,它们也会被加载。

我们需要适当的类加载器,以便在Java程序需要时加载不同类型的类

3. 类加载器之间的关系

Java类加载器遵循层次关系

每个查找或加载类的请求都被委托给相应的父类加载器,如果所有祖先类加载器都无法找到某个类,则当前类加载器会尝试定位它。这里,“当前类”表示当前正在执行的方法的类。

类加载器之间的这种关系有助于维护应用程序中资源的唯一性。此外,如果一个类已经被父类加载器加载过,则子类加载器不需要重新加载它。

4. 默认类加载器

类加载器加载其各自类路径上存在的类和资源:

  • 系统或应用程序类加载器从应用程序类路径加载类
  • 扩展类加载器在扩展类路径(JRE/lib/ext)上搜索
  • 启动类加载器查找Bootstrap类路径(JRE/lib/rt.jar)

启动或扩展类加载器是所有类加载器的父级,它加载Java运行时-运行JVM本身所需的类

当前类加载器以线性、分层的方式搜索资源。如果类加载器无法找到某个类,它会向相应的子类加载器抛出java.lang.ClassNotFoundException,子类加载器然后尝试搜索该类。

对于在层次结构中任何类加载器的类路径上都找不到所需资源的情况,我们会得到与java.lang.ClassNotFoundException相关的错误消息作为最终结果。

我们也可以自定义默认的类加载行为,我们可以在动态加载类时显式指定类加载器

但是,我们应该注意,如果我们从不同类型的类加载器加载相同的类,那么这些将被JVM视为不同的资源。

5. 上下文类加载器

除了默认的类加载器,J2SE还引入了上下文类加载器

Java中的每个线程都有一个关联的上下文类加载器

我们可以使用Thread类的getContextClassLoader()和setContextClassLoader()方法访问/修改线程的上下文类加载器。

上下文类加载器是在创建线程时设置的,如果没有明确设置,则默认为父线程的上下文类加载器

上下文类加载器也遵循层次结构模型,在这种情况下,根类加载器是原始线程的上下文类加载器。原始线程是操作系统创建的初始线程。

当应用程序开始执行时,可能会创建其他线程。原始线程的上下文类加载器初始设置为加载应用程序的类加载器,即系统类加载器。

假设我们不更新层次结构中任何级别的任何线程的上下文类加载器。因此,我们可以说默认情况下,线程的上下文类加载器与系统类加载器相同。对于这种情况,如果我们执行Thread.currentThread().getContextClassLoader()和getClass().getClassLoader()操作,两者将返回相同的对象。

5.1 处理委托问题

当所需资源不在默认Java类加载器的类路径中时,上下文类加载器就显得非常重要。因此,我们可以使用上下文类加载器来区别于传统的线性委托模型

在类加载器的分层模型中,父类加载器加载的资源对子类加载器可见,但反之则不然。在某些情况下,父类加载器可能需要访问存在于子类加载器类路径中的类。

上下文类加载器是实现这一目标的有用工具,我们可以在访问所需资源时将上下文类加载器设置为所需的值。因此,在上述情况下,我们可以使用子线程的上下文类加载器,并可以定位子类加载器级别存在的资源。

5.2 多模块环境

在设置上下文类加载器属性时,我们基本上是在切换加载资源的上下文。我们不是在当前类路径中搜索,而是获取一个指向不同类路径的新类加载器。如果我们想从第三方模块加载资源,或者如果我们在具有不同类名称空间的环境中工作,这将特别有用。

但是,我们应该在这里小心谨慎,将上下文类加载器属性重置回原始类加载器,以避免将来出现任何差异

6. 总结

在本文中,我们分析了使用上下文类加载器加载无法通过普通类加载器访问的资源的重要性。我们看到我们还可以选择临时更新给定线程的上下文类加载器以加载所需的类。

了解当前方法的工作环境至关重要,我们可以在不同的类路径上存在同名的资源。因此,在从多个类加载器加载资源时,我们应该小心谨慎。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章