Java线程池

一、简述

为避免频繁地创建和销毁线程,达到线程对象的重用,因而使用线程池。另外,使用线程池还可以根据项目灵活地控制并发的数目。

1️⃣线程池的概念
线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙。如果线程池所有线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值。超过最大值的线程可以排队,但要等到其他线程完成后才启动。

2️⃣线程池的工作机制
在线程池的编程模式下,任务是提交给整个线程池,而不是直接提交给某个线程。线程池在拿到任务后,就在内部寻找是否有空闲的线程,如果有,则将任务交给该空闲线程。一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务。

3️⃣使用线程池的原因
多线程运行时间,系统不断的启动和关闭新线程,成本非常高,会过渡消耗系统资源,以及过度切换线程太危险,可能导致系统资源的崩溃。这时,线程池就是最好的选择。使用线程池可以很好地提高性能,线程池在系统启动时即创建大量空闲的线程,程序将一个任务传给线程池,线程池就会启动一条线程来执行这个任务,执行结束以后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务。

二、四种常见的线程池详解

ExecutorService 是 Java 提供的用于管理线程池的类。该类的两个作用:控制线程数量和重用线程。ExecutoreService 提供了submit(),传递一个 Callable 或 Runnable,返回 Future。如果 Executor 后台线程池还没有完成 Callable 的计算,调用返回 Future 对象的 get() 会阻塞,直到计算完成。具体的四种常用的线程池实现如下:

1️⃣【可缓存线程池】Executors.newCachedThreadPool()
先查看池中有没有以前建立的线程,如果有,就直接使用。如果没有,就建一个新的线程加入池中。可缓存线程池为无限大,当执行当前任务时上一个任务已经完成,会复用执行上一个任务的线程,而不是每次新建线程。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。可缓存线程池通常用于执行一些生存期很短的异步型任务。示例代码:

