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

简介: 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

相关文章
|
11月前
|
Java
Java开发实现图片URL地址检验,如何编码?
【10月更文挑战第14天】Java开发实现图片URL地址检验,如何编码?
346 4
|
11月前
|
Java
Java实现随机生成某个省某个市的身份证号?如何编码?
【10月更文挑战第18天】Java实现随机生成某个省某个市的身份证号?如何编码?
720 5
|
11月前
|
Java
Java开发实现图片地址检验,如果无法找到资源则使用默认图片,如何编码?
【10月更文挑战第14天】Java开发实现图片地址检验,如果无法找到资源则使用默认图片,如何编码?
219 2
|
7月前
|
人工智能 监控 安全
Java智慧工地(源码):数字化管理提升施工安全与质量
随着科技的发展,智慧工地已成为建筑行业转型升级的重要手段。依托智能感知设备和云物互联技术,智慧工地为工程管理带来了革命性的变革,实现了项目管理的简单化、远程化和智能化。
169 5
|
10月前
|
SQL 安全 Java
Java 异常处理:筑牢程序稳定性的 “安全网”
本文深入探讨Java异常处理,涵盖异常的基础分类、处理机制及最佳实践。从`Error`与`Exception`的区分,到`try-catch-finally`和`throws`的运用,再到自定义异常的设计,全面解析如何有效管理程序中的异常情况,提升代码的健壮性和可维护性。通过实例代码,帮助开发者掌握异常处理技巧,确保程序稳定运行。
159 2
|
11月前
|
存储 缓存 Java
java基础:IO流 理论与代码示例(详解、idea设置统一utf-8编码问题)
这篇文章详细介绍了Java中的IO流,包括字符与字节的概念、编码格式、File类的使用、IO流的分类和原理,以及通过代码示例展示了各种流的应用,如节点流、处理流、缓存流、转换流、对象流和随机访问文件流。同时,还探讨了IDEA中设置项目编码格式的方法,以及如何处理序列化和反序列化问题。
286 1
java基础:IO流 理论与代码示例(详解、idea设置统一utf-8编码问题)
|
10月前
|
SQL 安全 Java
安全问题已经成为软件开发中不可忽视的重要议题。对于使用Java语言开发的应用程序来说,安全性更是至关重要
在当今网络环境下,Java应用的安全性至关重要。本文深入探讨了Java安全编程的最佳实践,包括代码审查、输入验证、输出编码、访问控制和加密技术等,帮助开发者构建安全可靠的应用。通过掌握相关技术和工具,开发者可以有效防范安全威胁,确保应用的安全性。
141 4
|
11月前
|
安全 Java 编译器
Java 泛型深入解析:类型安全与灵活性的平衡
Java 泛型通过参数化类型实现了代码重用和类型安全,提升了代码的可读性和灵活性。本文深入探讨了泛型的基本原理、常见用法及局限性,包括泛型类、方法和接口的使用,以及上界和下界通配符等高级特性。通过理解和运用这些技巧,开发者可以编写更健壮和通用的代码。
208 1
|
12月前
|
安全 Java API
java安全特性
java安全特性
76 9
|
12月前
|
存储 移动开发 Java
java核心之字符串与编码
java核心之字符串与编码
96 2

热门文章

最新文章