当线程(我没有开始)结束时,如何通知我?

时间:2014-08-05 19:23:25

标签: java multithreading

我在Jar文件中有一个库,需要跟踪使用我的库的线程数。当新线程进来时没问题:我将它添加到列表中。但我需要在列表死亡时从列表中删除它。

这是一个Jar文件,所以我无法控制何时或多少线程通过。由于我没有启动该线程,我无法强制应用程序(使用我的Jar)调用我的Jar中的方法,该方法说,"此线程正在结束,将其从列表中删除"。我非常真的而不必经常使用Thread.isAlive()来遍历列表中的所有线程。

顺便说一句:这是一些C ++代码的端口,它驻留在DLL中,可以轻松处理DLL_THREAD_DETACH消息。我喜欢Java中类似的东西。

编辑:

保留线程列表的原因是:出于业务原因,我们需要限制使用我们库的线程数。当一个线程进入我们的库时,我们检查它是否在列表中。如果没有,则添加它。如果它在列表中,我们将检索一些特定于线程的数据。当线程死亡时,我们需要将其从列表中删除。理想情况下,我希望在它死亡时收到通知,以便我可以将其从列表中删除。我可以将数据存储在ThreadLocal中,但这仍然无法帮助我获知线程何时死亡的通知。

EDIT2: 原始的第一句话是:"我在Jar文件中有一个库,需要跟踪使用库中对象的线程。"

3 个答案:

答案 0 :(得分:2)

通常你会让GC清理资源。您可以向线程添加一个组件,当该线程不再可访问时,该组件将被清理。

如果您使用自定义ThreadGroup,则会在从组中删除线程时通知我。如果使用组中的线程启动JAR,它也将成为组的一部分。您还可以更改线程组,以便通过反射进行通知。

但是,每隔几秒轮询一次线程可能会更简单。

答案 1 :(得分:2)

您可以使用ThreadLocal和WeakReference的组合。创建某种"票"对象以及当线程进入库时,创建一个新票证并将其放入ThreadLocal中。此外,为故障单实例创建一个WeakReference(带有ReferenceQueue)并将其放在库中的列表中。当线程退出时,票证将被垃圾收集并且您的WeakReference将排队。通过轮询ReferenceQueue,您基本上可以获得"事件"指示线程何时退出。

答案 2 :(得分:1)

根据您的修改,当线程死亡时,您真正的问题是跟踪,而是限制对您的库的访问。哪个好,因为没有可移植的方式来跟踪线程何时死亡(当然在Java API中没有办法)。

我会使用被动技术来解决这个问题,而不是尝试生成和响应事件的主动技术。您说您在进入库时已经创建了线程本地数据,这意味着您已经拥有了执行被动检查的分界点。我将实现一个类似于以下的ThreadManager类(您可以轻松地使方法/变量保持静态):

public class MyThreadLocalData {
    // ...
}

public class TooManyThreadsException
extends RuntimeException {
    // ...
}

public class ThreadManager
{
    private final static int MAX_SIZE = 10;

    private ConcurrentHashMap<Thread,MyThreadLocalData> threadTable = new ConcurrentHashMap<Thread,ThreadManager.MyThreadLocalData>();
    private Object tableLock = new Object();

    public MyThreadLocalData getThreadLocalData() {
        MyThreadLocalData data = threadTable.get(Thread.currentThread());
        if (data != null) return data;

        synchronized (tableLock) {
            if (threadTable.size() >= MAX_SIZE) {
                doCleanup();
            }            

            if (threadTable.size() >= MAX_SIZE) {
                throw new TooManyThreadsException();
            }

            data = createThreadLocalData();
            threadTable.put(Thread.currentThread(), data);
            return data;
        }
    }

线程本地数据保存在threadTable中。这是一个ConcurrentHashMap,这意味着它提供了快速的并发读取,以及并发迭代(这在下面很重要)。在快乐的情况下,线程已经在这里,所以我们只返回它的线程本地数据。

在新线程调用库的情况下,我们需要创建其线程本地数据。如果线程少于限制,则会快速进行:我们创建数据,将其存储在地图中,然后将其返回(createThreadLocalData()可以替换为new,但我倾向于喜欢工厂像这样的代码中的方法。)

令人遗憾的是,当新线程进入时,表格已经达到最大值。因为我们无法知道线程何时完成,所以我选择将死线程留在表中,直到我们需要空间 - 就像JVM和内存管理一样。如果我们需要空间,我们执行doCleanup()来清除死线程(垃圾)。如果在我们清除了死线程之后仍然没有足够的空间,我们会抛弃(我们也可以实现等待,但这会增加复杂性,对于库来说通常是一个坏主意。)

同步非常重要。如果我们同时有两个新线程通过,我们需要阻塞一个而另一个尝试添加到表中。关键部分必须包括检查的完整,可选择清理和添加新项目。如果您未将整个操作设为原子,则可能会超出限制。但请注意,初始get() 需要位于原子区域,因此我们不需要同步整个方法。

好的,转到doCleanup():这只是迭代地图并查找不再存在的线程。如果找到一个,它会调用析构函数(&#34;反工厂&#34;)作为其线程本地数据:

private void doCleanup() {
    for (Thread thread : threadTable.keySet()) {
        if (! thread.isAlive()) {
            MyThreadLocalData data = threadTable.remove(thread);
            if (data != null) {
                destroyThreadLocalData(data);
            }
        }
    }

}

即使从同步块中调用此函数,它也可以被编写,就好像可以同时调用它一样。 ConcurrentHashMap的一个很好的特性是它产生的任何迭代器可以同时使用,并在调用时提供地图视图。但是,这意味着两个线程可能会检查相同的映射条目,并且我们不想两次调用析构函数。所以我们使用remove()来获取条目,如果它为null,我们就知道它已被(/正在)另一个线程清理过。

事实证明,您可能希望同时调用该方法。就个人而言,我认为&#34;必要时进行清理&#34;方法是最简单的,但是如果不使用它,那么你的线程本地数据可能会很昂贵。如果是这种情况,请创建一个Timer,重复调用doCleanup()

public Timer scheduleCleanup(long interval) {
    TimerTask task = new TimerTask() {
        @Override
        public void run() {
            doCleanup();
        }
    };
    Timer timer = new Timer(getClass().getName(), true);
    timer.scheduleAtFixedRate(task, 0L, interval);
    return timer;
}