【问题标题】:A fast thread-safe way to identify method calls originating from the superclass一种快速线程安全的方法来识别源自超类的方法调用
【发布时间】:2013-02-10 17:26:43
【问题描述】:

让我们考虑一个具有以下特征的第三方类A

  1. 它的大部分实现是私有的并且相当复杂,因此可以扩展但不能合理修改。

  2. A 有一个 非常 昂贵的构造函数,应该 永远 被不必要地调用 - 在我的例子中,它构建了一个 huge 数据查找表供以后使用。

    这意味着创建一个decorator class 来替代A 是不可能的,因为任何装饰器都必须从A 继承并调用其构造函数。

  3. A 被第三方代码使用,因此不能使用 proxy class 代替它 - 其他代码需要 A 的实例,这就是它应该得到的。

  4. A 提供了一堆非常昂贵的报告方法,frabble很多wrabbles来生成一堆报告。 p>

    这些方法不会产生任何本地使用的结果 - 它们理论上可以是异步的,但不幸的是它们不是。

  5. A 的方法互相调用 - 很多。

  6. A 实现是不透明的,并且在不断变化 - 像使用反射后实例化这样的黑客攻击,例如瘦身是不可能的。

  7. A 不是线程安全的 - 不是真的...

我达到了A 成为我项目中的瓶颈的地步,所以我创建了一个子类B 希望推动wrabbles 的实际frabbling > 到一个单独的线程。我覆盖了A 的所有公共方法,只将Runnable 对象提交给单线程执行器服务。每个Runnable 然后从工作线程执行适当的super 方法。

不幸的是,A经常调用自己的公共方法。通常这不是问题,但在我的情况下,超类正在调用来自B 的方法。这一个主要问题,因为B 方法延迟实际执行并将新任务提交给执行器服务,从而导致性能和正确性问题。

我的解决方案是检查 Thread.currentThread() 在来自 B 的所有方法中的结果,以便来自工作线程的调用将直接委托给 super 方法,而不是作为新任务提交。

所以现在我正处于 Thread.currentThread() 已成为性能问题的地步,这可能是因为它是一种过于频繁调用的本机方法。

  • 是否有更快的方法以线程安全的方式检查源自超类和/或工作线程的方法调用?

  • 是否有替代设计可以让我将Afrabbling wrabbles 推送到另一个线程?在我看来,由于A 是第三方代码,我大多被困...

【问题讨论】:

  • 什么是“本地使用的结果”(本地到什么?),这与异步有什么关系(您的意思是线程安全的吗?)
  • @meriton:我的意思是报告方法调用是即发即弃的。我不必等待他们完成...
  • Thread.currentThread() 速度很快,怎么会有问题?特别是它在所谓的非常昂贵的操作之前被调用一次,它不应该被注意到。
  • @irreputable:问题是非常昂贵的操作调用了A 中的其他公共方法数千次。由于这些方法也被其他类使用,我不能直接委托给super - 我必须检查调用是否应该提交给工作线程。

标签: java multithreading performance inheritance


【解决方案1】:

是否有更快的方法来检查源自 超类和/或工作线程以线程安全的方式?

您可以将Thread.currentThread 替换为ThreadLocal。 只需设置例如BooleanThreadLocal 如果设置(在输入方法时检查它),不要产生新线程,而是在同一个线程上执行。

更新
使用 hashmap 来跟踪方法调用。进入方法后,检查它是否存在于 hashmap 中(方法名称作为键)。
如果不存在,则放入 hashmap 并使用后台线程调用该方法。
如果它存在,那么您必须已经在后台线程中。所以在同一个线程中调用

【讨论】:

  • 由于ThreadLocal.get() 调用Thread.currentThread(),与直接比较当前线程相比,为什么这样做会提高性能?
  • @meriton:是吗?我以为是基于线程id的hashmap
  • @Cratylus: 我如何在不调用Thread.currentThread() 的情况下获得线程名称?
  • @thkala:当你第一次创建线程时,你调用A的方法给它一个名字。将该名字存储在hashtable中。你不需要得到名字当前线程。您只需要检查哈希表以查看是否已使用预定义名称创建线程。您可以使用方法名称
  • @Cratylus:很抱歉,我无法理解您的提议。如果我使用方法名称,那不只是检查调用树中是否调用了相同的方法吗?
