4种常见的线程池创建方式

为什么使用线程池(线程池的优点):

  1. 线程复用:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗;
  2. 提高响应速度:当任务到达时,不需要等待线程的创建就可以立即执行;
  3. 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

线程池的实现:

  1. 通过Executors.newFixedThreadPool(int value) 方法来创建固定个数的线程池,value为池中线程个数。可以控制线程最大并发数,超出的线程会在队列中等待。实现代码如下:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolDemo_2 {
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newFixedThreadPool(4); // 一池四线程


        // 模拟10个用户来办理业务,每个用户就是一个来自外部的请求线程
        try {
            for (int i = 0; i < 10; i++) {
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"\t 来办理业务");
                });
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            // 关闭线程池,不关闭的话,程序不会停止,一直处于Waiting状态
            threadPool.shutdown();
        }
	}
}

结果如下:

pool-1-thread-2	 来办理业务
pool-1-thread-4	 来办理业务
pool-1-thread-3	 来办理业务
pool-1-thread-1	 来办理业务
pool-1-thread-4	 来办理业务
pool-1-thread-3	 来办理业务
pool-1-thread-4	 来办理业务
pool-1-thread-2	 来办理业务
pool-1-thread-3	 来办理业务
pool-1-thread-1	 来办理业务

通过结果可以看出,线程池中一共有四个线程在运行执行请求,也就是最大并发数为4.

查看源码可以发现,这种方式创建的线程池是通过new 一个ThreadPoolExecutor对象实现的,而且corePoolSize和maximumPoolSize值是相等的,也就是说,线程池中常驻线程数和最大线程数的相等的。阻塞队列使用的是LinkedBlockingQueue,队列长度为Integer.MAX_VALUE,即2147483647。源码如下 :

	public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
  1. 通过Executors.newSingleThreadExecutor()方法实现线程池,顾名思义,这是创建一个只有一个线程的线程池,所有的请求都由这一个线程处理,实现代码如下。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolDemo_2 {
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newSingleThreadExecutor(); // 一池一线程

        // 模拟10个用户来办理业务,每个用户就是一个来自外部的请求线程
        try {
            for (int i = 0; i < 10; i++) {
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"\t 来办理业务");
                });
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            // 关闭线程池,不关闭的话,程序不会停止,一直处于Waiting状态
            threadPool.shutdown();
        }
	}
}

运行结果:

pool-3-thread-1	 来办理业务
pool-3-thread-1	 来办理业务
pool-3-thread-1	 来办理业务
pool-3-thread-1	 来办理业务
pool-3-thread-1	 来办理业务
pool-3-thread-1	 来办理业务
pool-3-thread-1	 来办理业务
pool-3-thread-1	 来办理业务
pool-3-thread-1	 来办理业务
pool-3-thread-1	 来办理业务

查看方法的底层源码可以发现这种方式创建的线程池同样是new一个ThreadPoolExecutor对象实现的,而且corePoolSize和maximumPoolSize值是相等的,都是1。阻塞队列使用的是LinkedBlockingQueue,队列长度为Integer.MAX_VALUE,即2147483647。源码如下:

   public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
  1. 通过Executors.newCachedThreadPool()方法创建一个可缓存线程池,如果线程长度超过处理需要,可灵活回收空闲线程,若无空闲线程且请求较多,则创建新的线程。实现方法如下:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolDemo_2 {
    public static void main(String[] args) {
 
        ExecutorService threadPool = Executors.newCachedThreadPool(); // 一池N线程
        
        // 模拟10个用户来办理业务,每个用户就是一个来自外部的请求线程
        try {
            for (int i = 0; i < 10; i++) {
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"\t 来办理业务");
                });
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            // 关闭线程池,不关闭的话,程序不会停止,一直处于Waiting状态
            threadPool.shutdown();
        }
	}
}

运行结果如下,可以看出,创建了7个线程来处理业务,每次运行创建的线程数可能不相同。

pool-2-thread-2	 来办理业务
pool-2-thread-4	 来办理业务
pool-2-thread-3	 来办理业务
pool-2-thread-5	 来办理业务
pool-2-thread-1	 来办理业务
pool-2-thread-6	 来办理业务
pool-2-thread-7	 来办理业务
pool-2-thread-1	 来办理业务
pool-2-thread-7	 来办理业务
pool-2-thread-6	 来办理业务

