线程池初步学习
文章地址:https://blog.csdn.net/renhuan28/article/details/85322844
为什么使用线程池?
在我们的日常开发中,难免会使用到线程,部分还会用到多线程并发问题。我们知道,线程的创建和释放,需要占用不小的内存和资源。如果每次需要使用线程时,都new 一个Thread的话,难免会造成资源的浪费,而且可以无限制创建,之间相互竞争,会导致过多占用系统资源导致系统瘫痪。不利于扩展,比如如定时执行、定期执行、线程中断,所以很有必要了解下ExecutorService的使用。
Java通过Executors提供四种线程池,分别为:
//一个定长线程池,支持定时及周期性任务执行
ExecutorService executorServiceScheduled = Executors.newScheduledThreadPool(1);
//一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
ExecutorService executorServiceCached = Executors.newCachedThreadPool();
//一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
ExecutorService executorServiceSingle = Executors.newSingleThreadExecutor();
//一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
ExecutorService executorServiceFixed = Executors.newFixedThreadPool(5);
常见参数:
corePoolSize : 核心线程数,一旦创建将不会再释放。如果创建的线程数还没有达到指定的核心线程数量,将会继续创建新的核心线程,直到达到最大核心线程数后,核心线程数将不在增加;如果没有空闲的核心线程,同时又未达到最大线程数,则将继续创建非核心线程;如果核心线程数等于最大线程数,则当核心线程都处于激活状态时,任务将被挂起,等待空闲线程来执行。
maximumPoolSize : 最大线程数,允许创建的最大线程数量。如果最大线程数等于核心线程数,则无法创建非核心线程;如果非核心线程处于空闲时,超过设置的空闲时间,则将被回收,释放占用的资源。
keepAliveTime : 也就是当线程空闲时,所允许保存的最大时间,超过这个时间,线程将被释放销毁,但只针对于非核心线程。
unit : 时间单位,TimeUnit.SECONDS等。
workQueue : 任务队列,存储暂时无法执行的任务,等待空闲线程来执行任务。
threadFactory : 线程工程,用于创建线程。
线程池的执行:
有两种方式 submit 和 execute ,这两者的区别是:
1 接收的参数不一样;execute()的入参为Runnable ,submit ()的入参为Runnable 或Callable 。
(补充说明:Callable与Runnable类似,也是创建线程的一种方式,实现其call()方法即可,方法可以有返回值,而且方法上可以抛出异常;)
2 submit()有返回值,返回值为 Future ,而execute()没有;
3 submit()可以进行Exception处理; 通过对Future.get()进行抛出异常的捕获
以 FixedThreadPool 为例写个demo
public static void main(String[] args) {
List<Future<String>> futures = Lists.newArrayList();
int num = Runtime.getRuntime().availableProcessors();
//一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
ExecutorService executorServiceFixed = Executors.newFixedThreadPool(num);
//遍历启动线程池里的线程,执行用submit,入参用Callable
for (int i = 0; i < num; i++) {
Future<String> submit = executorServiceFixed.submit(new UserinfosTaskByCall(i));
futures.add(submit);
}
//打印返回值
for (Future future : futures) {
try {
System.out.println(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} finally {
executorServiceFixed.shutdown();
}
}
while (true) { }
}
入参Callable 任务
public class UserinfosTaskByCall implements Callable {
int b = 0;
public UserinfosTaskByCall(int i) {
b = i;
}
@Override
public Object call() throws Exception {
System.out.println("开始干活了,+++++++++++++" + Thread.currentThread().getName());
try {
Thread.sleep(5000);
if (b == 2) {
throw new InterruptedException("yichang");
}
} catch (InterruptedException e) {
return "有异常";
}
return "结束啦";
}
}
对ExecutorService的封装 CompletionService
CompletionService整合了Executor和BlockingQueue的功能。
ExecutorService要等所有submit对象执行完,才会返回get值;
CompletionService只要notEmpty,就会返回get值.(阻塞针对的是尾部full会阻塞,头部empty会阻塞),CompletionService的实现是维护一个保存Future对象的BlockingQueue。只有当这个Future对象状态是结束的时候,才会加入到这个Queue中,take()方法其实就是Producer-Consumer中的Consumer。它会从Queue中取出Future对象,如果Queue是空的,就会阻塞在那里,直到有完成的Future对象加入到Queue中。所以,先完成的必定先被取出。这样就减少了不必要的等待时间
CompletionService接口定义了一组任务管理接口:
submit() - 提交任务
take() - 获取任务结果
poll() - 获取任务结果
方法poll()的作用是获取并移除最新已完成的任务的Future,如果不存在这样的任务,则返回null,方法poll()不像take()方法那样具有阻塞的效果,立即调用立即返回结果。
BlockingQueue的核心方法
放入数据:
offer(anObject):表示如果可能的话,将anObject加到BlockingQueue里,即如果BlockingQueue可以容纳,则返回true,否则返回false.(本方法不阻塞当前执行方法的线程)
offer(E o, long timeout, TimeUnit unit),可以设定等待的时间,如果在指定的时间内,还不能往队列中加入BlockingQueue,则返回失败。
put(anObject):把anObject加到BlockingQueue里,如果BlockQueue没有空间,则调用此方法的线程被阻断直到BlockingQueue里面有空间再继续.
获取数据:
poll(time):取走BlockingQueue里排在首位的对象,若不能立即取出,则可以等time参数规定的时间,取不到时返回null;
poll(long timeout, TimeUnit unit):从BlockingQueue取出一个队首的对象,如果在指定时间内,队列一旦有数据可取,则立即返回队列中的数据。否则知道时间超时还没有数据可取,返回失败。
take():取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到BlockingQueue有新的数据被加入;
drainTo():一次性从BlockingQueue获取所有可用的数据对象(还可以指定获取数据的个数), 通过该方法,可以提升获取数据效率;不需要多次分批加锁或释放锁。