线程池自我介绍我是一个线程池(ThreadPoolExecutor),我的主要工作是管理我里面的多个线程(Thread),让它们可以同时执行多个任务,它不会造成大量的系统开销。有些人不明白,创建线程的开销是多少?不就是新建一个Thread让它运行吗?这里我简单说明一下:其实Java中的线程模型是基于操作系统原生的线程模型实现的,也就是说Java中的线程实际上是基于内核线程实现的。线程的创建、销毁、同步都需要系统调用,系统调用需要在用户态和内核之间来回穿梭。切换相对昂贵。线程的生命周期包括“线程创建时间”、“线程执行时间”和“线程销毁时间”。创建和销毁都需要引起系统调用。每个Thread都需要一个内核线程来支持,也就是说每个Thread都需要消耗一定的内核资源(比如内核线程的栈空间),因为可以创建的Thread是有限的,一个线程的线程栈线程默认大小为1M。如果每次来一个任务就创建一个线程,1024个任务光是创建一个线程就占用1G内存,系统很容易崩溃。corePoolSize所以我的主要作用是减少线程的创建时间和销毁时间。线程创建后,并没有立即销毁,而是常驻于此,随叫随到。我称这些常驻线程为核心线程。线程数不能太多,所以我指定了它们的个数(corePoolSize),假设是3。“线程池,这是我的任务之一,请帮我执行一下”,主线程抛出后立即返回任务交给我,于是我赶紧调用execute方法处理抛给我的任务(Runnable)publicinterfaceExecutor{voidexecute(Runnablecommand);}由于我从出生到现在都没有执行过任务,核心线程一直为0,所以在这个方法中我创建了一个线程作为核心线程。“线程池,任务又来了,请帮我执行”,任务又来了!于是又调用了execute,又创建了一个核心线程,此时核心线程数为2。过了一段时间,第一个核心线程执行完任务,空闲了,这时候任务又来了。..“线程池,这是我的任务之一,请帮我执行一下。”说完下一句主线就离开了。此时,一个核心线程忙,一个核心线程空闲。很多人可能会误认为,既然有一个核心线程是空闲的,就把任务交给这个线程就行了,不需要创建核心线程,但其实只要当前核心线程的个数小于原来设置的corePoolSize,不管当前核心线程是否空闲,我还是会创建另一个核心线程,主要是为了保证核心线程数量尽快达到我们设置的数量,这样如果后面有很多任务进来,这些创建的核心线程可以马上准备好处理这些任务,不需要再进行创建线程的耗时操作。经过上面的操作,核心线程的数量已经达到了初始设定的数量3。我们设置的数量为3,当然可以再创建一个线程,但是会造成另外一个系统调用的开销比较大。事实上,核心线程可能会在短时间后立即空闲。不如把任务放到一个队列里,让这些核心线程自己去取。聪明的你一定发现了,这是一个典型的生产者消费者模型。线程池中的线程只需要不断循环到workQueue队列中获取任务即可。为了避免workQueue为空而一直轮询导致的CPU资源占用问题,这里的workQueue使用了阻塞队列。所谓阻塞就是如果workQueue为空,获取元素的线程会等待队列变为非空。一旦有新的任务进入队列,就会唤醒等待的线程。画外音:线程等待是指调用LockSupport.park,使线程从运行状态变为阻塞状态。此时线程不会占用CPU资源,但好景不长。JVM老大给我反馈说有OOM问题。当我看到它时,我明白了这个问题。嗯,原来是一个菜鸟程序员在创建我的时候,声明使用了无界队列,导致核心线程无法及时处理任务,不断的将任务添加到workQueue(也就是生产任务的速度远大于消费任务的速度),导致workQueue越来越大,最终OOM!解决的办法很简单,就是用一个有界队列,这样当workQueue满了的时候,就不能再添加任务,也不会导致workQueue无限增加导致OOM。画外音:所谓有界队列,是指有固定大小的队列。当队列中的元素超过这个大小时,任务就不能再塞进这个队列了。由于无界队列没有固定大小,可以直接入队,直到溢出,很容易造成OOM,所以在创建线程池的时候,应该尽量使用有界队列maximumPoolSize。任务来了,核心线程还是处理不了,很快workQueue又满了。这时我想起了另一个参数maximumPoolSize。该参数定义了我可以创建的最大线程数。当其他线程要排队任务,但是发现workQueue已满时,由于我这边当前线程还没有达到maximumPoolSize(假设原来指定为5),我又创建了一个线程来处理这个任务.画外音:当workQueue满时,如果当前线程池的线程数>=corePoolSize且<=maximumPoolSize,如果其他线程不断丢任务,线程会一直创建到maximumPoolSize为止。RejectedExecutionHandler有一天,一个给我丢任务的线程报了异常。看到workQueue满了,线程数达到了maximumPoolSize。但是workQueue中仍然有任务插入,但是这样的话,已经超出了我的处理能力,所以我只好实现默认的拒绝策略,抛出RejectedExecutionException异常让其他线程(向我抛任务的线程)处理它自己。画外音:线程池提供了AbortPolicy、DiscardPolicy、DiscardOldestPolicy、CallerRunsPolicy,并自定义了这五种拒绝策略。默认值为AbortPolicykeepAliveTime。在线程的努力下,workQueue队列中的任务很快被清空,长时间没有任务进来,线程很快就会无事可做,又会重新占用资源。我该如何处理?此时我有核心线程3(corePoolSize=3),额外线程2(maximumPoolSize为5),我是这样的,如果当前线程总数超过corePoolSize,在keepAliveTime时间内,如果线程池中一直空闲的线程会被杀死,空闲时间先达到keepAliveTime的线程会被杀死,直到线程数减少到corePoolSize。画外音:线程池中没有核心线程和额外线程之分。只是为了故事方便人为划分,但实际上线程池中的线程都是平等的,任何一个线程都可以杀掉。本文转载自微信♂“码海”,可通过以下二维码关注。转载本文请联系码海公众号。
