线程是非常宝贵的计算资源,创建和销毁线程花费大量的时间和资源
线程池就是为了实现线程复用
线程池工作流程(工作原理):
总结:先是用核心线程去执行任务,核心线程满了就去队列中等(如果核心线程空闲了,就回会去执行阻塞队列中的任务),队列也满了就创建非核心线程去执行任务,达到最大线程数,再来任务就拒绝
核心线程-----阻塞队列-------非核心线程
任务进来之后,先检查一下核心线程是否全部在执行任务(如果不是全部在执行任务,就用核心线程去执行任务)
如果核心线程全部已经在执行任务了,就将任务放到任务队列当中去
如果任务队列也满了,就检查一下正在执行任务的线程是否达到最大线程数,如果没有达到最大线程数,就创建非核心线程执行任务
如果任务队列也满了,正在执行任务的线程数又达到了最大线程数,就会采用饱和策略进行任务拒绝(四种拒绝策略)
没有任务时,线程池里的核心线程处于空闲状态
当请求到来:线程池给这个请求分配一个空闲的核心线程,任务完成后回到线程池中等待下次任务(而不是销毁)
创建线程池的几种方式:主要有两种方式Executors,ThreadPoolExecutor
ExecutorService executorService1 = Executors.newFixedThreadPool(10);
ExecutorService executorService2 = new ThreadPoolExecutor(3,5,1L,TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(3), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
通过ThreadPoolExecutor的方式需要设定7个参数:
线程池七大参数:
1.corePoolSize: 核心线程数的大小(核心线程永远不会被销毁)
2.maximumPoolSize:线程池最大线程数
3.keepAliveTime:生存时间,空闲时间(非核心线程如果超过生存时间没有执行任务,就会被销毁)
4.TimeUnit.SECONDS:生存时间的单位,可以是秒也可以是小时.......
5.ArraryBlockQueue:任务队列(核心)
6.Executors.defaultThreadFactory:线程工厂,用来创建线程
7.拒绝策略
主要有四个拒绝策略:
(1)AbortPolicy:线程池的默认策略,拒绝并且抛出RejectedExecutionException异常
(2)CallerRunsPolicy:虽然线程池自己拒绝了这个任务,但是主线程会去执行这个任务
(3)DiscardPolicy:直接丢掉这个任务,不会有任何异常
(4)DiscardOldestPolicy:如果队列满了,将最早进入队列的任务删掉腾出空间,再将新任务加进队列
也可以自己自定义拒绝策略
阿里巴巴java开发手册规定:线程池不允许使用Executors去创建,而是需要通过ThreadPoolExecutor来创建,因为通过设置七个参数可以让同学们更加明确线程池的运行规则
这里总共来了100个任务,3个核心线程,最大线程数为5
ExecutorService executorService=new ThreadPoolExecutor(3,5,1L,TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(3), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
for(int i=0;i<100;i++)
{
executorService.execute(()->
{
System.out.println( Thread.currentThread().getName()+" 正在办理业务");
} );
}
executorService.shutdown();
可以看到最多只有五个线程在工作
//step 1:创建大小为10的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(10);
for(int i=0 ; i<10; i++)
{
//step 2:提交多个线程任务并执行
threadPool.execute(new Runnable()
{
@Override
public void run()
{
System.out.println(Thread.currentThread().getName() + " is running");
}
}
);
}
输出:
pool-1-thread-1 is running
pool-1-thread-2 is running
pool-1-thread-3 is running
pool-1-thread-4 is running
pool-1-thread-5 is running
pool-1-thread-6 is running
pool-1-thread-9 is running
pool-1-thread-7 is running
pool-1-thread-10 is runningpool-1-thread-8 is running
如何定义线程池的大小:
1.使用经验值来定义线程池大小
线程池可以分为两种类型:
(1)I/O密集型(大部分进行I/O操作,CPU占用率不高,例如mysql的读写,文件的读写,网络通信,这些操作不会消耗太多的cpu资源,但是I/O操作特别耗时,会占用比较多的时间,此时CPU相对比较空间,为了更好的利用cpu,不让cpu空闲下来,所以应该加大线程数量,建议线程池大小=2N+1,其中N为CPU的个数)
(2)CPU密集型(也叫计算任务密集型,几乎没有I/O操作,CPU使用时间远高于I/O时间,也就是计算的时候要执行很多逻辑判断,因此频繁的切换上下文是不明智的,所以设置一个较小的线程数,建议线程池大小设为N+1)
通过下面的方法可以拿到当前机器的cpu核数:
Runtime.getRuntime().availableProcessors();//8
2.使用公式来定义线程池大小
最佳线程数=cpu数目* (线程等待时间+线程cpu时间)/线程cpu时间
所以线程等待时间占比越高,就需要越多线程
虽然这个公式比较准确,但是实际中线程等待时间不好测量,所以公式法用的少,还是用经验值方法用的多
先创建一个线程池,然后用这个线程池去执行线程,每execute()一次就是执行一次线程
public class testCountDownLatch {
public static void main(String[] args) throws InterruptedException {
ExecutorService threadPool= Executors.newFixedThreadPool(5);//创建大小为5的线程池
threadPool.execute( ()->{
System.out.println("执行线程1");
});
threadPool.execute( ()->{
System.out.println("执行线程2");
});
threadPool.execute( ()->{
System.out.println("执行线程3");
});
threadPool.execute( ()->{
System.out.println("执行线程4");
});
threadPool.execute( ()->{
System.out.println("执行线程5");
});
threadPool.execute( ()->{
System.out.println("执行线程6");
});
threadPool.shutdown();//销毁线程池
}
}