zoukankan      html  css  js  c++  java
  • 线程池踩坑

    线程池可以把线程复用起来,减少线程创建销毁的时间和资源消耗,提高了程序任务执行的吞吐率。就像线程属于全局使用的资源一样,线程池一般也是全局性,对整个应用进程的线程复用做有效的管理。设计者一般都会把线程池作为类的静态成员或者单例成员,存活于整个进程的生命周期。
    但是还是例外地看到了类似这样的代码,比如放到了方法体中作为局部变量:

    private static void sampleFunc() {
        ExecutorService executor = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 100; i++) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    ...
                }
            });
        }
    }

    这些线程池的使用看起来挺正常的,隐藏着一个很严重的问题:
    当对象实例不再使用或者方法执行完毕后,什么时候会释放线程 ,关闭线程池?
    不同的线程池表现不一样。主要看是否设置了核心线程数。

    • 如果没有设置核心线程数,比如 newCachedThreadPool ,在线程池的线程空闲时间到达 60s 后,线程会关闭,所有线程关闭后线程池也相应关闭回收。
    • 如果设置了核心线程数,比如 newSingleThreadExecutor 和 newFixedThreadPool ,如果没有主动去关闭,或者设置核心线程的超时时间,核心线程会一直存在不会被关闭,这个线程池就不会被释放回收。

    验证如下:

        public static void main(String[] args) {
            while (true) {
                try {
                    ExecutorService service = Executors.newFixedThreadPool(1);
                    service.submit(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                Thread.sleep(2000); ////模拟处理业务
                            } catch (InterruptedException e) {
                            }
                        }
                    });
                    service = null;
                } catch (Exception e) {
                }
                try {
                    Thread.sleep(2000);
                    System.gc();
                } catch (InterruptedException e) {
                }
            }
        }

    运行后,查看jvm,会发现线程每2秒就增长一个。如下图所示:

    加了shutdown代码后:

        public static void main(String[] args) {
            while (true) {
                ExecutorService service = Executors.newFixedThreadPool(1);
                try {
                    service.submit(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                Thread.sleep(2000);
                            } catch (InterruptedException e) {
                            }
                        }
                    });
                } catch (Exception e) {
                }finally{
                    service.shutdown();
                }
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                }
            }
        }

    就一直很平稳,运行结果如下图所示:

    剖析
    为什么会有这样的现象?线程池无法被回收,是因为线程池的引用被它的内部类 Worker 持有了。而 Worker 和线程一一对应,是对 Thread 的增强,所以本质上就是因为线程没有被释放。那么任务队列已经空了,并且外界也没有任务过来,线程为什么还没有被释放?看 ThreadPoolExecutor 的 runWorker方法:

        final void runWorker(Worker w) {
            Thread wt = Thread.currentThread();
            Runnable task = w.firstTask;
            w.firstTask = null;
            w.unlock(); // allow interrupts
            boolean completedAbruptly = true;
            try {
                while (task != null || (task = getTask()) != null) {
                    w.lock();
                    // If pool is stopping, ensure thread is interrupted;
                    // if not, ensure thread is not interrupted.  This
                    // requires a recheck in second case to deal with
                    // shutdownNow race while clearing interrupt
                    if ((runStateAtLeast(ctl.get(), STOP) ||
                         (Thread.interrupted() &&
                          runStateAtLeast(ctl.get(), STOP))) &&
                        !wt.isInterrupted())
                        wt.interrupt();
                    try {
                        beforeExecute(wt, task);
                        Throwable thrown = null;
                        try {
                            task.run();
                        } catch (RuntimeException x) {
                            thrown = x; throw x;
                        } catch (Error x) {
                            thrown = x; throw x;
                        } catch (Throwable x) {
                            thrown = x; throw new Error(x);
                        } finally {
                            afterExecute(task, thrown);
                        }
                    } finally {
                        task = null;
                        w.completedTasks++;
                        w.unlock();
                    }
                }
                completedAbruptly = false;
            } finally {
                processWorkerExit(w, completedAbruptly);
            }
        }

    我们看到要执行线程退出 processWorkerExit 需要这几种情况:

    • 线程池的状态 >= STOP
    • getTask 获取到空任务

    第一个条件,线程池的状态要达到 STOP,需要调用 shutdown 或者 shutdownNow 方法,我们不满足。
    第二个条件,getTask 获取到空任务,继续看 getTask 的代码:

        private Runnable getTask() {
            boolean timedOut = false; // Did the last poll() time out?
    
            for (;;) {
                int c = ctl.get();
                int rs = runStateOf(c);
    
                // Check if queue empty only if necessary.
                if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                    decrementWorkerCount();
                    return null;
                }
    
                int wc = workerCountOf(c);
    
                // Are workers subject to culling?
                boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
    
                if ((wc > maximumPoolSize || (timed && timedOut))
                    && (wc > 1 || workQueue.isEmpty())) {
                    if (compareAndDecrementWorkerCount(c))
                        return null;
                    continue;
                }
    
                try {
                    Runnable r = timed ?
                        workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                        workQueue.take();
                    if (r != null)
                        return r;
                    timedOut = true;
                } catch (InterruptedException retry) {
                    timedOut = false;
                }
            }
        }

    任务队列使用的是阻塞队列 BlockingQueue,该队列提供了两种方法来获取任务:

    • poll,可以设置超时时间,当超时后会得到一个空任务。
    • take,阻塞住,直到有任务出现。

    从上面的 getTask 方法中我们可以看到:

    • 当前线程数大于核心线程,会调用 poll,超时后返回空任务。
    • 当前线程数小于等于核心线程,并且调用了 allowCoreThreadTimeOut 方法允许核心线程超时关闭的情况下,也是调用 poll,超时后返回空任务。
    • 其他情况,调用 take 阻塞等待。

    我们上面使用单个核心线程的线程池,在没有任务的情况下,核心线程正处于 getTask ,调用阻塞队列 BlockingQueue 的 take 方法阻塞等待获取到任务,从而导致线程池包括里面的核心线程迟迟不被关闭并且回收。

    小结
    像上面那样去设置线程池,可以理解为线程池的局部应用。
    不推荐用这样的方式,因为局部线程池能做到的事情,全局线程池也可以做到。而且全局单例的线程池还可以不用考虑关闭线程池的问题,毕竟生命周期和进程一致。
    如果业务场景非要这样用的话,并且线程池有核心线程的情况下,要注意做两件事情防止对象泄漏:

    • 对核心线程设置超时时间。
    • 主动调用 shutdown 或 shutdownNow 来关闭线程池。

    参考博客:
    https://cloud.tencent.com/developer/article/1497832
    https://blog.csdn.net/Qgwperfect/article/details/82849228

    郭慕荣博客园
  • 相关阅读:
    python模块--time模块
    python模块--如何相互调用自己写的模块
    Animating Views Using Scenes and Transitions
    fragment 切换
    android textview 设置text 字体
    android intent 5.1
    android EditView ime
    animation of android (4)
    animation of android (3)
    animation of android (2)
  • 原文地址:https://www.cnblogs.com/jelly12345/p/14960581.html
Copyright © 2011-2022 走看看