面试之线程池全面解析

什么是线程池?

实际开发中我们需要让程序执行某个特定任务时,就会开启一个线程,如果并发的线程数量太多,频繁地创建线程就会严重影响系统的运行效率,如何解决呢?有没有一种方式可以让线程得到复用?执行一次任务之后不被销毁,可以继续执行其他任务,这就跟数据库连接池的思路一样了,数据库连接池的实现逻辑是在缓冲池中预先放置一定数量的连接对象,然后进行复用,那么很显然,在缓冲池中预先放置一定数量的线程对象以实现复用的机制就叫做线程池。

线程池的好处

我们知道不用线程池的话,每个线程都要通过new Thread(xxRunnable).start()的方式来创建并运行一个线程,线程少的话这不会是问题,而真实环境可能会开启多个线程让系统和程序达到最佳效率,当线程数达到一定数量就会耗尽系统的CPU和内存资源,也会造成GC频繁收集和停顿,因为每次创建和销毁一个线程都是要消耗系统资源的,如果为每个任务都创建线程这无疑是一个很大的性能瓶颈。所以,线程池中的线程复用极大节省了系统资源,当线程一段时间不再有任务处理时它也会自动销毁,而不会长驻内存。

线程池的核心类ThreadPoolExecutor

在java.util.concurrent包中我们能找到线程池的定义,其中ThreadPoolExecutor是我们线程池核心类,首先看看线程池类的主要参数有哪些。

1、corePoolSize:线程池的核心大小(核心线程数)。

2 、maximumPoolSize:最大线程池大小。(核心线程数+额外线程数)
正常情况下,只有核心线程被调用,任务量激增时,会调用额外线程数,但是总数量不超过maxmumPoolSize

3、keepAliveTime:空余线程存活时间,指的是超过corePoolSize的线程(非核心线程)闲置多长时间才进行销毁。

4、unit:销毁时间单位。

5、workQueue:存储等待执行线程的工作队列。由execute()方法提交而来的的尚未执行线程存储在这个队列中。常用任务队列有以下几种:
(1)ArrayBlockingQueue:基于数组的先进先出队列,创建时必须指定大小。
(2)LinkedBlockingQueue:基于链表的先进先出队列,不指定大小,默认为Integer.MAX_VALUE;
(3)syhchronousQueue:该队列不会保存提交的任务,而是直接新建一个线程执行任务
(4)PriorityBlockingQueue:具有优先级的无界阻塞队列

6、threadFactory:创建线程的工厂,一般用默认即可。

7、handler:拒绝策略,当工作队列、线程池全已满时如何拒绝新任务,默认抛出异常。(RejectedExecutionException)。拒绝策略有以下几种:

ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出 RejectedExecutionException 异常。
ThreadPoolExecutor.DiscardPolicy:丢弃任务不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列首位的任务,然后重新尝试执行任务。
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务。

工作流程

1、提交一个任务时,线程池会创建一个新的线程执行任务,直到当前线程数等于 corePoolSize。
2、如果当前线程数为 corePoolSize,继续提交的任务被保存到任务队列中,等待被执行。
3、如果任务队列满了,那就创建新的线程执行当前任务,直到线程池中的线程数达到 maxPoolSize,这时再有任务来,只能执行reject() 拒绝处理该任务。

线程池状态

1、RUNNING:运行状态,线程池可以接收新任务,并处理队列中的任务。
2、SHUTDOWN: 关闭状态,线程池不接收新任务,但是会处理队列中的任务。
3、STOP : 停止状态,线程池中断所有正在运行的任务,不接收新任务,同时也不处理队列中的任务。
4、TIDYING : 整理状态,线程池对线程资源进行整理优化。
5、TERMINATED:结束状态,线程池停止工作。

线程池的使用(四种)

public class Test {
    public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 15, 100, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(5));
        for (int i = 0; i < 20; i++) {
            MyTask myTask = new MyTask(i);
            executor.execute(myTask);
            System.out.println("线程池中线程数量:"+executor.getPoolSize()+"、任务队列中等待执行的任务数量:"+
            executor.getQueue().size()+"、执行完成的任务数量:"+executor.getCompletedTaskCount());
        }
        executor.shutdown();
    }
}

class MyTask implements Runnable{
    private int num;
    public MyTask(int num) {
        this.num = num;
    }

    public void run() {
        // TODO Auto-generated method stub
        System.out.println("正在执行任务"+num);
        try {
            Thread.currentThread().sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("任务"+num+"执行完毕");
    }
}

运行结果如下所示:

在这里插入图片描述
从执行结果可以看出,当线程池中线程的数目大于10的时候,就会将任务放入队列,当任务队列满了之后,就会额外创建新的线程。在实际开发中,并不需要直接使用 ThreadPoolExecutor,可以使用 Executors 的静态方法来创建线程池:

1、newFixedThreadPool()
创建一个固定大小的线程池,任务超过10个时,会将线程放入等待队列中。初始化一个指定线程数的线程池,其中 corePoolSize == maxiPoolSize,使用 LinkedBlockingQuene 作为任务队列。
特点:即使当线程池没有可执行任务时,也不会释放线程。

ExecutorService executorService = Executors.newFixedThreadPool(10);
    for(int i = 0; i < 10; i++) {
        executorService.execute(new Runnable() {

        @Override
        public void run() {
            // TODO Auto-generated method stub
            try {
                Thread.currentThread().sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        System.out.println("...");
        }
    });
}
executorService.shutdown();

1000ms之后,10行…同时打印,因为线程池开启后直接创建了10个线程,一起等待1000ms之后同时执行。

2、newCachedThreadPool()
创建一个缓存线程池,初始化一个可以缓存线程的线程池,默认缓存60s,线程池的线程数可达到 Integer.MAX_VALUE,即2147483647,内部使用 SynchronousQueue 作为任务队列。
特点:在没有任务执行时,当线程的空闲时间超过 keepAliveTime,会自动释放线程资源。当提交新任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销,因此,使用时要注意控制并发的任务数,防止因创建大量的线程导致而降低性能。

ExecutorService executorService = Executors.newCachedThreadPool();

3、newSingleThreadExecutor()
创建一个单例线程池,线程池中只有一个线程,初始化只有一个线程的线程池,内部使用 LinkedBlockingQueue 作为任务队列。
特点:如果该线程异常结束,会重新创建一个新的线程继续执行任务,唯一的线程可以保证所提交任务的顺序执行。

ExecutorService executorService = Executors.newSingleThreadExecutor();
    for(int i = 0; i < 10; i++) {
        executorService.execute(new Runnable() {

        @Override
        public void run() {
            // TODO Auto-generated method stub
            try {
                Thread.currentThread().sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println("...");
        }
    });
}
executorService.shutdown();

4、newScheduledThreadPool()
任务调度线程池。
特点:初始化的线程池可以在指定的时间内周期性的执行所提交的任务,在实际的业务场景中可以使用该线程池定期的同步数据。

//任务调度的线程池
//10表示延迟10s开始任务调度,3表示调度之间间隔3s
ScheduledExecutorService sch = Executors.newScheduledThreadPool(10);
sch.scheduleAtFixedRate(new Runnable() {

@Override
public void run() {
    // TODO Auto-generated method stub
    System.out.println("...");
    }
}, 10, 3, TimeUnit.SECONDS);
sch.shutdown();

除了 newScheduledThreadPool 的内部实现特殊一点之外,其它线程池内部都是基于 ThreadPoolExecutor 类实现的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值