【解决方案2】:

由于A 不是线程安全的,因此用不同的线程调用它不是……安全的。

想法:

  • 您可能正在池化A 的实例,即,每当一个线程需要一个 A 时,它会从池中获取一个(或者如果池为空,则创建一个),使用它,然后将其放回池中。这样,您将同时拥有所需数量的 A 实例。
  • 您可以修补A 以跨实例缓存“巨大的查找表”(当然,这要求它是线程安全的,但如果仅在构造后读取该表,则情况很简单),并且在每个线程中使用 A 的新实例。

【讨论】:

  • 1.我不需要从多个线程调用A - 我只需要将其处理从主线程推送到单独的线程,基本上使调用异步。
  • 2.我只有 一个 A 的实例 - 我什至不想 需要什么才能拥有一个完整的小 expletive-removed 池 周围...
  • 3.修补A 需要 (a) 访问源代码和 (b) 处理这些混乱的意愿。我也没有。我想我只需要惹恼那些写它的人,希望得到某种改进......或者寻找替代品......
  • 打补丁不需要源代码,因为你可以反汇编类文件(当然,如果它被混淆,生成的代码将很难打补丁......)
  • 它不认为它被混淆了,但我必须同时获得一个适当的反编译源——根据我的经验,即使是 Java 反编译器也不能很好地生成正确的代码——并且 维护 该死的东西。不知何故,这听起来不太有趣:-)
【解决方案3】:

如果您的运行时环境允许使用自定义类加载器,您应该能够将第三方类 A 替换为使用反射调用原始类 A 的自定义委托类。

这是一个委托类 A 的示例:

package fi.test;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ClassA {
    private static final Class<?> clazz;
    private static final Method doSomething;
    private Object instance;

    static {
        ClassLoader classLoader = new ClassLoader(ClassA.class.getClassLoader()) {
            @Override
            public synchronized Class<?> loadClass(String name)
                    throws ClassNotFoundException {
                if (!name.equals(ClassA.class.getName())) {
                    return super.loadClass(name);
                }
                Class<?> clazz = findLoadedClass(name);
                if (clazz == null) {
                    // The original class A is stored as ClassA.class.orig
                    InputStream inputStream = getParent().getResourceAsStream(
                            "fi/test/ClassA.class.orig");
                    try {
                        byte[] buffer = new byte[100000];
                        int length = inputStream.read(buffer);
                        clazz = defineClass(name, buffer, 0, length);
                    } catch (IOException exception) {
                        throw new ClassNotFoundException("", exception);
                    } finally {
                        try {
                            inputStream.close();
                        } catch (IOException exception) {
                        }
                    }
                }
                return clazz;
            }
        };
        try {
            // Class A
            clazz = classLoader.loadClass(ClassA.class.getName());
            // Do something
            doSomething = clazz.getMethod("doSomething", new Class<?>[0]);
        } catch (Exception exception) {
            throw new Error(exception);
        }
    }

    public ClassA() {
        try {
            instance = clazz.newInstance();
        } catch (InstantiationException exception) {
            throw new RuntimeException(exception);
        } catch (IllegalAccessException exception) {
            throw new RuntimeException(exception);
        }

    }

    public void doSomething() {
        try {
            doSomething.invoke(instance, new Object[0]);
        } catch (IllegalAccessException exception) {
            throw new RuntimeException(exception);
        } catch (InvocationTargetException exception) {
            throw new RuntimeException(exception);
        }
    }
}

这是原来的A类:

package fi.test;

public class ClassA {
    public void doSomething() {
        System.out.println("This is magic!");
    }
}

这是我用来测试委托的类:

package fi.test;

public class Tester {
    public static void main(String[] args) {
        ClassA instance = new ClassA();
        instance.doSomething();
    }
}

【讨论】:

    猜你喜欢
    • 2021-11-25
    • 1970-01-01
    • 1970-01-01
    • 2015-05-25
    • 1970-01-01
    • 2011-05-12
    • 2010-11-10
    • 1970-01-01
    • 2014-01-09
    相关资源
    最近更新 更多