总结线程池

1. 创建线程池的原因

  使用了多进程确实能够进行并发编程,但是频繁创建销毁进程,成本比较高。因此我们引入了线程(轻量级进程)。复用资源的方式,来提高了创建销毁效率。

  随着创建销毁线程的频率进一步提升,开销仍然无法忽然不计了,由此我们就出现了下面的方法:

  1. 协程/纤程(轻量级线程)

  2. 线程池  (提前把要使用的线程,在线程池中准备好,需要用的时候就从池子里取,用完之后还给池子。这样子的代码属于纯用户态代码,就比从内核操作更快,更可控)

2. 线程池的参数介绍

2.1 int corePoolSize 和 int MaximumPoolSize

  我们来假设一个场景,这里我有正式员工和实习员工第一个参数为正式员工而第二个参数为正式员工和实习员工。现在是一个有一个紧急项目需要大家加班加点完成,可是就正式员工大概率完成不了,这个时候我们就需要找5个实习生来帮忙一起完成,而这个峰值已过就可以把实习生一脚踹开(注意这里一脚踹开,下次还需要实习生不是我们上次招过的而是全新的实习生,不存在“再次放回来的概念”,而且也不要把这俩个参数理解为最小值和最大值,在有些情况下并不合适)。

2.2 long keepAliveTime 和 TimeUnit unit

  这里我们第一个为保持存货时间,第二个为时间单位(s,min,ms,hour....)。这里我们还可以假设上述的场景,如果发现莫个实习生正在摸鱼(这个线程空闲),此时难道要立即马上把这个实习生开除吗?肯定是不行的,有可能刚一开出就又出现了峰值怎么办。因此,此处keepAliveTime意思是这个实习生线程空闲的时间(千万不能理解为这个实习生线程存货时间),空闲时间超过这个时间阈值,就会被直接销毁掉。

2.3 BlockingQueue workQueue

  这里可以看到 说明这里是使用Runnable来作为 描述主体的。而这个参数和定时类似,线程池中也可以持有很多个任务(也可以把BlockingQueue设置为PriorityBlockingQueue,带有优先级的)。

2.4 ThreadFactory threadFactory

   这里可以理解为是一个线程工厂,也是一个工厂模式,是一个常见的设计模式,通过专门的“工厂类”/“工厂对象” 来创建指定的对象,然后通过这个工厂类,来创建线程对象(Thread对象),在这个类里面提供了方法(也不一定非得是静态的)让方法封装new Thread的操作,并且同给Thread设置一些属性,构成了ThreadFactory线程工厂。

2.5 RejectedExecutionHandler handler

  这个也是我们八股文之一啦!!!它叫拒绝策略,在线程池中,有一个阻塞队列,能够容纳的元素是有上限的。当任务队列满了,如果还想继续往队列添加任务,那么线程会怎么办??下图就提供了几个方法:

总结线程池_第1张图片

  这里就提供了四个方法:1. 撂挑子,新任务旧任务都不执行了,继续添加任务,直接抛出异常。 2. 新的任务,由添加任务的线程负责执行。 3. 丢弃最老的任务,完成新的任务。 4. 丢弃最新的任务,还是继续完成旧任务。

3. 使用Executors

  ThreadPoolExecutor本身用起来比较复杂。因此标准库还提供了另一个版本,把ThreadPoolExecutor给封装了一下。成了Executors 工厂类,通过这个类创建出不同的线程对象(在内部把ThreadPoolExecutor 创建好了并且设置了不同的参数)。那这里就会出现一个问题什么时候用Execuors什么时候用ThreadPoolExecutor呢??这里也要分情况而定: 1. 如果你只简单使用的话就使用 Executors 2. 而希望高度定制话就使用ThreadPoolExecutor。 

  来简单使用一下Executors 如下:

package thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadDemo33 {
    public static void main(String[] args) {
        // 通过Executors来创建 5 个固定的线程
        ExecutorService service = Executors.newFixedThreadPool(5);
        for(int i = 0; i < 10; i++){
            // 内部类中的变量 要不能改变的 如果换成i 会报错
            int n = i;
            service.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("数" + n + "被线程" + Thread.currentThread().getName());
                }
            });
        }
    }
}

  要小心的点是如果把n换成i 会发生变量捕获,而此处的n 就是一个“实事final变量”,每次循环,都是一个新的n,n本身没有改变,就可以被捕获。还要注意的是,多个线程之间的执行顺序是不确定的,某个线程取到了任务,但是并非立即就执行,这个过程中另一个线程就插到前面了。 

你可能感兴趣的:(java-ee)