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

2021-12-09 11:13:33 +08:00
 linuxsteam
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();

    }
}

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

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

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

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

我现在只有尝试自己实现类似 URLClassLoader 的东西。然后修改这个 ClassLoader 引用中的 URL 属性。(感觉源码 URL 属性用数组是有道理的,我用集合代替 感觉多半不行)
zxlzy
2021-12-09 17:14:21 +08:00
“每引入一次插件就得创建一次 ClassLoader”,这个有什么问题呢,你是担心这个操作影响性能?所以不想每次都创建新的 ClassLoader?
linuxsteam
2021-12-09 19:13:58 +08:00
@zxlzy 这个没担心,创建完就得 setContextClassloader 呀
set 完 以后 之前的 ContextClassloader 就会被覆盖了
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
2021-12-09 22:01:52 +08:00
setContextClassLoader 主要是解决 bootstrap 加载的类能使用 SPI 加载用户指定的 jar 包,一般要先备份 classloader ,然后设置 context class loader ,使用完后恢复线程开始的 class loader
goalidea
2021-12-10 11:18:31 +08:00
@zxlzy 你这是 jdk 几啊,ClassLoader 在 1.8 里没有 getName()
zxlzy
2021-12-10 11:42:23 +08:00
@goalidea jdk9 开始有的,我用的 11

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/821066

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX