阿里巴巴规范说过,使用线程最好是用线程池,那就是说使用线程池有一定的好处,能够管理线程连接,开启用户使用的线程数,用完回归池中,可以让其他线程使用,减少连接线程的资源消耗。
那么Java中有提供ThreadPoolExecutor线程池的类实现,Java也对其封装了Executors的四种静态使用方法,先来讲一下四种线程池的使用。
1.newFixedThreadPool
fixed的意思就是固定, 见名知意就是创建一个固定容量的线程池,用户传要创建几个线程,那么所有的任务都由这几个线程来工作。代码示例如下:
这个代码模拟了开3个线程处理30个任务,看一下输出结果
public static void FixedThreadPoolDemo() throws InterruptedException {
//创建一个定长线程池
//定长线程池的特点:固定线程总数,空闲线程用于执行任务,如果线程都在使用的话,后续任务处于等待状态
//在线程池中的线程执行任务后在执行后续的任务
//如果任务处于等待的状态,备选的等待算法默认为(FIFO(先进先出))也可以设置LIFO(后进先出)
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 1; i <= 30; i++) {
final int index = i;
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("操作的线程:"+Thread.currentThread().getName()+" 索引:"+index);
//list.add(Thread.currentThread().getName().split("-")[3]);
}
});
}
Thread.sleep(1000);
// OptionalInt max = list.stream().mapToInt(str -> Integer.parseInt(str.toString())).max();
//shutdown()代表关闭线程池(等待所有线程完成)
//shutdownNow()代表立即终止线程池的运行,不等待线程,不推荐使用
executorService.shutdown();
public static void main(String[] args) throws InterruptedException {
FixedThreadPool.FixedThreadPoolDemo();
}
可以看出线程使用一直都是1,2,3个线程,不会在额外开辟线程
点进去看newFixedThreadPool方法,内部使用的ThreadPoolExecutor类创建线程池,讲一下参数把。
第1个参数:核心线程数,传的是3,这里nThreads就是那个3,
第2个参数:最大线程数也设置的是nThreads,代表我们所开的线程最大的也就是3个,
第3个参数:keepAliveTime最大线程数空闲存活时间,这里的0L相当于空闲了立即销毁最大线程数,但是我们的核心线程数也是3,所以不存在销毁
第4个参数:基于第3个参数,是它的单位
第5个参数:队列,用于等待线程处理的任务队列存储。这里用的LinkedBlockingQueue,阻塞的无界队列
2.newCachedThreadPool
还是见名知意可缓存线程池,用户不传要开多少个线程,根据任务进行分配线程,线程数量可能会很多,代码示例如下:
模拟200个任务进行操作,看看会开多少个线程
public static void cacheThreadDemo() throws InterruptedException {
//创建一个可缓存线程池
//ExecutorService调度器对象,用于管理线程池
//Executors.newCachedThreadPool()创建一个可缓存线程池
//可缓存线程池的特点:无限大,如果线程中没有可用的则创建,有空闲线程则利用起来
ExecutorService executorService = Executors.newCachedThreadPool();
// List list = new ArrayList();
int[] strs = new int[400];
for (int i = 1; i <= 200; i++) {
final int index = i;
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("操作的线程:"+Thread.currentThread().getName() + " 索引:" + index);
//list.add(Thread.currentThread().getName().split("-")[3]);
strs[index] = Integer.parseInt(Thread.currentThread().getName().split("-")[3]);
}
});
}
Thread.sleep(1000);
int max = Arrays.stream(strs).max().getAsInt();
System.out.println("开辟空间最大的线程池名称是:" + max);
executorService.shutdown();
}
public static void main(String[] args) throws InterruptedException {
CachedThreadPool.cacheThreadDemo();
}
有开辟的线程,有重用的线程,本次示例开了79个。
内部调用ThreadPoolExecutor,核心线程数是0,最大线程无止尽,直到内存用完,最大线程池空闲存活时间60秒,等待的队列是使用SynchronousQueue,SynchronousQueue是一个内部只能包含一个元素的队列,插入元素到队列的线程被阻塞,直到另一个线程从队列中获取了队列中存储的元素
3.newSingleThreadExecutor
见名知意,是单例,只会创建一个线程处理任务。代码示例如下:
public static void singleThreadDemo() throws InterruptedException {
//创建一个单线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 1; i <= 6; i++) {
final int index = i;
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ":" + index);
//list.add(Thread.currentThread().getName().split("-")[3]);
}
});
}
Thread.sleep(1000);
executorService.shutdown();
}
public static void main(String[] args) throws InterruptedException {
singlePool.singleThreadDemo();
}
结果如下所示:只会创造一个线程来处理任务。
实现是核心线程数设置为1,最大线程池数设置为1,线程池存活时间是空闲立即销毁,队列是阻塞无界队列
4.newScheduledThreadPool
名字里有任务调度,一般java是定期执行一些事情,那么这里也是,延迟定期执行事务,示例代码如下:
public static void scheduledPoolDemo() {
ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(5);
// 延迟1秒执行,每3秒执行一次
scheduledPool.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("操作线程:" + Thread.currentThread().getName() + " " + new Date() + "延迟1秒执行,每3秒执行一次");
}
}, 1, 3, TimeUnit.SECONDS);
}
public static void main(String[] args) {
scheduledPoolDemo();
}
执行结果如下:
咱们的核心线程数是5个,这里最大线程数是无数个,0秒最大线程空闲,队列采用延迟队列方式。
ThreadPoolExecutor讲解
那么这4大线程池介绍完毕了,在介绍下ThreadPoolExecutor,阿里巴巴规范说创建线程池最好用ThreadPoolExecutor,而不要用上述说的4个,原因是自己实现的可以设置队列为不是无界队列,也能控制核心线程数和最大核心线程数,根据自己的业务场景选择合适的参数。下面来讲讲7大参数,
1.corePoolSize:核心线程数,当有任务进来时还未达到核心线程数,则创建核心线程
1.1 当线程数没有达到核心线程数最大值的时候,新任务会继续创建线程,不会复用线程池的线程
1.2 核心线程一般不会销毁,空闲也不会销毁,除非通过方法allowCoreThreadTimeOut(boolean value)设置为true时,超时也 同样会被销毁
1.3 生产环境首次初始化的时候,可以调用prestartCoreThread(),prestartAllCoreThread()来预先创建所有的和核心线程,避免 第一次调用缓慢。
2.maximumPoolSize:最大线程数,线程池中能够容纳(包含核心线程数)同时执行的最大线程数,此知大于等于1
3.keepAliveTime:最大线程池中线程数量超过核心线程数时,多余的空闲线程存活的时间,当空闲时间达到keepAliveTime时,多余线程就会被(最大线程池的线程)销毁,当keepAliveTime设置为0时,表明最大线程池空闲立即销毁。
4.unit:keepAliveTime的单位
5.workQueue:等待执行的任务队列,核心线程满了,新的任务就会在加入等待队列中。
6.threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程,一般默认即可。
7.handler:拒绝策略,没有空闲的线程处理任务,并且等待队列已满,再有新任务进来如何来拒绝请求的runnable的策略。
拒绝策略还蛮重要的,记住这四个不同的拒绝策略:
AbortPolicy:直接抛出RejectedExecutionException异常阻止系统正常运行,默认使用的异常。
DiscardPolicy:默默丢弃无法处理的任务,不予任何处理也不抛出异常。
DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务。
CallerRunsPolicy:"调用者运行"一种调节机制,该策略既不会抛弃任务,也不会抛出异常,二是将某些任务回退到调用者,从而降低新任务流量。
自定义的线程池代码示例demo
public static void main(String[] args) {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2,
5,
2L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3), //不写的话默认也是Integer.MAX_VALUE
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());//默认的拒绝策略
try {
for (int i = 0; i <8; i++) {
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + "\t" + "办理业务");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown(); //关闭线程池
}
}
结果:
线程池执行流程
1.提交任务时,检查核心线程池是否已满,没有满则创建线程,核心线程池满了走2
2.检查队列是否已满,没有则把任务放入队列中,队列满了走3
3.检查最大线程池数是否已满,没有则继续创建线程执行任务,满了走4
4.执行拒绝策略
以上就是线程池的全部内容,希望可以帮到你!下节抽空会讲下队列哦!