多线程的创建 ------ 线程池

12 篇文章 0 订阅
2 篇文章 0 订阅

1.说到线程,就不得不说线程的创建方式了

  • 继承Thread 类,将子类对象传入Thread 的构造方法中
  • 实现 Runnable 接口 ,将接口的实现类传入到 Thread 的构造方法
  • 实现 Callable 接口 ,将接口的子类对象 传入FutureTask ,因为这个类实现 Runable 接口,就可以创建线程,但是这个是可以有返回值的
  • 线程池创建 ( 这个是今天的重点)

2. 创建线程池的方法有哪些呢 ?

这个我们先绕一下弯子, 去查看线程池 ThreadPoolExecutor 的构造方法, 最后都调用的这个方法。
没有无参构造。

 public ThreadPoolExecutor(int corePoolSize, // 核心线程数
                              int maximumPoolSize, // 最大线程数
                              long keepAliveTime, // 存活时间
                              TimeUnit unit, // 存活时间的单位
                              BlockingQueue<Runnable> workQueue, // 阻塞队列
                              ThreadFactory threadFactory, // 线程工厂
                              RejectedExecutionHandler handler) {
                              // 拒绝策略
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            // 对每个数量的参数进行判断排除,如果不规范小于0 ,就会抛出异常
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
            // 当传入的对象为空的时候也会抛出空指针异常
            // 由此可见 都不能为空
            // 当数据正确,才会将数据传入
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

线程的创建是由一个工具类 Executors 调用类中的静态方法来实现的

  1. newFixedThreadPool (int n) 创建固定的线程数
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

通过参数克制 ,最大线程数 == 核心线程数, 存活时间 ==0 , 只有创建了核心线程数。这样的好处就可以保证额外的线程数个数,且保证一直存活,可以工作。
特别的: new LinkedBlockingQueue () 无参构造中,默认容量是Integer.MAX_VALUE

  1. newSingleThreadExecutor() 创建一个简单的线程池。
  public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

线程数只有一个 也没有非核心线程数,只有一个核心线程, 这样保证额外只有一个核心线程一个进行工作。

  1. newCachedThreadPool() 创建缓冲线程池
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

没有核心线程数, 但是非线程数是最大的数量Integer.MAX_VALUE,没有上限, 且每个存活的时间为 60 秒 。
特别的: 这个阻塞队列 SynchronousQueue() 是没有容量的,有任务就会创建线程来执行

3. 线程池的工作流程应该是怎么样的?

  • 首先当任务进来之后 ,会优先使用核心线程去执行任务,核心线程在线程池创建的时候就创建了出来。
  • 当线程池慢了之后,就会加入阻塞队列阻塞。
  • 当阻塞队列满了之后,就会创建急救线程 ( 非核心线程数= 最大线程数 - 核心线程数 )
  • 但是急救线程数是有存活时间的( 存活 相应的时间)
  • 当运行的线程数达到最大的时候,阻塞队列也慢的时候就会实行拒绝策略

4. ThreadFactory 是干什么的呢 ?

我们发现在 Executors 在内部实现了 ThreadFactory 接口, 同时也提供了默认实现,每次线程池在创建的时候都会创建 DefaultThreadFactory 。
特别的 : 这个类主要就是为我们创建的线程起名字的。 在底层创建这个类的实例对象的时候,会通过创建线程池的数量,合理的初始化每个默认线程工厂中的 前缀 。当创建先吃执行任务的时候, 创建线程的方法中,会将名称加入。

 namePrefix = "pool-" +
                          poolNumber.getAndIncrement() +
                         "-thread-";

 Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);

5. 线程池的拒绝策略

线程池提供了有四种拒绝策略 ,查看源码,我们发现四种实现都是在 ThreadPoolExecutor
内部, 内部类实现了 RejectedExecutionHandler 接口。

public interface RejectedExecutionHandler {
             // 拒绝执行处理器
    /**
    * 在每个实现类中,对方法重写进行实现, r 就是传入的额外任务,而 executor 就是线程池本身
    */
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
  1. CallerRunsPolicy 调用者执行政策
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
        }

很明显是,先判断线程池的状态,如果没有 进入 shutdown 状态 ,(这个是线程池的一个状态,后面会提及),它是直接执行 run(),方法没有 start() ,说明没有开辟额外的线程,是调用者的主线程来执行这个任务。

  1. AbortPolicy 中止政策
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        }

