自带的四种线程池:
Java通过Executors提供四种线程池,分别为
- newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
- newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
- newScheduledThreadPool 创建一个可定期或者延时执行任务的定长线程池,支持定时及周期性任务执行。
- newCachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
自定义线程池ThreadPoolExecutor
四种线程池本质都是创建ThreadPoolExecutor类,ThreadPoolExecutor构造参数如下
- int corePoolSize, 核心线程大小
- int maximumPoolSize,最大线程池大小,最大值Integer.MAX_VALUE
- long keepAliveTime, 超过corePoolSize的线程多久不活动被销毁时间
- TimeUnit unit,时间单位
- BlockingQueue<Runnable> workQueue 任务队列
- ThreadFactory threadFactory 线程池工厂
- RejectedExecutionHandler handler 拒绝策略
任务队列
- ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列
- LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列(常用)
- PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列
- DelayQueue: 一个使用优先级队列实现的无界阻塞队列
- SynchronousQueue: 一个不存储元素的阻塞队列(常用)
- LinkedTransferQueue: 一个由链表结构组成的无界阻塞队列
- LinkedBlockingDeque: 一个由链表结构组成的双向阻塞队列
各个线程池的参数对比
线程池和默认参数
线程池 | corePoolSize | maximumPoolSize | keepAliveTime | unit | workQueue |
newSingleThreadExecutor | 1 | 1 | 0 | 无 |
|
newFixedThreadPool | 必填 | 跟corePoolSize一致 | 0 | 无 |
|
newScheduledThreadPool | 必填 | Integer.MAX_VALUE | 0 | 无 |
|
newCachedThreadPool | 0 | Integer.MAX_VALUE |
|
|
|
代码测试案例:
公共代码,线程类:
package thread;
import java.util.Date;
// 线程测试类
public class MyRunnable implements Runnable{
private int i ;
public MyRunnable(int i){
this.i = i;
}
@Override
public void run() {
System.out.println(" 第"+i+"次-线程名称:" + Thread.currentThread().getName() +" 时间:"+ new Date());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
1.newSingleThreadExecutor
测试代码:
package thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 单线程线程池 - 只有一个线程执行程序,执行完一次再执行一次
*/
public class TestSingleThreadExecutor {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newSingleThreadExecutor();
for(int i=0;i<10 ;i++){
MyRunnable r = new MyRunnable(i);
threadPool.execute(r);
}
}
}
运行结果:
:
总结:都是一个线程在执行
2.newFixedThreadPool
测试代码:
package thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 定长线程池
*/
public class TestFixedThreadPool {
public static void main(String[] args) {
/**
* 固定长度的线程池,循环使用线程,线程下标1~3 然后再到1~3,循环往复
*/
ExecutorService threadPool = Executors.newFixedThreadPool(3);
for(int i=0;i<10 ;i++){
MyRunnable r = new MyRunnable(i);
threadPool.execute(r);
}
}
}
运行结果:
总结:定长线程池,循环使用线程,没有空闲的则需要等待,运行完空闲了也不会销毁线程
3.newScheduledThreadPool
测试代码:
package thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* 可定期或者延期执行的线程池
*/
public class TestScheduledThreadPool {
public static void main(String[] args) {
// 需要用 ScheduledExecutorService 接口接收,好调用内部的方法
ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(3);
// 普通任务执行
for(int i=0; i<10 ; i++){
MyRunnable r = new MyRunnable(i);
threadPool.execute(r);
try {
Thread.sleep(1000);//睡眠1s
} catch (InterruptedException e) {
e.printStackTrace();
}
}
MyRunnable r = new MyRunnable(100);
System.out.println("定时线程begin");
// 3秒后再执行任务。第一参数是任务,第二个是时间数值,第三个是指定时间单位的枚举,这里指定是秒
threadPool.schedule(r,3, TimeUnit.SECONDS);//
System.out.println("定时线程end");
MyRunnable r2 = new MyRunnable(200);
System.out.println("循环线程begin");
// 5秒后再执行任务,然后每3秒执行一次。第一参数是任务,第二个是延迟时间值,第三个是每隔多久执行一次时间值,第四个是指定时间单位的枚举,这里指定是秒
threadPool.scheduleAtFixedRate(r2,5,3,TimeUnit.SECONDS);
System.out.println("循环线程end");
}
}
运行结果:
总结:可以指定时间和时间周期运行,创建需要传入corePoolSize参数,如果线程没用空闲还是会等待有空闲再调用。试着传入corePoolSize为0,只有一个线程处理任务
4.newCachedThreadPool
测试代码:
package thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 缓存线程池
*/
public class TestCachedThreadPool {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newCachedThreadPool();
for(int i=0;i<10 ;i++){
MyRunnable r = new MyRunnable(i);
threadPool.execute(r);
}
}
}
运行结果:
总结:默认corePoolSize为0,
5.自定义线程池ThreadPoolExecutor
测试代码:
package thread;
import java.util.Date;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
// 自定义连接池
public class TestThreadThreadPoolExecutor {
public static void main(String[] args) {
BlockingQueue<Runnable> queue = new LinkedBlockingQueue (1);
/**
* 如果队列容量为 1 ,线程池最大容量maximumPoolSize为3,要执行的线程超过4(1+3=4)。
* queue + maximumPoolSize
*/
Executor executor = new ThreadPoolExecutor(2, 3, 60, TimeUnit.SECONDS, queue);
for(int i=0; i<20; i++){
MyRunnable r = new MyRunnable(i);
executor.execute(r);
}
}
}
运行结果:
总结:为什么会报异常呢,因为要执行的线程为20,大于queue + maximumPoolSize 20>1+3。 所以会报异常。把线程池maximumPoolSize或者queue改大一些就可以了。这个总数一般要预估的,一般是两倍足够了。上面4种都有缺陷,听说阿里这些大厂不用原生的,要自己写。原生的看都有这一些缺陷,我们自己写细节还是要注意一下了
maximumPoolSize 改为20,执行没报错,结果如下。
备注:
- 一般如果线程池任务队列采用LinkedBlockingQueue队列的话,那么不会拒绝任何任务(因为其大小为Integer.MAX_VALUE),这种情况下,ThreadPoolExecutor最多仅会按照最小线程数corePoolSize来创建线程,也就是说线程池大小被忽略了。
- 如果线程池任务队列采用ArrayBlockingQueue队列,初始化设置了最大队列数。那么ThreadPoolExecutor的maximumPoolSize才会生效,那么ThreadPoolExecutor的maximumPoolSize才会生效会采用新的算法处理任务,
- 例如假定线程池的最小线程数为4,最大为8,ArrayBlockingQueue最大为10。随着任务到达并被放到队列中,线程池中最多运行4个线程(即核心线程数)直到队列完全填满,也就是说等待状态的任务小于等于10,ThreadPoolExecutor也只会利用4个核心线程线程处理任务。
- 如果队列已满,而又有新任务进来,此时才会启动一个新线程,这里不会因为队列已满而拒接该任务,相反会启动一个新线程。新线程会运行队列中的第一个任务,为新来的任务腾出空间。如果线程数已经等于最大线程数,任务队列也已经满了,则线程池会拒绝这个任务,默认拒绝策略是抛出异常。
- 这个算法的理念是:该池大部分时间仅使用核心线程(4个),即使有适量的任务在队列中等待运行。这时线程池就可以用作节流阀。如果挤压的请求变得非常多,这时该池就会尝试运行更多的线程来清理;这时第二个节流阀—最大线程数就起作用了。
- CPU密集型和IO密集型: CPU密集型也叫计算密集型,指的是系统的硬盘、内存性能相对CPU要好很多,此时,系统运作大部分的状况是CPU Loading 100%;IO密集型指的是系统的CPU性能相对硬盘、内存要好很多,此时,系统运作,大部分的状况是CPU在等I/O (硬盘/内存) 的读/写操作,此时CPU Loading并不高。创建线程池需要考虑到这两个服务器条件,如果cpu密集型,需要考虑是否需要少量核心进程,队列大一些;Io密集型是否考虑核心进程数设置大一些,减少队列大小等等