查看方法的底层源码可以发现这种方式创建的线程池同样是new一个ThreadPoolExecutor对象实现的,但是和前两种方式不同的是corePoolSize为0,maximumPoolSize值是Integer.MAX_VALUE,阻塞队列使用的是SynchronousQueue。源码如下

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
  1. 看过前三种创建线程池的方法,它们有一个共同点就是底层使用new ThreadPoolExecutor创建线程池的,那么第四种创建方式就是我们自己调用这个方法创建,灵活的配置参数来满足业务的需求,这也是工作中最常用的创建线程池的方式。

    当然,这样做的前提是,要理解这个方法的七个参数的意义,可以看我的另一篇文章。
    首先,我们来看看怎么创建线程池:

import org.xml.sax.helpers.DefaultHandler;

import java.util.concurrent.*;

public class ThreadPoolDemo_2 {
    public static void main(String[] args) {
    	// 只写前五个参数也可以
        ExecutorService threadPool = new ThreadPoolExecutor(
                        2, // 常驻核心线程数
                        10, // 最大线程数
                        2L, // 空闲线程活跃时间
                        TimeUnit.SECONDS, // 空闲线程活跃时间单位
                        new LinkedBlockingQueue<Runnable>(5), // 阻塞队列
                        Executors.defaultThreadFactory() , // 默认线程工厂
                        new ThreadPoolExecutor.AbortPolicy() // 阻塞队列的拒绝策略
                        );
        // 模拟10个用户来办理业务,每个用户就是一个来自外部的请求 线程
        try {
            for (int i = 0; i < 10; i++) {
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"\t 来办理业务");
                });
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            // 关闭线程池,不关闭的话,程序不会停止,一直处于Waiting状态
            threadPool.shutdown();
        }
	}
}
运行结果如下:
pool-4-thread-1	 来办理业务
pool-4-thread-4	 来办理业务
pool-4-thread-3	 来办理业务
pool-4-thread-4	 来办理业务
pool-4-thread-2	 来办理业务
pool-4-thread-1	 来办理业务
pool-4-thread-3	 来办理业务
pool-4-thread-5	 来办理业务
pool-4-thread-4	 来办理业务
pool-4-thread-2	 来办理业务

当然,使用这种方式要合理的配置这七个参数,不然容易出现运行异常,或者是线程请求丢失的情况,如下图所示。

pool-4-thread-2	 来办理业务
pool-4-thread-6	 来办理业务
pool-4-thread-2	 来办理业务
pool-4-thread-5	 来办理业务
pool-4-thread-7	 来办理业务
pool-4-thread-4	 来办理业务
pool-4-thread-3	 来办理业务
pool-4-thread-1	 来办理业务
java.util.concurrent.RejectedExecutionException: Task Demo.ThreadPoolDemo_2$$Lambda$1/589446616@4cdbe50f rejected from java.util.concurrent.ThreadPoolExecutor@7e0babb1[Running, pool size = 7, active threads = 0, queued tasks = 0, completed tasks = 8]
	at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
	at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
	at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
	at Demo.ThreadPoolDemo_2.main(ThreadPoolDemo_2.java:28)

为什么使用前面三种不会出现上面的问题呢?
是因为newFixedThreadPool和newSingleThreadExecutor的阻塞队列使用的是默认容量,大约是21亿的长度,正常情况下,不可能出现这么大数量的请求,而newCachedThreadPool虽然使用的是同步队列,但是他的最大线程数是21亿左右,线程数是足够的。而自定义参数的话可能定义了固定长度的阻塞队列和固定的最大线程数,当请求足够多的情况下,就会出现异常或者是丢失请求的情况,当然这取决于线程池采用的拒绝策略。

如何合理的配置线程池参数
取决于业务需求:

  • CPU密集型:需要大量的运算,而没有阻塞,配置尽可能少的线程数量, 一般为(CPU核数+1)个线程的线程池。
  • IO密集型:并不是一直在执行任务,而是在执行类似硬盘读取这种操作的话,则应配置尽可能多的线程数,如CPU核数*2。
// 查看计算机核数
System.out.println(Runtime.getRuntime().availableProcessors());

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值