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/
最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!
欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!
java安全编码指南之:ThreadPool的使用的更多相关文章
- java安全编码指南之:基础篇
目录 简介 java平台本身的安全性 安全第一,不要写聪明的代码 在代码设计之初就考虑安全性 避免重复的代码 限制权限 构建可信边界 封装 写文档 简介 作为一个程序员,只是写出好用的代码是不够的,我 ...
- java安全编码指南之:Mutability可变性
目录 简介 可变对象和不可变对象 创建mutable对象的拷贝 为mutable类创建copy方法 不要相信equals 不要直接暴露可修改的属性 public static fields应该被置位f ...
- java安全编码指南之:字符串和编码
目录 简介 使用变长编码的不完全字符来创建字符串 char不能表示所有的Unicode 注意Locale的使用 文件读写中的编码格式 不要将非字符数据编码为字符串 简介 字符串是我们日常编码过程中使用 ...
- java安全编码指南之:输入校验
目录 简介 在字符串标准化之后进行校验 注意不可信字符串的格式化 小心使用Runtime.exec() 正则表达式的匹配 简介 为了保证java程序的安全,任何外部用户的输入我们都认为是可能有恶意攻击 ...
- java安全编码指南之:声明和初始化
目录 简介 初始化顺序 循环初始化 不要使用java标准库中的类名作为自己的类名 不要在增强的for语句中修改变量值 简介 在java对象和字段的初始化过程中会遇到哪些安全性问题呢?一起来看看吧. 初 ...
- java安全编码指南之:Number操作
目录 简介 Number的范围 区分位运算和算数运算 注意不要使用0作为除数 兼容C++的无符号整数类型 NAN和INFINITY 不要使用float或者double作为循环的计数器 BigDecim ...
- java安全编码指南之:堆污染Heap pollution
目录 简介 产生堆污染的例子 更通用的例子 可变参数 简介 什么是堆污染呢?堆污染是指当参数化类型变量引用的对象不是该参数化类型的对象时而发生的. 我们知道在JDK5中,引入了泛型的概念,我们可以在创 ...
- java安全编码指南之:可见性和原子性
目录 简介 不可变对象的可见性 保证共享变量的复合操作的原子性 保证多个Atomic原子类操作的原子性 保证方法调用链的原子性 读写64bits的值 简介 java类中会定义很多变量,有类变量也有实例 ...
- java安全编码指南之:异常处理
目录 简介 异常简介 不要忽略checked exceptions 不要在异常中暴露敏感信息 在处理捕获的异常时,需要恢复对象的初始状态 不要手动完成finally block 不要捕获NullPoi ...
随机推荐
- Mysql 多表连查 xml写法 非注解形式
1.xml写法 <!-- 联查用户users表 --> <resultMap type="nanh.entity.Tasks" id="selectTa ...
- NX二次开发-NX访问SqlServer数据库(增删改查)C#版
版本:NX9+VS2012+SqlServer2008r2 以前我写过一个NX访问MySQL数据库(增删改查)的文章https://www.cnblogs.com/nxopen2018/p/12297 ...
- java8--排序
排序的传统的写法是: Collections.sort( SortTest.users, new Comparator<User>() { @Override public int com ...
- OSPF及实验
OSPF:Open Shortest Path First,最短路径优先1)基本概念:标准的LS型协议--共享拓扑组播更新:224.0.0.5/6触发更新,存在周期更新 30minOSPF是跨层封装 ...
- MySQL-Atlas--读写分离架构
一.Atlas简介 Atlas是由 Qihoo 360公司Web平台部基础架构团队开发维护的一个基于MySQL协议的数据中间层项目.它在MySQL官方推出的MySQL-Proxy 0.8.2版本的基础 ...
- 跟我一起学.NetCore之Swagger让前后端不再烦恼及界面自定义
前言 随着前后端分离开发模式的流行,接口对接.联调成为常事,前端同事会经常问:我需要调哪个接口?这个接口数据格式是啥?条件都传啥? 对于一些紧急接口可能会采取沟通对接,然后补文档,其他的都会回一句:看 ...
- 创建Vue项目及封装axios
1. 始vue化项目 https://www.cnblogs.com/xiaonq/p/11027880.html vue init webpack deaxios # 使用脚手架创建项目 deaxi ...
- CS:APP配套实验 Data Lab
刚刚完成注册博客,想写一篇随笔,方便以后自己回顾.如果恰好也能帮助到你,是我的荣幸. 这次随笔是记载我的计算机系统(CS:APP,Computer Systems:A Programer's Pers ...
- P3545 [POI2012]HUR-Warehouse Store
题目描述 n天.第i天上午会进货Ai件商品,中午的时候会有顾客需要购买Bi件商品,可以选择满足顾客的要求,或是无视掉他. 如果要满足顾客的需求,就必须要有足够的库存.问最多能够满足多少个顾客的需求. ...
- 序列化的JavaScript
下载 序列化的JavaScript序列化的JavaScript 将JavaScript序列化为包含正则表达式.日期和函数的JSON超集. 概述 这个包中的代码最初是作为表示状态的内部模块.为了扩展它的 ...