第二十七章 线程池ThreadPool基本使用

2020-07-14  本文已影响0人  唔笛plk

转载于https://juejin.im/post/5f059e53f265da230a123cab

一、适用场景:

为突然大量爆发的线程设计的,通过有限固定线程提高线程的利用效率,减少了创建和销毁线程所需的时间,方便管理线程。 ThreadPool适于并发运行和运行时间不长且互不干扰的函数。

二、不适用场景:

二、作用&优势

1.线程复用

三、线程池的使用

1.ThreadPoolExecutor是线程池的真正实现类,通过ThreadPoolExecutor来创建一个线程池

// 通过构造方法&配置核心参数,创建线程池对象
Executor threadPool = new ThreadPoolExecutor( CORE_POOL_SIZE , MAXIMUM_POOL_SIZE , 
                                            KEEP_ALIVE , TimeUnit.SECONDS,  
                                            sPoolWorkQueue , sThreadFactory );
//构造函数源码分析
new ThreadPoolExecutor(int corePoolSize,    //核心线程数
                       int maximumPoolSize, //线程池最大线程数
                       long keepAliveTime,  //线程活动保持时间
                       TimeUnit unit,       //线程活动保持时间单位
                       BlockingQueue<Runnable> workQueue,  //任务队列
                       ThreadFactory threadFactory);  //线程工厂

2.线程池参数说明

3.线程池执行

a.使用execute提交任务,execute方法没有返回值,所以无法判断任务是否被线程池执行成功

//向线程池提交任务:execute()
threadPool.execute(new Runnable() {
    @Override
    public void run() {
        ... // 线程执行任务
       }
});

b.使用submit()方法来提交任务,它会返回一个future,那么可以通过future来判断任务是否执行成功,future的get方法获取返回值,get方法会阻塞直到任务完成

Future<Object> future = threadPool.submit(harReturnValuetask);
try {
     Object o = future.get();
} catch (InterruptedException e) {
    // 处理中断异常
} catch (ExecutionException e) {
    // 处理无法执行任务异常
} finally {
    // 关闭线程池
    threadPool.shutdown();
}

4.线程池关闭

//关闭线程池shutdown() 
threadPool.shutdown();
//或者threadPool.shutdownNow(); 

a.原理:

通过调用线程池的shutdown或shutdownNow方法来关闭线程池,它们的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止

b.区别:

shutdownNow首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表; shutdown只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。

c.选择:

3)选择
只要调用了这两个关闭方法的其中一个,isShutdown方法就会返回true。当所有的任务都已关闭后,才表示线程池关闭成功,这时调用isTerminaed方法会返回true。至于我们应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用shutdown()来关闭线程池,如果任务不一定要执行完,则可以调用shutdownNow()来关闭线程池。

四、合理的配置线程池

1.任务的性质:CPU密集型任务、IO密集型任务和混合型任务。
2.任务的优先级:高、中和低。
3.任务的执行时间:长、中和短。
4.任务的依赖性:是否依赖其他系统资源,如数据库连接。

corePoolSize = Runtime.getRuntime().availableProcessors() + 1
maximumPoolSize = Runtime.getRuntime().availableProcessors() * 2 + 1 

五、工作流程

QQ截图20200714104347.jpg

当线程池中的线程数量 > 核心线程数(corePoolSize)时,若某线程(非核心线程)的空闲时间 > 闲置超时时长(keepAliveTime) 线程将被终止。通过这样的策略,线程池可动态调整池中的线程数。

六、常见线程池分类

1.定长线程池(FixedThreadPool)

特点:只有核心线程 & 不会被回收、线程数量固定、任务队列无大小限制(超出的线程任务会在队列中等待)
应用场景:控制线程最大并发数
具体使用:通过Executors.newFixedThreadPool()创建

// 1. 创建定长线程池对象 & 设置线程池线程数量固定为3
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);

// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task =new Runnable(){
  public void run(){
    System.out.println("执行任务啦");
     }
    };

// 3. 向线程池提交任务:execute()
fixedThreadPool.execute(task);

// 4. 关闭线程池
fixedThreadPool.shutdown();
2.定时线程池(ScheduledThreadPool )

特点:核心线程数量固定、非核心线程数量无限制(闲置时马上回收)
应用场景:执行定时 / 周期性 任务
使用:通过Executors.newScheduledThreadPool()创建
示例:

// 1. 创建 定时线程池对象 & 设置线程池线程数量固定为5
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);

// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task =new Runnable(){
       public void run(){
              System.out.println("执行任务啦");
          }
    };
// 3. 向线程池提交任务:schedule()
scheduledThreadPool.schedule(task, 1, TimeUnit.SECONDS); // 延迟1s后执行任务
scheduledThreadPool.scheduleAtFixedRate(task,10,1000,TimeUnit.MILLISECONDS);// 延迟10ms后、每隔1000ms执行任务
// 4. 关闭线程池
scheduledThreadPool.shutdown();
3.可缓存线程池(CachedThreadPool)

特点:只有非核心线程、线程数量不固定(可无限大)、灵活回收空闲线程(具备超时机制,全部回收时几乎不占系统资源)、新建线程(无线程可用时);任何线程任务到来都会立刻执行,不需要等待
应用场景:执行大量、耗时少的线程任务
使用:通过Executors.newCachedThreadPool()创建
示例:

// 1. 创建可缓存线程池对象
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task =new Runnable(){
  public void run(){
        System.out.println("执行任务啦");
    }
  };

// 3. 向线程池提交任务:execute()
cachedThreadPool.execute(task);

// 4. 关闭线程池
cachedThreadPool.shutdown();

//当执行第二个任务时第一个任务已经完成
//那么会复用执行第一个任务的线程,而不用每次新建线程。

4.单线程化线程池(SingleThreadExecutor)

特点:只有一个核心线程(保证所有任务按照指定顺序在一个线程中执行,不需要处理线程同步的问题)
应用场景:不适合并发但可能引起IO阻塞性及影响UI线程响应的操作,如数据库操作,文件操作等
使用:通过Executors.newSingleThreadExecutor()创建
示例:

// 1. 创建单线程化线程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();

// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task =new Runnable(){
  public void run(){
        System.out.println("执行任务啦");
    }
  };

// 3. 向线程池提交任务:execute()
singleThreadExecutor.execute(task);

// 4. 关闭线程池
singleThreadExecutor.shutdown();

六、线程池的监控

通过线程池提供的参数进行监控。线程池里有一些属性在监控线程池的时候可以使用。

taskCount:线程池需要执行的任务数量。
completedTaskCount:线程池在运行过程中已完成的任务数量。小于或等于taskCount。
largestPoolSize:线程池曾经创建过的最大线程数量。通过这个数据可以知道线程池是否满过。如等于线程池的最大大小,则表示线程池曾经满了。
getPoolSize:线程池的线程数量。如果线程池不销毁的话,池里的线程不会自动销毁,所以这个大小只增不减。
getActiveCount:获取活动的线程数。

通过继承线程池并重写线程池的beforeExecute,afterExecute和terminated方法,我们可以在任务执行前,执行后和线程池关闭前干一些事情。如监控任务的平均执行时间,最大执行时间和最小执行时间等。这几个方法在线程池里是空方法。如:

protected void beforeExecute(Thread t, Runnable r) { }
上一篇 下一篇

猜你喜欢

热点阅读