关于java:java安全编码指南之ThreadPool的使用

简介

在java中,除了单个应用Thread之外,咱们还会应用到ThreadPool来构建线程池,那么在应用线程池的过程中须要留神哪些事件呢?

一起来看看吧。

java自带的线程池

java提供了一个十分好用的工具类Executors,通过Executors咱们能够十分不便的创立出一系列的线程池:

Executors.newCachedThreadPool,依据须要能够创立新线程的线程池。线程池中已经创立的线程,在实现某个工作后兴许会被用来实现另外一项工作。

Executors.newFixedThreadPool(int nThreads) ,创立一个可重用固定线程数的线程池。这个线程池里最多蕴含nThread个线程。

Executors.newSingleThreadExecutor() ,创立一个应用单个 worker 线程的 Executor。即便工作再多,也只用1个线程实现工作。

Executors.newSingleThreadScheduledExecutor() ,创立一个单线程执行程序,它可安顿在给定提早后运行命令或者定期执行。

提交给线程池的线程要是能够被中断的

ExecutorService线程池提供了两个很不便的进行线程池中线程的办法,他们是shutdown和shutdownNow。

shutdown不会承受新的工作,然而会期待现有工作执行结束。而shutdownNow会尝试立马终止现有运行的线程。

那么它是怎么实现的呢?咱们看一个ThreadPoolExecutor中的一个实现:

    public List<Runnable> shutdownNow() {
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(STOP);
            interruptWorkers();
            tasks = drainQueue();
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
        return tasks;
    }

外面有一个interruptWorkers()办法的调用,实际上就是去中断以后运行的线程。

所以咱们能够失去一个论断,提交到ExecutorService中的工作肯定要是能够被中断的,否则shutdownNow办法将会生效。

先看一个谬误的应用例子:

    public void wrongSubmit(){
        Runnable runnable= ()->{
            try(SocketChannel  sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8080))) {
            ByteBuffer buf = ByteBuffer.allocate(1024);
            while(true){
                sc.read(buf);
            }
            } catch (IOException e) {
                e.printStackTrace();
            }
        };
        ExecutorService pool =  Executors.newFixedThreadPool(10);
        pool.submit(runnable);
        pool.shutdownNow();
    }

在这个例子中,运行的代码无奈解决中断,所以将会始终运行。

上面看下正确的写法:

    public void correctSubmit(){
        Runnable runnable= ()->{
            try(SocketChannel  sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8080))) {
                ByteBuffer buf = ByteBuffer.allocate(1024);
                while(!Thread.interrupted()){
                    sc.read(buf);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        };
        ExecutorService pool =  Executors.newFixedThreadPool(10);
        pool.submit(runnable);
        pool.shutdownNow();
    }

咱们须要在while循环中加上中断的判断,从而控制程序的执行。

正确处理线程池中线程的异样

如果在线程池中的线程产生了异样,比方RuntimeException,咱们怎么才可能捕捉到呢? 如果不可能对异样进行正当的解决,那么将会产生不可意料的问题。

看上面的例子:

    public void wrongSubmit() throws InterruptedException {
        ExecutorService pool = Executors.newFixedThreadPool(10);
        Runnable runnable= ()->{
            throw new NullPointerException();
        };
        pool.execute(runnable);
        Thread.sleep(5000);
        System.out.println("finished!");
    }

下面的例子中,咱们submit了一个工作,在工作中会抛出一个NullPointerException,因为是非checked异样,所以不须要显式捕捉,在工作运行结束之后,咱们基本上是不可能得悉工作是否运行胜利了。

那么,怎么才可能捕捉这样的线程池异样呢?这里介绍大家几个办法。

第一种办法就是继承ThreadPoolExecutor,重写

 protected void afterExecute(Runnable r, Throwable t) { }

protected void terminated() { }

这两个办法。

其中afterExecute会在工作执行结束之后被调用,Throwable t中保留的是可能呈现的运行时异样和Error。咱们能够依据须要进行解决。

而terminated是在线程池中所有的工作都被调用结束之后才被调用的。咱们能够在其中做一些资源的清理工作。

第二种办法就是应用UncaughtExceptionHandler。

Thread类中提供了一个setUncaughtExceptionHandler办法,用来解决捕捉的异样,咱们能够在创立Thread的时候,为其增加一个UncaughtExceptionHandler就能够了。

然而ExecutorService执行的是一个个的Runnable,怎么应用ExecutorService来提交Thread呢?

别怕, Executors在构建线程池的时候,还能够让咱们传入ThreadFactory,从而构建自定义的Thread。

    public void useExceptionHandler() throws InterruptedException {
        ThreadFactory factory =
                new ExceptionThreadFactory(new MyExceptionHandler());
        ExecutorService pool =
                Executors.newFixedThreadPool(10, factory);
        Runnable runnable= ()->{
            throw new NullPointerException();
        };
        pool.execute(runnable);
        Thread.sleep(5000);
        System.out.println("finished!");
    }

    public static class ExceptionThreadFactory implements ThreadFactory {
        private static final ThreadFactory defaultFactory =
                Executors.defaultThreadFactory();
        private final Thread.UncaughtExceptionHandler handler;

        public ExceptionThreadFactory(
                Thread.UncaughtExceptionHandler handler)
        {
            this.handler = handler;
        }

        @Override
        public Thread newThread(Runnable run) {
            Thread thread = defaultFactory.newThread(run);
            thread.setUncaughtExceptionHandler(handler);
            return thread;
        }
    }

    public static class MyExceptionHandler implements Thread.UncaughtExceptionHandler {
        @Override
        public void uncaughtException(Thread t, Throwable e) {

        }
    }

下面的例子有点简单了, 有没有更简略点的做法呢?

有的。ExecutorService除了execute来提交工作之外,还能够应用submit来提交工作。不同之处是submit会返回一个Future来保留执行的后果。

    public void useFuture() throws InterruptedException {
        ExecutorService pool = Executors.newFixedThreadPool(10);
        Runnable runnable= ()->{
            throw new NullPointerException();
        };
        Future future = pool.submit(runnable);
        try {
            future.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        Thread.sleep(5000);
        System.out.println("finished!");
    }

当咱们在调用future.get()来获取后果的时候,异样也会被封装到ExecutionException,咱们能够间接获取到。

线程池中应用ThreadLocal肯定要留神清理

咱们晓得ThreadLocal是Thread中的本地变量,如果咱们在线程的运行过程中用到了ThreadLocal,那么当线程被回收之后再次执行其余的工作的时候就会读取到之前被设置的变量,从而产生未知的问题。

正确的应用办法就是在线程每次执行完工作之后,都去调用一下ThreadLocal的remove操作。

或者在自定义ThreadPoolExecutor中,重写beforeExecute(Thread t, Runnable r)办法,在其中退出ThreadLocal的remove操作。

本文的代码:

learn-java-base-9-to-20/tree/master/security

本文已收录于 http://www.flydean.com/java-security-code-line-threadpool/

最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!

欢送关注我的公众号:「程序那些事」,懂技术,更懂你!

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理