【Java】线程池凭什么可以提高运行效率?到底有多少种线程池?| Java开发实战

这是我参与更文挑战的第4天,活动详情查看:更文挑战

本文正在参加「Java主题月 – Java 开发实战」,详情查看 活动链接

一、什么是线程池

1.概念

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

一个线程池包含了一个或多个线程,这几个线程都由线程池统一管理
一般情况下,线程池在刚接到任务时就会创建一个线程去执行任务,当线程数量达到线程池规定的线程数量时,就不会再进行创建了,而是把任务添加到工作队列中,线程池中空闲的线程就会去执行队列中的任务

举个例子,线程池就是包头工,他的手底下会有几个工人(线程)一旦包头共接到了工作,就会轮流分配给工人,等工人做完了工作再来接下一个

2.好处

  1. 降低资源消耗。在线程池中的线程是不会销毁,通过线程池可以反复利用线程,省去了频繁创建和销毁线程带来的消耗
  2. 节省时间。使用线程池,就不需要反复创建线程了,节省了反复创建线程的时间
  3. 提高线程的可控性。线程池可以控制线程数,有效地控制了线程所占用的资源

二、线程池的使用

首先创建一个内部类MyRunnable,创建成外部类也行

public static class MyRunnable implements Runnable{
        @Override
        public void run() {
            for (int i=0;i<10;i++){
                number+=1;
                System.out.println(Thread.currentThread().getName()+"    number:"+number);
            }
        }
    }
复制代码

1.SingleThreadExecutor

单线程线程池,这个线程池只会调用一个线程来进行执行任务,实际上和自己创建一个Thread对象然后start没什么区别,然是它内部的线程是可以复用的

/**
 * @author xxj
 * 线程池测试
 */
public class ThreadPoolTest {
    public static void main(String[] args) {
       ExecutorService singleThreadExecutor= Executors.newSingleThreadExecutor();
        for (int i = 0; i <10 ; i++) {
            singleThreadExecutor.execute(new MyRunnable());
        }
        singleThreadExecutor.shutdown();
    }
}

复制代码

优点:
实现了线程的复用

2.FixedThreadPool

多线程线程池,这个线程池在创建时可以指定池内线程的数量

/**
 * @author xxj
 * 线程池测试
 */
public class ThreadPoolTest {
    static  int number=0;
    public static void main(String[] args) {
       ExecutorService fixedThreadPool= Executors.newFixedThreadPool(5);
        for (int i = 0; i <10 ; i++) {
            fixedThreadPool.execute(new MyRunnable());
        }
        fixedThreadPool.shutdown();
    }
}
复制代码

优点:
在实现了线程的复用的基础上,让线程数更加可控

3.CachedThreadPool

缓存线程池,这个线程池在创建时并没有规定线程的数量,这个线程池接到任务时,会优先寻找空闲的线程,如果有空闲的线程,就把任务交给它,如果没有再创建一个线程来执行任务。当线程很久没有执行任务时就会回收该线程,该线程实现了线程的动态创建和销毁

public static void main(String[] args) {
       ExecutorService fixedThreadPool= Executors.newCachedThreadPool();
        for (int i = 0; i <100 ; i++) {
            fixedThreadPool.execute(new MyRunnable());
        }
        fixedThreadPool.shutdown();
    }
复制代码

优点:
实现了线程的复用和动态创建,执行任务的速度很高
缺点:
线程的并发数不可控,存在线程的反复创建和销毁

4.ScheduledThreadPool

定时线程池,这个线程池是在FixedThreadPool的基础上进行扩展的,可以规定线程的数量,还可以实现定时任务
定时线程池中,有三种启动线程的方式schedule,scheduleAtFixedRate,scheduleWithFixedDelay

schedule

这种方式只有三个参数,使用这种启动方式只能做到延迟执行

/**
     * @param1 Runnable command 线程任务,就是Runnable对象
     * @param2 long delay 延迟执行时间
     * @param3 TimeUnit unit 时间单位
     */
复制代码

这里是延迟了一秒后执行

/**
 * @author xxj
 * 线程池测试
 */
public class ThreadPoolTest {
    static  int number=0;
    public static void main(String[] args) {
        ScheduledExecutorService scheduledThreadPool= Executors.newScheduledThreadPool(5);
        for (int i = 0; i <10 ; i++) {
            System.out.println(Thread.currentThread().getName()+"加入线程池时间:"+ DateFormat.getTimeInstance().format(new Date()));
            scheduledThreadPool.schedule(new MyRunnable(),1000, TimeUnit.MILLISECONDS);
        }
        scheduledThreadPool.shutdown();
    }
    public static class MyRunnable implements Runnable{
        @Override
        public void run() {
       		System.out.println(Thread.currentThread().getName()+"线程开始时间:"+ DateFormat.getTimeInstance().format(new Date()));
            for (int i=0;i<10;i++){
                number+=1;
            }
            System.out.println(Thread.currentThread().getName()+"线程结束时间:"+DateFormat.getTimeInstance().format(new Date()));
            System.out.println( "number:"+number);
        }
    }
}
复制代码

