简单入门线程池(二)

简单入门线程池(一)中,对线程池进行了简单的介绍。
下面是本系列文章的第二篇。

线程池的内部实现

1. 实现

无论是newFixedThreadPool( )方法、newSingleThreadExecutor( )还是newCachedThreadPool( )方法,内部实现均使用了ThreadPoolExecutor。简单入门线程池(一)中提到了线程池构造方法中的几个参数。corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue、threadFactory和handler。
如图所示:
在这里插入图片描述

下面介绍一下线程池的任务执行流程

2. 执行流程

在这里插入图片描述
即:

  1. 一般情况下,新任务到来时,如果当前线程池中线程数目小于corePoolSize时,会直接创建一个新线程去处理该任务。特别的,即使其他线程现在是空闲的,也会去创建一个新的线程。
  2. 若是线程池中线程数目大于等于coolPoolSize,那么该任务会尝试排队。
  3. 如果队列满了或其他原因,导致无法入队
  4. 去检测线程数目是否达到了maximumPoolSize,如果没有,就会继续创建线程,直到达到了maximumPoolSize。
  5. 反之,会调用任务拒绝策略。
    下面是源代码:
public void execute(Runnable var1) {
        if (var1 == null) {
            throw new NullPointerException();
        } else {
        // 获取当前线程池的状态
            int var2 = this.ctl.get();
            // 1. 如果运行的线程数量小于corePoolSize,就创建新线程执行该任务
            if (workerCountOf(var2) < this.corePoolSize) {
                if (this.addWorker(var1, true)) {
                    return;
                }
                var2 = this.ctl.get();
            }
			// 如果数量大于核心线程数目,就来到此处。
			// 2. 检测线程池是否处于运行的状态,以及任务队列是否可以放入线程
            if (isRunning(var2) && this.workQueue.offer(var1)) {
                int var3 = this.ctl.get();
                // 再次判断线程池是否处于运行状态,任务是否被移除
                if (!isRunning(var3) && this.remove(var1)) {					
                	// 是,执行任务拒绝策略
                    this.reject(var1);
                } // 如果当前工作线程数为0,就创建一个新线程在最大线程池
                else if (workerCountOf(var3) == 0) {
                    this.addWorker((Runnable)null, false);
                }
            } // 如果在最大线程池添加失败,就执行相应拒绝策略
            else if (!this.addWorker(var1, false)) {
                this.reject(var1);
            }
        }
    }

实例

/**
 * @author LISHANSHAN
 * @ClassName ThreadPoolDemo
 * @date 2022/05/2022/5/30 00:43
 */

public class ThreadPoolDemo {
    public static class MyTask implements Runnable {
        @Override
        public void run() {
            System.out.println(System.currentTimeMillis() + " :Thread ID: "
            + Thread.currentThread().getId());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        MyTask task = new MyTask();
        // 定义固定大小为5的线程池
        ExecutorService es = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 10; i++) {
        // 提交10个任务
            es.submit(task);
        }
    }
}

运行结果:(虽然睡眠时间是1000毫秒)
在这里插入图片描述
运行结果也可以体现出,当任务数目超过当前线程数目,那么任务会被尝试放入任务队列中,加入队列成功的话,会等待有线程空闲之后再去执行。

3. 任务拒绝策略

ThreadPoolExecutor中定义了四种策略:
1. ThreadPoolExecutor.AbortPolicy:默认的策略,直接抛出异常
2. ThreadPoolExecutor.DiscardPolicy:静默处理,忽略新任务,不抛出异常,也不执行
3. ThreadPoolExecutor.DiscardOldestPolicy:将等待时间最长的任务扔掉,让当前任务排队
4. ThreadPoolExecutor.CallerRunsPolicy:在任务提交者线程中执行任务,而不是等待交给线程池中的线程。

4. BlockingQueue

  • 直接提交的队列:该功能由SynchronousQueue对象提供。该对象没有容量,每一个插入操作都需要等待一个对应的删除操作,同样,每一个删除操作,都需要等待一个插入的插入操作。如果使用SynchronousQueue,提交的任务不会被真实保存,而总是将新任务提交给线程执行。如果没有空闲的进程,则尝试创建新的进程,如果达到线程最大值,则执行拒绝策略。因此,如果使用这个队列,就需要设置较大的maximumPoolSize值,否则很容易执行拒绝策略
  • 有界的任务队列:使用ArrayBlockingQueue实现。其构造函数中必须带一个容量参数,表示该队列的最大容量。使用该队列,当有新任务提交,若当前线程数量小于corePoolSize,那么优先创建新的线程。若大于,则会将新任务加入等待队列,若等待队列已满,无法加入,则在线程数量不大于maximumPoolSize的情况下,创建新的线程执行任务。若大于,则执行拒绝策略。也就是说,除非系统繁忙,否则该队列优先确保核心线程数目维持在corePoolSize。
  • 无界的任务队列:通过LinkedBlockingQueue实现。除非系统资源耗尽,否则无界的任务队列不会有任务入队失败的情况。对于它而言,如果系统的线程数达到corePoolSize之后,就不会继续增加,如果后续有新的任务加入,但没有空闲线程,就会将任务直接进入队列等待,直到耗尽系统内存。
  • 优先任务队列:带有优先执行级的队列。它通过PriorityBlockingQueue实现,是一个特殊的无界队列。前二者都是按照先进先出的顺序处理任务,但它可以根据任务自身的优先级顺序先后执行。

5. ThreadFactory

之前提到过,线程池的主要作用是为了线程复用,也就是避免了线程的频繁创建。而这就涉及到了最开始的那些线程的来处,ThreadFactory。

简介

ThreadFactory是一个接口,它只有一个方法,用来创建线程:

Thread newThread(Runnable r);

当线程池需要新建线程时,就会调用这个方法。

自定义线程池

自定义线程池可以帮助我们做不少事情。比如,可以跟踪线程池究竟在何时创建了多少线程,也可以自定义线程的名称、组以及优先级等信息。

简单的栗子

下面的线程池,一方面记录了线程的创建,另一方面将所有的线程都设置为守护线程。

public static void main(String[] args) {
		// 创建新的任务
        MyTask task = new MyTask();
        ExecutorService es = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS,
                new SynchronousQueue<Runnable>(),
                new ThreadFactory() {
                    @Override
                    public Thread newThread(Runnable r) {
                        Thread t = new Thread();
                        // 将线程设置为守护线程
                        t.setDaemon(true);
                        // 输出线程创建的信息
                        System.out.println("create " + t);
                        return t;
            }
                });
        for (int i = 0; i < 10; i++) {
            es.submit(task);
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

6. 简单总结

  1. 回顾newFixedThreadPool( )方法的实现,它返回了一个corePoolSize和maximumPoolSize一样大的,并且使用了LinkedBlockingQueue任务对立的线程池。
  2. 而单线程线程池则是它的一种退化,将线程池线程数量设置为1。
  3. newCachedThreadPool( )方法返回corePoolSize为0,maximumPoolSize为无穷大的线程池。则没有任务时,池中没有线程。若有任务提交,但无空闲线程,则将任务加入SynchronousQueue队列,**而该队列是一个直接提交的队列,又会迫使线程池增加新的线程执行任务。但任务执行完毕后,因为corePoolSize大小为0,因此空闲线程又会在指定时间内被回收。**但若同时有大量资源提交,但线程处理任务速度又不是很快,就容易造成系统资源耗尽。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值