这个很明显 是直接抛出了 RejectedExecutionException ,将错误信息打印出来,包含有任务信息 和 线程池信息 。这个也是线程池默认的策略

  1. DiscardPolicy 丢弃政策
 public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        }

方法空实现,很明显,任务被搁置了,没有任何处理,被抛弃了。

  1. DiscardOldestPolicy 抛弃最老的政策
 public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                e.getQueue().poll();
                e.execute(r);
            }
        }

先判断 线程池的状态是不是中止状态,然后再将先吃池中的阻塞队列 中最早到的那个进行抛弃,然后再将这个任务加入到线程池中。

6. 线程池的状态有哪些?

不说了 ,上源码。

   // 这个原子类就是来保证状态码的改变,底层通过CAS 来实现状态码的变化。
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    // 位移量: 状态码是32位 ,但是表示状态的是 前三位,Integer.size = 32 
    private static final int COUNT_BITS = Integer.SIZE - 3;
    // 标记量: 通过运算后悔出现 29位都是1,且前三位为0 的情况,方便获取 状态码 和 线程数值
    private static final int COUNT_MASK = (1 << COUNT_BITS) - 1;
    // 高三位是状态码 , 低位是线程数量
    // runState is stored in the high-order bits(运行码在高阶)
    // 通过偏移量来设置状态
    private static final int RUNNING    = -1 << COUNT_BITS;  // 101
    private static final int SHUTDOWN   =  0 << COUNT_BITS;  // 000
    private static final int STOP       =  1 << COUNT_BITS;  // 001
    private static final int TIDYING    =  2 << COUNT_BITS;  // 010
    private static final int TERMINATED =  3 << COUNT_BITS;  // 011

TERMINATED > TIDYING > STOP > SHUTDOWN > RUNNING
那么问题来了,这些状态之间如何转换的。

  • RUNNING :在RUNNING状态下,线程池可以接收新的任务和执行已添加的任务。

线程池的初始化状态是RUNNING。换句话说,线程池被一旦被创建(比如调用Executors.newFixedThreadPool()或者使用ThreadPoolExecutor进行创建),就处于RUNNING状态,并且线程池中的任务数为0!线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。

  • SHUTDOWN :线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。

当一个线程池调用shutdown()方法时,线程池由RUNNING -> SHUTDOWN。

  • STOP : 线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在执行的任务。

调用线程池的shutdownNow()方法的时候,线程池由(RUNNING或者SHUTDOWN ) -> STOP。

  • TIDYING :当所有的任务已终止,记录的”任务数量”为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。

当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。 当线程池在STOP状态下,线程池中执行的任务为空时,会由STOP -> TIDYING。(terminated() 为空实现,可以由用户自己完成)

  • TERMINATED :当钩子函数terminated()被执行完成之后,线程池彻底终止,就变成TERMINATED状态。

线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。

如果不信:看官方解释
在这里插入图片描述
再看官方所说的状态转换
在这里插入图片描述

7. 线程池的方法有哪些?

  • 执行任务的方法
 public void execute(Runnable command)
  • 关闭线程池
public void shutdown()
  • 终止线程池
public List<Runnable> shutdownNow()

8.带有定时任务的线程池

在这里插入图片描述
之前介绍的都是ThreadPoolExecutor ,我们学习一下ScheduledThreadPoolExecutor 这个是继承了之前学习的线程池,有延时任务的线程池

  • 创建的构造函数
public ScheduledThreadPoolExecutor(int corePoolSize,
                                       ThreadFactory threadFactory) {
        super(corePoolSize, Integer.MAX_VALUE,
              DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
              new DelayedWorkQueue(), threadFactory);
    }

这个构造函数还是调用的父类的构造函数 ,核心线程数是构造是传入的,最大线程数是最大值,但是阻塞队列使用的是一种延时队列

  • 执行任务的方法
    有两种
    1.就是之前的普通执行方法,executor,传入 Runnable 接口
 public Future<?> submit(Runnable task) {
        return schedule(task, 0, NANOSECONDS);
    }

2.就是submit 方法 ,带有返回值的 ,传入 callable 接口

 public <T> Future<T> submit(Callable<T> task) {
        return schedule(task, 0, NANOSECONDS);
    }

综上,其实只列举两个简单的方法,但是这个线程池,普遍都是可以进行延时的,而且是可以有返回值的,两个任务接口都可以有返回值,这也是这个线程池的一大特点,是对原有线程池进行继承和扩展。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值