Java中多线程详解(3)线程池

一、什么是线程池
线程池可以理解为一个帮助你管理线程的工具,借助线程池可以自动完成对线程的创建和调度,线程池里的线程可以反复利用,避免了线程资源浪费和创建线程所需要的时间。
二、线程池的运行流程
在这里插入图片描述

线程池的运行流程如上图所示,可以看到线程池的几个关键的参数,核心线程池数、等待队列数、最大线程数、饱和策略,这些参数就构成了线程池的运行流程,也是创建线程池所需的必要参数,不理解这些参数的含义不要紧,可以先往下学习自定义创建线程池的方法后再来理解。
二、创建线程池的方法
1.自动创建
在Executors工具类中,为我们准备了四大常用的线程,方便我们使用,分别是1. newSingleThreadExecutor(单线程线程池),2.newFixedThreadPool(固定大小线程池),3.newCachedThreadPool(动态大小线程池),4.newScheduledThreadPool(周期任务线程池)
由于自动创建的线程池都多多少少都有一些缺点,如果场景不合适就会导致内存溢出的问题,所以我们重点讲一下手动创建线程池。
2.手动创建
可以通过ThreadPoolExecutor来手动创建一个线程池。
手动创建线程池需要自定义参数
(1)corePoolSize(核心线程数):这个数量代表了该线程池中可以同时运行的线程数量,最优情况是CPU数量+1,如果超过了这个线程数就会到任务队列中等待。
(2)runnableTaskQueue(任务队列):任务队列可以理解为一个阻塞队列,在线程数超过核心线程数后,多出来的线程就会被阻塞在这个队列中,任务队列有以下几种
(2.1)LinkedBlockingQueue队列:基于链表的队列,队列效果是先进先出;
(2.2)ArrayBlockingQueue队列:基于数组的队列,也是先进先出;
(2.3)SynchronousQueue:和synchronized同步锁方法相似的一个队列,队列中的线程必须被另个线程取出后才会进行下一个线程,否则会被阻塞,是线程中的一对一操作;
(2.4)PriorityBlockingQueue:这是一个大小无限制的阻塞队列,容易引发内存溢出。
(3)maximumPoolSize(最大线程数):如果队列满了,并且已创建的线程数小与maximumPoolSize,多出来的线程就会放到这里,线程池会创建新的线程来执行任务。要注意的是如果队列是无界的比如PriorityBlockingQueue队列,maximumPoolSize就没用了。(4)ThreadFactory(线程工厂):可以为线程自定义名称,方便查看。
(5)RejectedExecutionHandler(拒绝策略):当队列和线程池都满了,就不能再创建新的线程了,所以就需要一种拒绝创建线程的策略。拒绝策略有以下几种
(5.1)AbortPolicy:该策略是线程池的默认策略。使用该策略时,如果线程池队列满了丢掉这个任务并且抛出RejectedExecutionException异常。
(5.2)CallerRunsPolicy:如果线程池饱和了,用这个策略会自己去执行该任务,不会等待线程池中的线程去执行。
(5.3)DiscardPolicy:这个策略会丢弃多余的线程,并且不会报错。
(5.4)DiscardOldestPolicy:这个策略如果发现线程池慢了,就会丢弃最先接入队列的那个线程,然后让新线程进入。
拒绝策略支持自定义,需要继承RejectedExecutionHandler接口。
(6)keepAliveTime(线程超时时间):当线程空闲后的存活时间,如果线程中的任务很快就能完成,就可以适当的修改,保证其他线程的运行。
(7)TimeUnit(线程超时时间单位):天(DAYS),小时(HOURS),分钟(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒),毫微秒(NANOSECONDS, 千分之一微秒)。
下面是手动创建线程池的一个例子。

public class ThreadPoolTest {
    static ThreadPoolExecutor threadPoolExecutor= new ThreadPoolExecutor(
                5,//核心线程数,就是可以同时运行的线程
                10,//最大线程数
                60,//线程超时时间
                TimeUnit.SECONDS,//超时时间的参数,例子中代表60秒
                new LinkedBlockingDeque<>(50),//最大线程队列
            new ThreadFactoryBuilder().setNameFormat("ThreadPoolTest-%d").build(),//线程工厂,在这里用来给线程命名
            new ThreadPoolExecutor.AbortPolicy()//拒绝策略
        );

三、线程池的使用实例
下面的实例中,创建了一个继承Runable的线程方法和一个继承Callable的线程方法,区别是Callable有返回值,然后用之前创建出的线程池对象的execute方法和submit方法来执行这些线程,可以看到submit方法的返回类型是Future的,所以如果有返回值就用submit方法执行,没有返回值的话用execute方法速度快些。
使用完线程池要停止时,可以用shutdown方法,这个方法会等所有线程运行完后停止,包括队列中等待的线程任务,或者使用shutdownNow方法,将会立即停止线程池。

public class ThreadPoolTest {public static void main(String[] args) throws ExecutionException, InterruptedException {
        RunableTest runableTest=new RunableTest();
        CallableTest1 callableTest1=new CallableTest1();
        threadPoolExecutor.execute(runableTest);
        threadPoolExecutor.execute(runableTest);
        Future<String> f= threadPoolExecutor.submit(callableTest1);
        System.out.println(f.get());
        threadPoolExecutor.shutdown();//停止线程池    }
}
class RunableTest implements Runnable{
    @Override
    public void run() {
        for (int i=0;i<3;i++){
            System.out.println("线程名称"+Thread.currentThread().getName()+"-"+i);
        }
    }
}
class CallableTest1 implements Callable<String>{
    @Override
    public String call() throws Exception {
        System.out.println("Callable线程");
        return "submit获取返回结果";
    }
}
//运行结果
线程名称ThreadPoolTest-0-0
线程名称ThreadPoolTest-0-1
线程名称ThreadPoolTest-0-2
线程名称ThreadPoolTest-1-0
Callable线程
线程名称ThreadPoolTest-1-1
线程名称ThreadPoolTest-1-2
submit获取返回结果
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值