多线程之线程池

1、为什么要用线程池

1、线程的创建需要开辟虚拟机栈、本地方法栈、程序计数器等线程私有的内存空间;
2、在销毁时需要回收这些系统资源,通过复用已有线程可以更好地管理和协调线程的工作;
3、线程池主要解决两个问题:a. 当执行大量异步任务时线程池能够提供很好的性能;b. 线程池提供了一种资源限制和管理手段,比如可以限制线程的个数,动态新增线程等。

2、线程池工作原理分析

2.1 线程池工作原理

在这里插入图片描述

当调用execute或者submit,将一个任务提交给线程池,线程池收到这个任务请求后,有以下几种处理情况:

  • 当前线程池中运行的线程数量还没有达到corePoolSize大小时,线程池会创建一个新线程执行提交的任务,无论之前创建的线程是否处于空闲状态。
  • 当前线程池中运行的线程数量已经达到corePoolSize大小时,线程池会把任务加入到等待队列中,直到某一个线程空闲了,线程池会根据我们设置的等待队列规则,从队列中取出一个新的任务执行;
  • 如果线程数大于corePoolSize数量但是还没有达到最大线程数maximumPoolSize,并且等待队列已满,则线程池会创建非核心线程来执行任务;
  • 如果提交的任务,无法被核心线程直接执行,又无法加入等待队列,又无法创建“非核心线程”直接执行,线程池会根据拒绝处理器定义的策略处理这个任务。
  • 拒绝策略类型
  • AbortPolicy:默认饱和策略,丢弃任务并抛出RejectedExecutionException异常。调用者可以捕获这个异常并自行处理;
  • CallerRunsPolicy:线程池中不再处理该任务,由调用线程处理该任务;
  • DiscardPolicy:当任务无法添加到队列中等待执行时,DiscardPolicy策略会丢弃任务,并且不抛异常;
  • DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试提交新的任务。
2.2 线程池结构
  • worker集合:保存所有的核心线程和非核心线程,其本质是一个HashSet
private final HashSet<Worker> workers=new HashSet<>();
  • 等待任务队列:当核心线程的个数达到corePoolSize时,新提交的任务会被先保存在等待队列中,其本质是一个阻塞队列BlockingQueue
private final BlockingQueue<Runnable> workQueue;

ThreadPoolExecutor中使用以下几种队列

  • ArrayBlockingQueue:数组实现的有界阻塞队列,队列满时,后续需要提交的任务通过handler中的拒绝策略去处理;
  • LinkedBlockingQueue:链表实现的阻塞队列,默认大小是Integer.MAX_VALUE(无界队列),也可以通过传入指定队列大小capacity;
  • SynchronousQueue:内部并没有缓存数据,缓存的是线程。当生产者线程进行添加操作(put)时必须等待消费者线程的移除操作(take)才会返回。SynchronousQueue可以用于实现生产者与消费者的同步;
  • PriorityBlockingQueue:二叉堆实现的优先级阻塞队列,传入队列的元素不能为null并且必须实现Comparable接口;
  • DelayQueue:延时阻塞队列,队列元素需要实现Delayed接口。
  • ctl:是一个AtomicInteger类型,二进制高3位用来标识线程池的状态,低29位用来记录池中线程的数量;
2.3 线程池的几种状态
  • RUNNING:默认状态,接收新任务并处理排队任务;
  • SHUTDOWN:不接受新任务,但处理排队任务,调用shutdown()会处于该状态;
  • STOP:不接收新任务,也不处理排队任务,并中断正在运行的任务,调用shutdownNow()会处于该状态;
  • TIDYING:所有任务都已终止,workerCount为零时,线程会转换到TIDYING状态,并将运行terminate()方法;
  • TERMINATED:terminate()运行完成后线程池转为此状态。
2.4 参数分析
  • corePoolSize:表示核心线程数量;
  • MaximumPoolSize:表示线程池最大能够容纳同时执行的线程数,必须大于或等于1,如果和corePoolSize相等即是固定大小线程池;
  • keepAliveTime:表示非核心线程存活时间,当空闲时间达到此值时,线程会被销毁直到剩下corePoolSize;
  • unit:用来指定keepAliveTime的时间单位,有MILLISECONDS、SECONDS、MINUTES、HOURS等;
  • workQueue:等待队列,BlockingQueue类型;当请求任务数大于corePoolSize时,任务被缓存在此BlockingQueue中;
  • threadFactory:线程工厂,线程池中使用它来创建线程,如果传入的是null,则使用默认工厂类DefaultThreadFactory
  • handler:执行拒绝策略的对象,当workQueue满了之后并且活动线程数大于maximumPoolSize时,线程池通过该策略处理请求;
    注: 当ThreadPoolExecutor的allowCoreThreadTimeOut设置为true时,核心线程超时后也会被销毁。

