深入理解线程池

一、为什么要有线程池?
1.减少频繁创建和销毁线程的开销
2.增加响应速度,需要调用线程时,不需要等待线程的创建
3.方便对线程的统一管理,避免过多的创建线程导致系统负载

二、线程池的创建方式:
可以调用Executors的3个方法创建线程:
1.创建固定大小的线程池:
ExecutorService threadPool = Executors.newFixedThreadPool(3);
2.创建一个线程的线程池:
ExecutorService threadPool = Executors.newSingleThreadExecutor();
3.创建不限线程数上限的线程池
ExecutorService threadPool = Executors.newCachedThreadPool();
上面的三个方法其实都是调用了ThreadPoolExecutor类的构造器来实现的:
例如:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
4.还可以手写一个ThreadPoolExecutor(线程池),注意线程池的7大参数设定
java线程池的完整构造函数:

public ThreadPoolExecutor(int corePoolSize,//线程池长期维持的线程数,即使线程处于idle状态,也不会会回收
                              int maximumPoolSize,//线程数的上限
                              long keepAliveTime,//超过corePoolSize的线程的idle时长,超过这个时间,多余的线程会被回收
                              TimeUnit unit,//
                              BlockingQueue<Runnable> workQueue,//任务的排队队列
                              ThreadFactory threadFactory,//新线程的产生方式
                              RejectedExecutionHandler handler)//拒绝策略
                              {}

三、线程池的七大参数,4大拒绝策略
七大参数:

public ThreadPoolExecutor(int corePoolSize,//线程池长期维持的线程数,即使线程处于idle状态,也不会会回收
                              int maximumPoolSize,//线程数的上限
                              long keepAliveTime,//超过corePoolSize的线程的idle时长,超过这个时间,多余的线程会被回收
                              TimeUnit unit,//
                              BlockingQueue<Runnable> workQueue,//任务的排队队列
                              ThreadFactory threadFactory,//新线程的产生方式
                              RejectedExecutionHandler handler)//拒绝策略
                              {}

注意:
1.corePoolSize和maximumPoolSize设置不当,会影响效率,甚至耗尽线程
2.workQueue设置不当会导致OOM,最好设置有界队列
通过Executors创建线程池采用了默认的任务对列,如下:

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());//无界对列,容易导致OOM
    }
 public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }

3.handler设置不当会导致提交任务的时候抛出异常,最好指定具体的拒绝策略
通过Executors创建线程池采用了默认的拒绝策略,如下:

private static final RejectedExecutionHandler defaultHandler =
        new AbortPolicy();

该拒绝策略会抛出RejectedExecutionException异常。
线程池的工作顺序:corePoolSize->任务队列->maximumPoolSize->拒绝策略

四大拒绝策略:
1.AbortPolicy()【默认】:会抛出RejectedExecutionException异常,该异常为非检测异常,容易忘记捕获,如果不关系任务被拒绝的事情,可以直接设为DiscardPolicy
2.DiscardPolicy():什么也不做,直接丢弃
3.DiscardOldestPolicy():丢弃任务队列中最老的任务,尝试为当前提交的线程腾出位置
4.CallerRunsPolicy():直接由提交任务者执行这个任务,即谁让它过来的谁执行

四、线程池的提交方式:
1.void execute(Runnable command) 没有返回结果
2.Future<?> submit(Runnable task) 有返回结果,future.get()总是为null
3.Future submit(Callable task) 有返回结果
栗子:

/**
 * @Author wuxia
 * @Date 2020/4/13 9:37
 * 测试线程池submit和execute方法区别
 */
public class ExecutorsGetResult {
    public static void main(String[] args) {
        ExecutorService es = Executors.newFixedThreadPool(5);
        FutureTask future = (FutureTask) es.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程"+Thread.currentThread().getName()+"\t执行任务");
            }
        });
        try {
            //get()方法会阻塞,但是get(2,TimeUnit.SECONDS)会在2s抛出异常TimeoutException
            future.get(2, TimeUnit.SECONDS);
            System.out.println("es.submit(new Runnable())有返回值,但是是null:->"+future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("=============================");
        FutureTask future1 = (FutureTask) es.submit(new Callable<Object>() {
            @Override
            public Object call() throws Exception {
                return "2";
            }
        });
        try {
            //get()方法会阻塞,但是get(2,TimeUnit.SECONDS)会在2s抛出异常TimeoutException
            future.get(2, TimeUnit.SECONDS);
            System.out.println("es.submit(new Callable<Object>() )有返回值:->"+future1.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("=============================");
         es.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("execute(new Runnable())只能传Runnable类型");
            }
        });
    }
}

五、获取线程池处理结果和异常:
线程池的处理结果、以及处理过程中的异常都被包装到Future中,通过在调用Future.get()方法获取该异常,因为submit()本身不会传递结果和任务执行过程中的异常。
栗子:

/**
 * 2020-04-11
 * 获取线程池处理结果和异常
 */
public class GetResultAndException {
    public static void main(String[] args) {
        ExecutorService es = Executors.newFixedThreadPool(3);
        Future<Object> future = es.submit(new Callable<Object>() {
            @Override
            public Object call() throws Exception {
                throw new RuntimeException("该异常会在调用future.get()时传递给调用者~~");
            }
        });
        //ctrl+alt+t
        try {
            future.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {//捕获异常
            e.printStackTrace();
        }
    }}

注意
获取单个结果:
future.get()会发生阻塞,可以使用 V get(long timeout, TimeUnit unit)指定等待的超时时间。
获取多个结果:
可以依次调用future.get(),但一般多使用ExecutorCompletionService。其中维护了一个阻塞队列,任务执行完成后就会poll到阻塞队列。所以take()去取的时候,阻塞队列中没有完成的任务,就会发生阻塞。take()取任务执行完的结果是无序的,先执行完的任务会先take()到。
源码:

 public ExecutorCompletionService(Executor executor) {
        if (executor == null)
            throw new NullPointerException();
        this.executor = executor;
        this.aes = (executor instanceof AbstractExecutorService) ?
            (AbstractExecutorService) executor : null;
        this.completionQueue = new LinkedBlockingQueue<Future<V>>();
    }

测试栗子:

 public static void main(String[] args) {
        ExecutorService es =Executors.newFixedThreadPool(5);
        CompletionService<Object> ecs = new ExecutorCompletionService<Object>(es);// 构造器
        for (int i = 0; i < 5; i++) {
            ecs.submit(new Callable<Object>() {
                @Override
                public Object call() throws Exception {
                    return Thread.currentThread().getName()+"->"+ UUID.randomUUID().toString().substring(0,8);
                }
            });
        }
        for (int i = 0; i < 5; ++i) {// 获取每一个完成的任务
            Object r = null;
            try {
                r = ecs.take().get();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
            if (r != null)
                System.out.println("----"+r);
        }

六、使用线程池会带来的问题:
1.使用线程池会带来OOM问题
2.如何配置线程池大小

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值