在线程之间传递对象时,是否可以使用 instanceof?

14

我遇到了一个instanceof有时起作用,有时不起作用的问题。详细说明很困难,但我认为这可能是问题所在:

阅读以下内容:http://www.theserverside.com/news/thread.tss?thread_id=40229(搜索Thread.currentThread),它似乎暗示,即使两个对象属于相同的类,如果它们在具有不同类加载器的线程之间传递,instanceof(和isAssignableFrom)仍可能失败。

这肯定可以解释我目前面临的行为,但我想知道是否有人能够验证?

(我希望讨论开始时链接的文章仍然可用,但似乎不是这样。)


你是如何在线程之间传递实例的?如果没有正确处理,就会涉及到内存可见性的微妙陷阱。 - nos
是的,我并不是故意这么做的。实际上,我不确定线程是否真的是问题所在。我正在向Java Logger Handler传递一个参数,该参数采用Object[]形式。我尝试将其强制转换回参数对象,但有时会失败,通常是当应用程序在服务器上重新部署时。 - Risser
2个回答

30
这与线程无关,只与类加载器有关。当不同的类加载器加载相同的类定义时,JVM将其视为两个不同的类。因此,在这两个类之间进行instanceof或转换将失败。
所以回答您最初的问题:在由同一个类加载器加载的线程之间传递对象是安全的,并且instanceof等运算符可以正常工作。
这里有一篇关于类加载问题的 文章
另请参见我的早期答案,了解验证哪些类加载器处于活动状态的方法。
Romain的评论更新如下:
这里有一些代码可用于测试instanceof的行为,等等:
URL[] urls = new URL[] {new File("build/classes/").toURL()};
ClassLoader loader1 = new URLClassLoader(urls, null);
ClassLoader loader2 = new URLClassLoader(urls, null);
Class<?> c1 = loader1.loadClass("net.torokpeter.Foo");
Class<?> c2 = loader2.loadClass("net.torokpeter.Foo");
Object foo1 = c1.newInstance();
Object foo2 = c2.newInstance();

System.out.println("c1.toString(): " + c1);
System.out.println("c2.toString(): " + c2);
System.out.println("c1.equals(c2): " + c1.equals(c2));
System.out.println("c1 == c2: " + (c1 == c2));
System.out.println("foo1: " + foo1);
System.out.println("foo2: " + foo2);
System.out.println("foo1 instanceof Foo: " + (foo1 instanceof Foo));
System.out.println("foo2 instanceof Foo: " + (foo2 instanceof Foo));
System.out.println("c1.isAssignableFrom(c1): " + c1.isAssignableFrom(c1));
System.out.println("c2.isAssignableFrom(c2): " + c2.isAssignableFrom(c2));
System.out.println("c1.isAssignableFrom(c2): " + c1.isAssignableFrom(c2));
System.out.println("c2.isAssignableFrom(c1): " + c2.isAssignableFrom(c1));
System.out.println("c1.isAssignableFrom(Foo.class): " + c1.isAssignableFrom(Foo.class));
System.out.println("c2.isAssignableFrom(Foo.class): " + c2.isAssignableFrom(Foo.class));
System.out.println("Foo.class.isAssignableFrom(c1): " + Foo.class.isAssignableFrom(c1));
System.out.println("Foo.class.isAssignableFrom(c2): " + Foo.class.isAssignableFrom(c2));

最后输出结果为(在Eclipse,Java5中):

c1.toString(): class net.torokpeter.Foo
c2.toString(): class net.torokpeter.Foo
c1.equals(c2): false
c1 == c2: false
foo1: net.torokpeter.Foo@360be0
foo2: net.torokpeter.Foo@45a877
foo1 instanceof Foo: false
foo2 instanceof Foo: false
c1.isAssignableFrom(c1): true
c2.isAssignableFrom(c2): true
c1.isAssignableFrom(c2): false
c2.isAssignableFrom(c1): false
c1.isAssignableFrom(Foo.class): false
c2.isAssignableFrom(Foo.class): false
Foo.class.isAssignableFrom(c1): false
Foo.class.isAssignableFrom(c2): false

所以一切都看起来是一致的 :-)


5
@Romain,我非常怀疑这一点,因为这意味着instanceof返回true,然后类转换失败并抛出异常!这完全是不合逻辑的,并且使instanceof变得不可靠。虽然我没有具体的证据,但如果你有,请随时提供。 - Péter Török
到目前为止,据我所知,Péter是正确的。 instanceof、isAssignableFrom()和类转换全部失败了。我将输出类加载器并查看发生了什么。 - Risser
@Romain,我添加了一些测试代码和输出结果,表明“instanceof”确实返回了“false”。 - Péter Török
1
两个类加载器可以共享一个父类加载器,这个父类加载器可能会加载一些类。这些类在两个类加载器中是相同的,这可能是混淆的源头。 - starblue
1
@starblue,正确。上面的类加载器是使用new URLClassLoader(urls, null)创建的,这将它们的父级设置为null。但是,如果它们被创建为new URLClassLoader(urls);,它们将共享相同的父级,然后由它们加载的两个Foo类是相同的。 - Péter Török
显示剩余3条评论

1
问题就像Péter Török所说的那样,与类加载器有关。顺便提一下,这也是JNDI存在的原因,它允许通过单个中央类加载器创建常见对象(这意味着您需要的类需要在单个中央类加载器的类路径中,当您需要更多内容时,这会带来各种乐趣,而不仅仅是字符串)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接