java多线程与线程池

1. 场景描述

以前多线程也常用,这次因需再页面上用到多线程,如下图,总结下,有需要的朋友可以参考下。

2. 解决方案

2.1 线程池概念

线程池官方定义不说了,通俗说下:池子的概念,事先(预定义)创建后,后续的线程可以直接从池子中拿,好处:

(1)来创建线程比较消耗资源,不用重复创建;

(2)池子事先定义好,避免无节制创建线程,导致系统出现不可预测风险。

2.2 创建方式

采用jdk自带的线程池创建方式,jdk1.5开始提供,在java.util.concurrent 的包下面。

表面上有两种创建方式,其实一种。

(1)一种是采用new ThreadPoolExecutor进行创建;

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {

(2)一种是采用Executors.newFixedThreadPool(3),不过还是调用的new ThreadPoolExecutor进行的线程池创建,赋值了几个默认参数而已。

new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());

2.3 参数含义

一共有 7 个参数:

(1)corePoolSize

核心线程数,当有任务进来的时候,如果当前线程数还未达到 corePoolSize 个数,则创建核心线程,核心线程有几个特点:

1、当线程数未达到核心线程最大值的时候,新任务进来,即使有空闲线程,也不会复用,仍然新建核心线程;2、核心线程一般不会被销毁,即使是空闲的状态,但是如果通过方法 allowCoreThreadTimeOut(boolean value) 设置为 true 时,超时也同样会被销毁;3、生产环境首次初始化的时候,可以调用 prestartCoreThread() 方法来预先创建所有核心线程,避免第一次调用缓慢;

(2)maximumPoolSize

除了有核心线程外,有些策略是当核心线程完全无空闲的时候,还会创建一些临时的线程来处理任务,maximumPoolSize 就是核心线程 + 临时线程的最大上限。临时线程有一个超时机制,超过了设置的空闲时间没有事儿干,就会被销毁。

(3)keepAliveTime

这个就是上面两个参数里所提到的超时时间,也就是线程的最大空闲时间,默认用于非核心线程,通过 allowCoreThreadTimeOut(boolean value) 方法设置后,也会用于核心线程。

(4)unit

这个参数配合上面的 keepAliveTime ,指定超时的时间单位,秒、分、时等。

(5)workQueue

等待执行的任务队列,如果核心线程没有空闲的了,新来的任务就会被放到这个等待队列中。

(6)threadFactory

它是一个接口,用于实现生成线程的方式、定义线程名格式、是否后台执行等等,可以用 Executors.defaultThreadFactory() 默认的实现即可,也可以用 Guava 等三方库提供的方法实现,如果有特殊要求的话可以自己定义。它最重要的地方应该就是定义线程名称的格式,便于排查问题了吧。

(7)handler

当没有空闲的线程处理任务,并且等待队列已满(当然这只对有界队列有效),再有新任务进来的话,就要做一些取舍了,而这个参数就是指定取舍策略的,有下面四种策略可以选择:

ThreadPoolExecutor.AbortPolicy:直接抛出异常,这是默认策略;

ThreadPoolExecutor.DiscardPolicy:直接丢弃任务,但是不抛出异常。

ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后将新来的任务加入等待队列

ThreadPoolExecutor.CallerRunsPolicy:由线程池所在的线程处理该任务,比如在 main 函数中创建线程池,如果执行此策略,将有 main 线程来执行该任务

2.4 测试验证

2.4.1 测试线程

package com.yutong.laowang.test;

public class ThreadTest extends Thread{
    @Override
    public void run() {
        System.out.println("软件老王:" +Thread.currentThread().getName());
  }
}

2.4.2 Executors创建

private static void test4() {
        Executor mExecutor = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 10; i++) {
            Thread thread = new ThreadTest();
            mExecutor.execute(thread);
        }
    }

结果:

软件老王:pool-2-thread-2
软件老王:pool-2-thread-3
软件老王:pool-2-thread-1
软件老王:pool-2-thread-3
软件老王:pool-2-thread-2
软件老王:pool-2-thread-1
软件老王:pool-2-thread-3
软件老王:pool-2-thread-2
软件老王:pool-2-thread-3
软件老王:pool-2-thread-1

2.4.3 ThreadPoolExecutor创建

private static void test3() {
        int poolSize = 5;
        int queueSize = 100;
        ExecutorService executorService = new ThreadPoolExecutor(poolSize, poolSize, 0L, TimeUnit.SECONDS,
                new ArrayBlockingQueue<Runnable>(queueSize), new ThreadPoolExecutor.AbortPolicy());
                
        for (int i=0;i<10;i++) {
            executorService.submit(new ThreadTest());
        }
    }

结果:

软件老王:pool-2-thread-1
软件老王:pool-2-thread-3
软件老王:pool-2-thread-4
软件老王:pool-2-thread-2
软件老王:pool-2-thread-5
软件老王:pool-2-thread-5
软件老王:pool-2-thread-1
软件老王:pool-2-thread-2
软件老王:pool-2-thread-2
软件老王:pool-2-thread-1

2.4.4 线程停用

thread.interrupt();
---有时候不一定能执行成功,一般会结合判断使用,软件老王,例如:
在ThreadTest类中进行判断,默认为false,当满足条件下为true,停用线程。
 public volatile boolean exit = false;

I’m 「软件老王」,如果觉得还可以的话,关注下呗,后续更新秒知!欢迎讨论区、同名公众号留言交流!

我来评几句
登录后评论

已发表评论数()

相关站点

热门文章