public static void main(String[] args) {
    //创建一个可缓存线程池
    ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
    for (int i = 0; i < 10; i++) {
        try {
            //sleep可明显看到使用的是线程池里面以前的线程,没有创建新的线程
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        cachedThreadPool.execute(new Runnable() {
            public void run() {
                //打印正在执行的缓存线程信息
                System.out.println(Thread.currentThread().getName() + "正在被执行");
            }
        });
    }
}

输出结果:

pool-1-thread-1正在被执行
pool-1-thread-1正在被执行
pool-1-thread-1正在被执行
pool-1-thread-1正在被执行
pool-1-thread-1正在被执行
pool-1-thread-1正在被执行
pool-1-thread-1正在被执行
pool-1-thread-1正在被执行
pool-1-thread-1正在被执行
pool-1-thread-1正在被执行

2️⃣【可重用固定个数的线程池】Executors.newFixedThreadPool(int n)
以共享的无界队列方式来运行这些线程。因为线程池大小为 3,每个任务输出打印结果后 sleep 2 秒,所以每两秒打印 3 个结果。定长线程池的大小最好根据系统资源进行设置。如:Runtime.getRuntime().availableProcessors()。示例代码:

public static void main(String[] args) {
    //创建一个可重用固定个数的线程池
    ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
    for (int i = 0; i < 10; i++) {
        fixedThreadPool.execute(new Runnable() {
            public void run() {
                try {
                    //打印正在执行的缓存线程信息
                    System.out.println(Thread.currentThread().getName() + "正在被执行");
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

输出结果:

pool-1-thread-2正在被执行
pool-1-thread-3正在被执行
pool-1-thread-1正在被执行
pool-1-thread-1正在被执行
pool-1-thread-3正在被执行
pool-1-thread-2正在被执行
pool-1-thread-1正在被执行
pool-1-thread-3正在被执行
pool-1-thread-2正在被执行
pool-1-thread-2正在被执行

3️⃣【定长线程池】Executors.newScheduledThreadPool(int n)
支持定时及周期性任务执行。延迟执行示例代码:

public static void main(String[] args) {
    //创建一个定长线程池,支持定时及周期性任务执行——延迟执行
    ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
    //延迟1秒执行
    scheduledThreadPool.schedule(new Runnable() {
        public void run() {
            System.out.println("延迟1秒执行");
        }
    }, 1, TimeUnit.SECONDS);
}

输出结果:延迟1秒执行

定期执行示例代码:

public static void main(String[] args) {
    //创建一个定长线程池,支持定时及周期性任务执行——定期执行
    ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
    //延迟1秒后每3秒执行一次
    scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
        public void run() {
            System.out.println("延迟1秒后每3秒执行一次");
        }
    }, 1, 3, TimeUnit.SECONDS);
}

输出结果:
延迟1秒后每3秒执行一次
延迟1秒后每3秒执行一次

4️⃣【单线程化的线程池】Executors.newSingleThreadExecutor()
它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO/LIFO优先级)执行。示例代码:

public static void main(String[] args) {
    //创建一个单线程化的线程池
    ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
    for (int i = 0; i < 10; i++) {
        final int index = i;
        singleThreadExecutor.execute(new Runnable() {
            public void run() {
                try {
                    //结果依次输出,相当于顺序执行各个任务
                    System.out.println(Thread.currentThread().getName()+
                        "正在被执行,打印的值是:"+index);
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

输出结果:

pool-1-thread-1正在被执行,打印的值是:0
pool-1-thread-1正在被执行,打印的值是:1
pool-1-thread-1正在被执行,打印的值是:2
pool-1-thread-1正在被执行,打印的值是:3
pool-1-thread-1正在被执行,打印的值是:4
pool-1-thread-1正在被执行,打印的值是:5
pool-1-thread-1正在被执行,打印的值是:6
pool-1-thread-1正在被执行,打印的值是:7
pool-1-thread-1正在被执行,打印的值是:8
pool-1-thread-1正在被执行,打印的值是:9

三、总结

Executors 类,提供了一系列工厂方法用于创建线程池,返回的线程池都实现了 ExecutorService 接口。
1️⃣public static ExecutorService newFixedThreadPool(int nThreads)
创建固定数目线程的线程池。适合使用在任务量比较固定但耗时长的任务。

2️⃣public static ExecutorService newCachedThreadPool()
创建一个可缓存的线程池,调用 execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。适合使用在任务量大但耗时少的任务。

3️⃣public static ExecutorService newSingleThreadExecutor()
创建一个单线程化的 Executor。适合使用在多个任务顺序执行的场景。

4️⃣public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代 Timer 类。适合使用在执行定时任务和具体固定周期的重复任务。

5️⃣public static ExecutorService newWorkStealingPool()
jdk1.8新引进的线程池,创建一个拥有多个任务队列(以便减少连接数)的线程池。由于能够合理的使用 CPU 进行对任务操作(并行操作),所以适合使用在很耗时的任务中。

四、如何合理配置线程池的大小

结论

最佳线程数目 = ((线程等待时间+线程CPU时间) / 线程CPU时间) * CPU数目

简化后:

最佳线程数目 = (线程等待时间与线程CPU时间之比 + 1) * CPU数目

线程池大小因任务特性而异

1️⃣【CPU 密集型】cpu 利用率较高,设置线程数量和 cpu 核心数一样即可,以充分利用 cpu。
2️⃣【IO密集型】应配置尽可能多的线程,因为 IO 操作不占用 CPU,不要让 CPU 闲下来,应加大线程数量,如配置两倍CPU个数+1。
3️⃣【混合型的任务】如果可以拆分,拆分成IO密集型和CPU密集型分别处理。前提是两者运行的时间是差不多的,如果处理时间相差很大,则没必要拆分了。

若任务对其他系统资源有依赖,如某个任务依赖数据库的连接返回的结果。此时等待的时间越长,则 CPU 空闲的时间越长,那么线程数量应设置得越大,才能更好的利用 CPU。因此,线程等待时间占比越高,需要越多线程。线程CPU时间占比越高,需要越少线程。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JFS_Study

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值