3、创建线程池

3.1 newSingleThreadExecutor

创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按先进先出的顺序执行。

public class CreateSingleThreadExecutor {
    public static void main(String[] args) throws InterruptedException {
        //创建单线程池
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        for(int i=1;i<=5;i++){
            final int taskID=i;
            //向线程池提交任务
            singleThreadExecutor.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程:"+Thread.currentThread().getName()
                    +"正在执行 task:"+taskID);
                }
            });
            Thread.sleep(1000);
        }
    }
}
  • 打印结果
线程:pool-1-thread-1正在执行 task:1
线程:pool-1-thread-1正在执行 task:2
线程:pool-1-thread-1正在执行 task:3
线程:pool-1-thread-1正在执行 task:4
线程:pool-1-thread-1正在执行 task:5
3.2 newCachedThreadPool

创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程;若无可回收,则新建线程。

public class NewCachedThreadPool {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
        for (int i=1;i<=5;i++){
            final int taskID=i;
            newCachedThreadPool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程:"+Thread.currentThread().getName()+
                            "正在执行: task:"+taskID);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        newCachedThreadPool.shutdown();
    }
}
  • 执行结果
线程:pool-1-thread-3正在执行: task:3
线程:pool-1-thread-2正在执行: task:2
线程:pool-1-thread-1正在执行: task:1
线程:pool-1-thread-5正在执行: task:5
线程:pool-1-thread-4正在执行: task:4
  • 从上面日志可以看出,缓存线程池会创建新的线程来执行任务。但是如果在提交任务前休眠1秒钟,如下:
public class NewCachedThreadPool {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
        for (int i=1;i<=5;i++){
            final int taskID=i;
            Thread.sleep(1000);
            newCachedThreadPool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程:"+Thread.currentThread().getName()+
                            "正在执行: task:"+taskID);
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        newCachedThreadPool.shutdown();
    }
}
  • 执行结果
线程:pool-1-thread-1正在执行: task:1
线程:pool-1-thread-1正在执行: task:2
线程:pool-1-thread-1正在执行: task:3
线程:pool-1-thread-1正在执行: task:4
线程:pool-1-thread-1正在执行: task:5

再次执行则打印日志与SingleThreadPool一模一样,原因是提交的任务只需要500毫秒即可执行完毕,休眠1秒导致在新的任务提交之前,线程“pool-1-thread-1”已经处于空闲状态,可以被复用执行任务。

3.3 newFixedThreadPool

创建一个固定数目的、可重用的线程池。

public class NewFixedThreadPool {
    public static void main(String[] args) {
        //创建线程数量为3的线程池
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
        for (int i=1;i<=10;i++){
            final int taskID=i;
            fixedThreadPool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程:"+Thread.currentThread().getName() +
                            "正在执行的task:"+taskID);
                }
            });
        }
    }
}
  • 执行结果
线程:pool-1-thread-1正在执行的task:1
线程:pool-1-thread-3正在执行的task:3
线程:pool-1-thread-2正在执行的task:2
线程:pool-1-thread-1正在执行的task:4
线程:pool-1-thread-2正在执行的task:6
线程:pool-1-thread-1正在执行的task:7
线程:pool-1-thread-3正在执行的task:5
线程:pool-1-thread-1正在执行的task:9
线程:pool-1-thread-2正在执行的task:8
线程:pool-1-thread-3正在执行的task:10
3.4 newScheduledThreadPool

创建一个定时线程池,支持定时及周期性任务执行

public class NewScheduledThreadPool {
    public static void main(String[] args) throws InterruptedException {
        //创建一个线程数量为3的线程池
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
       //延迟1s执行,每隔1秒执行一次
        scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                Date now = new Date();
                System.out.println("线程:" + Thread.currentThread().getName() + "报时" + now);
            }
        }, 1000, 1000, TimeUnit.MILLISECONDS);
        Thread.sleep(5000);
        //使用shutdown关闭定时任务
        scheduledThreadPool.shutdown();
    }
}

上面代码创建了一个线程数量为3的定时任务线程池,通过scheduleAtFixedRate方法,指定每隔1s执行一次任务,并且在5s之后通过shutdown方法关闭定时任务,打印结果如下:

线程:pool-1-thread-1报时Tue May 04 11:19:48 CST 2021
线程:pool-1-thread-1报时Tue May 04 11:19:49 CST 2021
线程:pool-1-thread-2报时Tue May 04 11:19:50 CST 2021
线程:pool-1-thread-2报时Tue May 04 11:19:51 CST 2021
线程:pool-1-thread-2报时Tue May 04 11:19:52 CST 2021
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值