线程泄漏

转载自:http://blog.youkuaiyun.com/huoyunshen88/article/details/8618642

       当单线程应用程序中的主线程抛出一个未捕获的异常时,因为控制台中会打印堆栈跟踪(也因为程序停止),所以很可能注意到。但在多线程应用程序中,尤其是在作为服务器运行并且不与控制台相连的应用程序中,线程死亡可能成为不太引人注目的事件,这会导致局部系统失败,从而产生混乱的应用程序行为。
       编写得不正确的线程池会“泄漏”线程,直到最终丢失所有线程。大多数线程池实现通过捕获抛出的异常或重新启动死亡的线程来防止这一点,但线程泄漏的问题并不仅限于线程池,使用线程来为工作队列提供服务的服务器应用程序也可能具有这种问题。当服务器应用程序丢失了一个工作线程(worker thread)时,在较长时间内应用程序仍可能显得一切正常,这使得该问题的真实原因难以确定。
       许多应用程序用线程来提供后台服务:处理来自事件队列的任务、从套接字读取命令或执行UI线程以外的长期任务。当由于抛出未捕获的 RuntimeException 或 Error ,或者只是停下来,等待阻塞的 I/O 操作(原本未预计到阻塞),从而引起这些线程之一死亡时,会发生什么呢?
       有时,譬如当线程执行由用户启动的长期任务(如拼写检查)时,用户会注意到任务没有进展,他们可能会异常终止操作或程序。但其它时间,后台线程执行“清理维护”任务 ,它们可能消失很长时间而不被察觉。

       服务器应用程序中的线程泄漏问题在于不是总是容易从外部检测它。因为大多数线程只处理服务器的部分工作负载,或可能仅处理特定类型的后台任务,所以当程序实际上遭遇严重故障时,在用户看来它仍在正常工作。这一点,再加上引起线程泄漏的因素并不总是留下明显痕迹,就会引起令人惊讶甚或使人迷惑的应用程序行为。
       当线程抛出未捕获的异常或错误时它们可能消失;而当线程等待的 I/O 操作永远不会完成,或没人为它们等待的监视器调用 notify() 时,它们只是停止工作。意外线程死亡的最常见根源是 RuntimeException(如NullPointerException、 ArrayIndexOutOfBoundsException 等)。在多线程应用程序中,由于未查出的异常,线程会无声无息地死亡,使得用户和开发人员对于发生的问题和为什么发生这些问题毫无头绪。
       所以,对于服务方法,我们应该怀疑,它是不是真好到可以假设它从不抛出未查出异常的程度。如果服务例程抛出 RuntimeException ,则调用线程应该捕获这个异常。

示例一中的代码是典型的从工作队列处理 Runnable 任务的线程,handleMessage()方法可能抛出 RuntimeException。
private class TrustingPoolWorker extends Thread {
    public void run() {
        IncomingResponse ir;
        while (true) { 
            ir = (IncomingResponse) queue.getNext(); 
            PlugIn plugIn = findPlugIn(ir.getResponseId()); 
            if (plugIn != null) 
                plugIn.handleMessage(ir.getResponse()); 
            else 
                log("Unknown plug-in for response " + ir.getResponseId());
        } 
    } 
} 
只要通过捕获RuntimeException ,然后进行纠正操作,就可以防止破坏整个服务器。


示例二:

private class SaferPoolWorker extends Thread { 
    public void run() { 
        IncomingResponse ir;
        while (true) { 
            ir = (IncomingResponse) queue.getNext(); 
            PlugIn plugIn = findPlugIn(ir.getResponseId()); 
            if (plugIn != null) { 
                try { 
                    plugIn.handleMessage(ir.getResponse()); 
                } catch (RuntimeException e) { 
                    // Take some sort of action; 
                    // - log the exception and move on 
                    // - log the exception and restart the worker thread 
                    // - log the exception and unload the offending plug-in 
                } 
            } else 
                log("Unknown plug-in for response " + ir.getResponseId());
        } 
    }
} 

    除了将外来代码视作较可能抛出 RuntimeException 的方法之外,使用 ThreadGroup 类的uncaughtException 函数也是明智的。如果线程组中的一个线程因抛出一个未捕获的异常而死亡,则调用该线程组的 uncaughtException() 方法,该方法可以向日志写入一条记录、重新启动线程,然后重新启动系统,或采取它认为必要的任何纠正或诊断操作。至少,如果在线程死亡时所有线程都写一条日志消息,将有一个何时、何处出错的记录,而不是只能奇怪处理线程到哪里去了。
    对于使用线程池时,可以使用  public ThreadFactoryBuilder setUncaughtExceptionHandler( UncaughtExceptionHandler uncaughtExceptionHandler)来实现。

    当线程从应用程序中消失时会引起混乱,并且在很多情况下,线程消失时没有(堆栈)跟踪。象对付许多风险一样,防止线程泄漏的最佳方法是预防和检测相结合;注意有可能抛出RuntimeException的地方(如调用外来代码时),并使用ThreadGroup提供的 uncaughtException 处理程序来在线程异常终止时进行检测。
### Android 平台上线程泄漏的原因 在线程生命周期管理不当的情况下,可能会发生线程泄漏。具体来说: - **长时间运行的线程**:如果某个线程被设计成无限期执行或者其停止条件难以满足,则该线程可能永远不会结束,从而占用不必要的资源[^2]。 - **未正确终止或取消的任务**:当Activity或其他组件销毁时,如果没有显式地中断正在后台工作的线程,这些线程将继续持有对该已销毁上下文的引用,进而引发内存泄漏。 ### 解决方案 针对上述提到的问题,有几种有效的措施来预防和修复线程泄漏现象: #### 使用 `HandlerThread` `HandlerThread` 是一种特殊的线程实现方式,它自带了一个Looper用于处理MessageQueue中的消息。相比普通的Java Thread而言,在适当时候调用quit()方法可以让这个线程优雅退出并释放所占有的资源[^1]。 ```java // 创建一个新的 HandlerThread 实例 HandlerThread handlerThread = new HandlerThread("MyHandlerThread"); handlerThread.start(); // 获取 Looper 对象并与之关联一个 Handler Handler handler = new Handler(handlerThread.getLooper()); // 当不再需要此线程时,记得让其安全退出 handlerThread.quitSafely(); ``` #### 利用 `AsyncTask` 对于短时间内的异步操作,建议采用 `AsyncTask` 来代替手动创建线程。因为 `AsyncTask` 已经内置了良好的生命周期管理和回调机制,能够自动适应UI的变化而不会造成泄漏风险。 ```java new AsyncTask<Void, Void, String>() { @Override protected String doInBackground(Void... params) { // 执行耗时任务... return result; } @Override protected void onPostExecute(String s) { super.onPostExecute(s); // 更新 UI 或者其他后续逻辑 } }.execute(); ``` #### 显式的清理工作 无论何时启动新的线程,都应该确保在不需要它们的时候对其进行妥善处置。这通常意味着要在合适的时机(比如 Activity 的 onDestroy 方法里)调用 stop(), interrupt() 或 join() 等方法来通知线程应该尽快返回,并等待其完成当前的工作后再彻底摧毁自己。 ```java @Override protected void onDestroy() { super.onDestroy(); if (thread != null && thread.isAlive()) { thread.interrupt(); // 尝试打断线程 try { thread.join(500); // 给予一定的时间让它有机会正常结束 } catch (InterruptedException e) { Log.e(TAG, "Failed to properly terminate the worker thread", e); } } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值