剩下的两种方式如果不主动shutdowm,则会一直在后台运行

scheduleAtFixedRate

这中启动方式不仅能够做到延迟执行,还可以实现周期性运行

/**
     * @param1 Runnable command 线程任务,就是Runnable对象
     * @param2 long initialDelay 延迟执行时间
     * @param3 long period 执行的绝对间隔时间
     * @param4 TimeUnit unit 时间单位
     */
复制代码

这里是延迟1秒执行,每隔5秒再执行一次

 public static void main(String[] args) {
        ScheduledExecutorService scheduledThreadPool= Executors.newScheduledThreadPool(5);
        for (int i = 0; i <10 ; i++) {
            System.out.println(Thread.currentThread().getName()+"加入线程池时间:"+ DateFormat.getTimeInstance().format(new Date()));
            scheduledThreadPool.scheduleAtFixedRate(new MyRunnable(),
                    1000,5000, TimeUnit.MILLISECONDS);
        }
//        scheduledThreadPool.shutdown();
    }
复制代码

注意: 间隔时间需要大于任务执行的时间,不然就会执行完任务马上就开始下一个周期

scheduleWithFixedDelay

这种方式和scheduleAtFixedRate的方式很像,功能也差不多,这里就不在演示了

/**
     * @param1 Runnable command 线程任务,就是Runnable对象
     * @param2 long initialDelay 延迟执行时间
     * @param3 long delay 执行的相对间隔时间
     * @param4 TimeUnit unit 时间单位
     */
复制代码

scheduleWithFixedDelay和scheduleAtFixedRate的区别

scheduleAtFixedRate的间隔时间是绝对的,是从任务执行开始后开始计算的,所以当执行任务时间大于间隔时间时,线程就会立马进入一个周期
scheduleWithFixedDelay的间隔时间是相对的,是从任务执行完成后开始计算的

5.手动创建线程池

上面四种线程池的创建方式,都是用Executors这个类提供的静态方法创建的,然而为了能够更加准确地控制线程和节省资源,更加推荐使用手动创建线程池的方式

想要手动创建线程池,直接创建一个ThreadPoolExecutor对象就可以了
先来看看它的构造方法的参数

/**
* @param1 int corePoolSize 线程池的最大核心线程数
* @param2 int maximumPoolSize 线程池的最大线程数
* @param3 long keepAliveTime 线程池中空闲线程的存活时长
* @param4 TimeUnit unit 上一个参数的时间单位
* @param5 BlockingQueue<Runnable> workQueue 存放任务的队列,这里使用阻塞队列
* 上面是最简单的一个构造方法的参数,下面都是其他构造方法中有的
*@param6 ThreadFactory threadFactory 线程池创建线程的工厂
*@param7 RejectedExecutionHandler handler 线程池的控制器,用来处理任务队列和最大线程数都达到最大值,仍然有任务要加入任务队列的情况
*/
复制代码

这里创建了一个核心线程数为5,最大线程数为5,空闲线程的存活时长为30秒,任务队列长度为10的线程池

/**
 * @author xxj
 * 手动创建线程池
 */
public class ManualThreadPoolTest {
    public static void main(String[] args) {
        ExecutorService executorService=new ThreadPoolExecutor(
                5,5,30000, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(10));
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"子线程");
            }
        });
        executorService.shutdown();
    }
}
复制代码

最大核心线程数和最大线程数的区别

画个图出来就看到明白了
在这里插入图片描述

三、总结

线程池的出现,就是为了解决线程反复创建的销毁带来的开销、增加线程的复用性和加强对线程可控性
如果想要降低资源的消耗,可以使用SingleThreadExecutor和FixedThreadPool,还有手动创建线程池
如果想要更快的执行速度,可以使用CachedThreadPool
如果想要实现定时任务,可以使用ScheduledThreadPool,但需要注意它的三种启动方式的区别

——————————————————————————————

你知道的越多,不知道的越多。

如果本文章内容有问题,请直接评论或者私信我。如果觉得写的还不错的话,点个赞也是对我的支持哦

未经允许,不得转载!

微信公众号【程序员徐小白】,关注即可第一时间阅读最新文章。回复【面试题】有我准备的50道高频校招面试题,以及各种Java学习资料。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享