V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
linuxsteam
V2EX  ›  Java

Java 线程上下文 类加载器 会进行传递吗?

  •  
  •   linuxsteam · 2021-12-09 11:13:33 +08:00 · 3009 次点击
    这是一个创建于 1109 天前的主题,其中的信息可能已经有所发展或是发生改变。
    package com.company;
    
    import java.io.File;
    import java.net.URL;
    import java.net.URLClassLoader;
    
    public class Main {
        
        public static void main(String[] args) {
    
            Thread mainThread = Thread.currentThread();
            new Thread(new Runnable() {
    
                @Override
                public void run() {
                    try {
                        URL[] urls = new URL[1];
                        urls[0] = new File("/Users/wf/IdeaProjects/thread/dom4j-2.1.3.jar").toURI().toURL();
                        URLClassLoader urlClassLoader = new URLClassLoader(urls);
                        mainThread.setContextClassLoader(urlClassLoader);
                        System.out.println("在" + Thread.currentThread().getName() + "设置了主线程的自定义 classLoader " + urlClassLoader);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }, "线程 1").start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        for (int i = 0; i < 100; i++) {
                            Thread.sleep(1000);
                            System.out.println(Thread.currentThread().getName() + "上下文 设置前的 classloader" + Thread.currentThread().getContextClassLoader());
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }, "线程 2").start();
    
        }
    }
    
    

    首先我不知道标题应该怎么起更好一些,

    上面代码大概意思就是 主线程同时新建立两个线程。

    • 1 号线程里 操作了主线程的上下文 ClassLoader
    • 2 号线程里 直接就能获取到 1 号线程操作后的主线程 classLoader 。

    是的,我看了部分文章,代码运行的结果,跟我想象中的一样。(子线程会一直使用父线程的 classLoader 并且还是实时更新的)

    但是我现在工作中遇到的问题 跟我上述的结论不一样,不知道是哪里出问题了。。。
    14 条回复    2021-12-10 11:42:23 +08:00
    zxlzy
        1
    zxlzy  
       2021-12-09 11:45:33 +08:00
    然而并不能啊。在设置 classLoader 前加个 Thead.sleep() 就知道不能了。你的结论就是错的。本质上是线程 1 先运行线程 2 才运行的。
    ```java
    public class Main {

    public static void main(String[] args) {

    Thread mainThread = Thread.currentThread();
    new Thread(new Runnable() {

    @Override
    public void run() {
    try {
    ClassLoader cl = new ClassLoader() {
    @Override
    public String getName() {
    return "MyCloassLoader";
    }
    };
    mainThread.setContextClassLoader(cl);
    System.out.println("在" + Thread.currentThread().getName() + "设置了主线程的自定义 classLoader " + cl);
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    }, "线程 1").start();

    new Thread(new Runnable() {
    @Override
    public void run() {
    try {
    for (int i = 0; i < 100; i++) {
    Thread.sleep(1000);
    System.out.println(Thread.currentThread().getName() + "上下文 设置前的 classloader" + Thread.currentThread().getContextClassLoader());
    }
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    }, "线程 2").start();

    }
    }

    ```
    zxlzy
        2
    zxlzy  
       2021-12-09 11:46:56 +08:00
    代码贴错了

    public class Main {

    public static void main(String[] args) {

    Thread mainThread = Thread.currentThread();
    new Thread(new Runnable() {

    @Override
    public void run() {
    try {
    ClassLoader cl = new ClassLoader() {
    @Override
    public String getName() {
    return "MyCloassLoader";
    }
    };
    TimeUnit.SECONDS.sleep(3);
    mainThread.setContextClassLoader(cl);
    System.out.println("在" + Thread.currentThread().getName() + "设置了主线程的自定义 classLoader " + cl);
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    }, "线程 1").start();

    new Thread(new Runnable() {
    @Override
    public void run() {
    try {
    for (int i = 0; i < 100; i++) {
    Thread.sleep(1000);
    System.out.println(Thread.currentThread().getName() + "上下文 设置前的 classloader" + Thread.currentThread().getContextClassLoader());
    }
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    }, "线程 2").start();

    }
    }
    BBCCBB
        3
    BBCCBB  
       2021-12-09 11:53:17 +08:00
    你线程 2 不是线程 1 的子线程主要是 ==
    linuxsteam
        4
    linuxsteam  
    OP
       2021-12-09 12:35:50 +08:00
    @zxlzy 明白了 谢谢大佬。谢谢谢谢谢~~~~~~。
    项目中结果就是大佬说的情况
    请问大佬,用户可以自定义多个 classloader 吗?就是 set 上下文加载器的时候 不把上次设置的覆盖。
    我测试是没啥办法,这方面资料 搜索引擎都不好搜、、。。
    zxlzy
        5
    zxlzy  
       2021-12-09 14:49:12 +08:00
    @BBCCBB 其实根本就没有父子线程的概念。
    zxlzy
        6
    zxlzy  
       2021-12-09 14:54:31 +08:00
    @linuxsteam 首先你为什么要这样做呢。就算你不覆盖上次的,那你具体加载类的时候,还是只能用一个类加载器加载呀。
    Class.forName 是可以传类加载器的。forName(String name, boolean initialize, ClassLoader loader)。
    BBCCBB
        7
    BBCCBB  
       2021-12-09 15:10:23 +08:00
    @zxlzy 主要是 Thread 类里的当前线程用的名字叫 parent. 大家能懂起就行.
    linuxsteam
        8
    linuxsteam  
    OP
       2021-12-09 16:04:13 +08:00
    @zxlzy 工作中的项目,是引入外部 jar 包插件。每引入一次插件就得创建一次 ClassLoader
    因为 UrlClassLoader 除了构造,不支持修改 URL 属性 [是固定长度数组] ;

    我现在只有尝试自己实现类似 URLClassLoader 的东西。然后修改这个 ClassLoader 引用中的 URL 属性。(感觉源码 URL 属性用数组是有道理的,我用集合代替 感觉多半不行)
    zxlzy
        9
    zxlzy  
       2021-12-09 17:14:21 +08:00
    “每引入一次插件就得创建一次 ClassLoader”,这个有什么问题呢,你是担心这个操作影响性能?所以不想每次都创建新的 ClassLoader?
    linuxsteam
        10
    linuxsteam  
    OP
       2021-12-09 19:13:58 +08:00
    @zxlzy 这个没担心,创建完就得 setContextClassloader 呀
    set 完 以后 之前的 ContextClassloader 就会被覆盖了
    pursuer
        11
    pursuer  
       2021-12-09 19:43:35 +08:00
    JVM 链接查找类时的 ClassLoader 和 ContextClassloader 好像是无关的。如果想实现动态增删的 ClassLoader ,可以通过覆写 findClass 实现。也可以参考下面这个,这是我一个小项目里的一个支持动态增减 ClassLoader 的类加载器。
    https://github.com/Pursuer2/xplatj/blob/master/commonj/src/main/java/xplatj/javaplat/pursuer/lang/IntegratedClassLoader.java
    jorneyr
        12
    jorneyr  
       2021-12-09 22:01:52 +08:00
    setContextClassLoader 主要是解决 bootstrap 加载的类能使用 SPI 加载用户指定的 jar 包,一般要先备份 classloader ,然后设置 context class loader ,使用完后恢复线程开始的 class loader
    goalidea
        13
    goalidea  
       2021-12-10 11:18:31 +08:00
    @zxlzy 你这是 jdk 几啊,ClassLoader 在 1.8 里没有 getName()
    zxlzy
        14
    zxlzy  
       2021-12-10 11:42:23 +08:00
    @goalidea jdk9 开始有的,我用的 11
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2912 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 13:46 · PVG 21:46 · LAX 05:46 · JFK 08:46
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.