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

22次阅读

共计 4597 个字符,预计需要花费 12 分钟才能阅读完成。

简介

在 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/

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

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

正文完
 0