ExecutorService线程池详解

线程池理解

  • 对线程池 "池"的理解
    可以理解为工厂,如果工厂生产斧头,小王从工厂借了一把,当小王用完了,还给工厂,之后小李也可以借去用
    复用已有资源
    控制资源总量

你一个任务过来了,我发现池子里有没事干并且还活着的线程,来,拿去用,我也不用费事给你创建一条线程了,要知道线程的创建和销毁可都是麻烦事
你一个任务过来了,我发现池子的线程都在忙,并且现在池子的线程已经太多了,在不限制下去就要内存溢出了,来,排队去

线程池的作用

1、new Thread的弊端

new Thread(new Runnable() {
	@Override
	public void run() {
		System.out.println("--------");
	}
}).start();

a. 每次new Thread新建对象性能差:每次都要经历新建,就绪,运行,阻塞,死亡
b. 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom。
c. 缺乏更多功能,如定时执行、定期执行、线程中断。

2、相比new Thread,Java提供的四种线程池的好处在于:
a. 重用存在的线程,减少对象创建、消亡的开销,性能佳。
b. 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
c. 提供定时执行、定期执行、单线程、并发数控制等功能。

Executors介绍

通过Executors可以获得四种线程池
Executors
ExecutorService service = Executors.newCachedThreadPool();
//创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。池中物任务时,超过60秒,会自动关闭线程池。

ExecutorService service = Executors.newSingleThreadExecutor();
// 单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。(测试时都是先进先出的规则)

ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
//创建一个定长线程池,支持定时及周期性任务执行。ScheduledExecutorService为ExecutorService的子类

ExecutorService service = Executors.newFixedThreadPool(15);
//创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

实际项目中都不推荐者四种方式创建线程池,原因如下:

  • 阿里开发手册中对于 Java 线程池的使用规范
    在这里插入图片描述
  • 原因2:https://zhuanlan.zhihu.com/p/112527671

例子

  • 线程TestThread
public class TestThread extends Thread{

	@Override
	public void run() {
		try {
			Thread.sleep(1000);
			
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(System.currentTimeMillis()+"===="+ getPriority()+"----数据库操作---"+this.getName());
	}
}

  • 缓存线程池Executors.newCachedThreadPool();
ExecutorService service = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
    Thread td = new TestThread();
	service.execute(td);
}
  • 单例线程池Executors.newSingleThreadExecutor();
ExecutorService service = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
    Thread td = new TestThread();
	service.execute(td);
}
// 打印结果
// 1523331778779====5----数据库操作---Thread-0
// 1523331779780====5----数据库操作---Thread-1
// 1523331780780====5----数据库操作---Thread-2
// 1523331781780====5----数据库操作---Thread-3
// 1523331782780====5----数据库操作---Thread-4
// 1523331783780====5----数据库操作---Thread-5
// 1523331784780====5----数据库操作---Thread-6
// 1523331785780====5----数据库操作---Thread-7
// 1523331786781====5----数据库操作---Thread-8
// 1523331787781====5----数据库操作---Thread-9
  • 定时线程池Executors.newScheduledThreadPool(10);
ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
for (int i = 0; i < 10; i++) {
    Thread td = new TestThread();
	service.schedule(td, 5, TimeUnit.SECONDS); // 添加任务5秒之后执行
}
  • 定长线程池Executors.newFixedThreadPool(15);
ExecutorService service = Executors.newFixedThreadPool(15);
for (int i = 0; i < 10; i++) {
    Thread td = new TestThread();
	service.execute(td);
}

项目中的用法

package com.hesvit.common.task.execute;

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

/**
 * <p>
 * 线程池创建
 * <p>
 * @author liuping
 * @since 2017年12月26日
 */
public class TaskExecute {
	// 创建一个缓冲池,缓冲池容量大小为Integer.MAX_VALUE
	private static ExecutorService fixedThreadPool; 
	// 创建固定容量大小的缓冲池
	private static ExecutorService singleThreadExecutor;
	// 创建容量为1的缓冲池
	private static ExecutorService cachedThreadPool; 

	public static ExecutorService newFixedThreadPool(int nThreads) {
		if (fixedThreadPool == null)
			return Executors.newFixedThreadPool(nThreads);
		else
			return fixedThreadPool;
	}

	public static ExecutorService newSingleThreadExecutor() {
		if (singleThreadExecutor == null)
			return Executors.newSingleThreadExecutor();
		else
			return singleThreadExecutor;
	}

	public static ExecutorService newCachedThreadPool() {
		if (cachedThreadPool == null)
			return Executors.newCachedThreadPool();
		else
			return cachedThreadPool;
	}
	
	public static void execute(Runnable task) {
		newFixedThreadPool(6).execute(task);
	}
}

TaskExecute.execute(new RefreshAppUserThread());// 刷新app用户信息

// 线程
public class RefreshAppUserThread extends Thread {

	@Override
	public void run() {
		RedisUtil.refreshAppUser();
	}

}


import redis.clients.jedis.Jedis;
@Component
public class RedisUtil {

	/** 过时时间为2小时:2 * 60 *60 */
	private static int EXPIRE_2_HOUR = 7200;
	// 缓存所有App用户
	private static String ALL_APP_USERS = "all_app_users";
	
	
	public static List<User> refreshAppUser() {
		return refreshAppUser(null);
	}

	/**
	 * 刷新App用户列表
	 * liuping
	 */
	public static List<User> refreshAppUser(Jedis redis) {
		if (redis == null)
			redis = MyJedisPool.getJedisObject();// 获得jedis实例
		List<User> users = new ArrayList<>();
		try {
			User user = new User();
			user.setLimit(-1);
			WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext();
			UserService service = (UserService) context.getBean("userService");

			users = service.selectByIndex(user);

			if (null != users) {
				JSONArray jsonArray = JSONArray.fromObject(users);
				String struser = String.valueOf(jsonArray);
				redis.set(ALL_APP_USERS, struser);
				redis.expire(ALL_APP_USERS, EXPIRE_2_HOUR); // 设置过期时间
			}
		} catch (Exception e) {
			logger.error("Failed to get wxuser from redis: {}", e.getMessage());
		} finally {
			MyJedisPool.recycleJedisOjbect(redis);
		}
		return users;
	}

}

ThreadPoolExecutor 介绍

1-ThreadPoolExecutor 参数说明

参数说明形象文章: https://zhuanlan.zhihu.com/p/112527671

  • 花瓶解释: 瓶口-maximumPoolSize、瓶颈-workQueue、瓶底-corePoolSize

线程池中各个参数如何合理设置: https://blog.csdn.net/riemann_/article/details/104704197

public ThreadPoolExecutor(int corePoolSize,//核心线程数
                          int maximumPoolSize,//最大线程数
                          long keepAliveTime,//线程空闲时间
                          TimeUnit unit,//时间单位
                          BlockingQueue<Runnable> workQueue,//任务队列
                          ThreadFactory threadFactory,//线程工厂
                          RejectedExecutionHandler handler//拒绝策略) 
{
    ...
}
  • corePoolSize -线程池中保留的线程数,即使它们处于空闲状态
  • maximumPoolSize - 池中允许的最大线程数
  • keepAliveTime -当线程数大于内核时,这是多余空闲线程在终止前等待新任务的最大时间。
  • unit - keepAliveTime参数的时间单位
  • workQueue——用于在任务执行前保存任务的队列。此队列将仅保存由execute方法提交的可运行任务。
  • threadFactory —— 线程工厂,用来创建线程。
  • handler—— 当线程边界和队列容量达到时,执行被阻塞时使用的处理程序
拒绝策略,默认是AbortPolicy,会抛出异常。
当线程数已经达到maxPoolSize,且队列已满,会拒绝新任务。
当线程池被调用shutdown()后,会等待线程池里的任务执行完毕再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务。
AbortPolicy 丢弃任务,抛运行时异常。
CallerRunsPolicy 由当前调用的任务线程执行任务。[常用]
DiscardPolicy 忽视,什么都不会发生。
DiscardOldestPolicy 从队列中踢出最先进入队列(最后一个执行)的任务。

  • ThreadPoolExecutor和spring封装的ThreadPoolTaskExecutor案例
    ThreadPoolExecutor是Java的线程池
    ThreadPoolTaskExecutor是spring封装的线程池

2-线程池的执行顺序

线程池按以下行为执行任务

  • 当线程数小于核心线程数时,创建线程。
  • 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
  • 当线程数大于等于核心线程数,且任务队列已满,若线程数小于最大线程数,创建线程。
  • 若线程数等于最大线程数,则执行拒绝策略

3 线程池配置

package cc.nrbc.bschain.server.common.configure;

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 线程池配置
 *
 * @author Feny
 * @date Created in 2021/10/20
 */
@EnableAsync
@Configuration
public class ThreadPoolConfig {
    private final Integer corePool = 5;  //核心线程数
    private final Integer maxPool = 10;  //最大线程数
    private final Integer keepAlive = 30; //线程空闲时间
    private final Integer queueCapacity = 1000;
    private final RejectedExecutionHandler executionHandler = new ThreadPoolExecutor.CallerRunsPolicy();

    @Bean
    @Primary
    @Order(99)
    @ConditionalOnMissingBean(value = ThreadPoolTaskExecutor.class)
    public ThreadPoolTaskExecutor initExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // CPU核数  int i = Runtime.getRuntime().availableProcessors();
        // 核心线程数目 i * 2
        executor.setCorePoolSize(corePool);
        //指定最大线程数
        executor.setMaxPoolSize(maxPool);
        // 队列中最大的数目 i * 2 * 10
        executor.setQueueCapacity(queueCapacity);
        executor.setAwaitTerminationSeconds(60);
        // 线程空闲后的最大存活时间
        executor.setKeepAliveSeconds(keepAlive);
        // 线程名称前缀
        executor.setThreadNamePrefix("Primary-Async-");
        // 当调度器shutdown被调用时等待当前被调度的任务完成
        executor.setWaitForTasksToCompleteOnShutdown(true);
        /**
         * rejection-policy:当pool已经达到max size的时候,如何处理新任务
         * CALLER_RUNS:不在新线程中执行任务,而是由调用者所在的线程来执行对拒绝task的处理策略
         */
        executor.setRejectedExecutionHandler(executionHandler);
        //加载
        executor.initialize();
        return executor;
    }

    // 两个相同的bean 注入是需要加注解 @Qualifier("secondAsync")
    @Bean(name = "secondAsync")
    public ThreadPoolTaskExecutor secondAsync() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setMaxPoolSize(maxPool);
        executor.setCorePoolSize(corePool);
        executor.setAwaitTerminationSeconds(60);
        executor.setKeepAliveSeconds(keepAlive);
        executor.setQueueCapacity(queueCapacity);
        executor.setThreadNamePrefix("Second-Async-");
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.setRejectedExecutionHandler(executionHandler);
        return executor;
    }

    /*@Bean
    public ThreadPoolExecutor initThreadPoolExecutor() {
        return new ThreadPoolExecutor(corePool,
                maxPool,
                keepAlive,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(queueCapacity),  //任务队列
                executionHandler); //拒绝策略
    }*/



    @Bean
    public ThreadPoolExecutor initThreadPoolExecutor() {
        //获取cpu核心数
        int i = Runtime.getRuntime().availableProcessors();
        //核心线程数
        int corePoolSize = i * 2;
        //最大线程数
        int maximumPoolSize = i * 2;
        //线程无引用存活时间
        long keepAliveTime = 60;
        //时间单位
        TimeUnit unit = TimeUnit.SECONDS;
        //任务队列,接收一个整型的参数,这个整型参数指的是队列的长度,
        //ArrayBlockingQueue(int,boolean),boolean类型的参数是作为可重入锁的参数进行初始化,默认false,另外初始化了notEmpty、notFull两个信号量。
        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue(i * 2 * 10);
        /**
         * 1. 同步阻塞队列 (put,take),直接提交。直接提交策略表示线程池不对任务进行缓存。新进任务直接提交给线程池,当线程池中没有空闲线程时,创建一个新的线程处理此任务。
         * 这种策略需要线程池具有无限增长的可能性。实现为:SynchronousQueue
         * 2. 有界队列。当线程池中线程达到corePoolSize时,新进任务被放在队列里排队等待处理。有界队列(如ArrayBlockingQueue)有助于防止资源耗尽,
         * 但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,
         * 但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O 边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,
         * CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。
         * 3. 无界队列。使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。
         * 这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,
         * 适合于使用无界队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
         */

        /**
         * 线程工厂
         * defaultThreadFactory() 返回用于创建新线程的默认线程工厂。
         * privilegedThreadFactory() 返回一个用于创建与当前线程具有相同权限的新线程的线程工厂。
         */
        // ThreadFactory threadFactory =Executors.defaultThreadFactory();
        //创建线程池
        // return new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
        return new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);  // 默认线程工厂
    }
}

4 线程池使用

@Api(tags = "买车")
@Slf4j
@Validated
@RestController
@RequestMapping("buyCar")
@RequiredArgsConstructor
public class BuyCarController {

    private final ThreadPoolExecutor threadPool;


    private static final ThreadPoolExecutor pool = new ThreadPoolExecutor(16,
            32,
            60,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(100),  //任务队列
            new ThreadPoolExecutor.CallerRunsPolicy()); //拒绝策略

    public static void main(String[] args) throws Exception {
        submitTest();
    }
    /**
     *
     * 1 注释掉 future.get() 打印结果:
     *     主线程执行完成
     *     进入 getName()
     *     getName()执行完成
     * 2 执行 future.get() 打印结果:
     *     进入 getName()
     *     getName()执行完成
     *     主线程执行完成
     * 由此可见,submit.get(),进行了线程阻塞,获取到结果之后才往下执行    
     */
    public static void submitTest() throws Exception {
        String name = "恰恰";
        Future<String> future = pool.submit(() -> getName(name));
        /*
         * 跟future 一样
         Future<String> future2 =pool.submit(new Callable<String>() {
            @Override
            public String call()  {
                try {
                    Thread.sleep(2000L);
                } catch (InterruptedException e) {
                }
            }
        });*/

        future.get();
        System.out.println("主线程执行完成");
    }

    public static String getName(String name) {
        System.out.println("进入 getName()");
        try {
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
        }
        System.out.println("getName()执行完成");
        return "我是" + name;
    }
    

    /**
     * 获取用车记录信息
     * threadPool.submit() 异步获取返回数据
     * 通过get() 获取异步返回数据
     */
    @GetMapping("getUseCarRecord")
    public NrbcResponse getUseCarRecord(@NotNull(message = "车辆id不能为空") @RequestParam Long carInfoId) throws Exception {
        UseCarRecordResponse response = new UseCarRecordResponse();
        // 异步获取数据
        Future<List<CarClaimRecord>> claimRecord = threadPool.submit(() -> this.getClaimRecord(carInfoId));
        Future<List<MileageResponse>> mileageRecord = threadPool.submit(() -> this.getMileageRecord(carInfoId));
        response.setClaimRecord(claimRecord.get());  // claimRecord.get(),get()之前进行异步阻塞,有数据返回才往下执行
        response.setMileageRecord(mileageRecord.get());
        return new NrbcResponse().data(response);
    }

    /**
     * 获取理赔数据
     *
     * @param carInfoId 车辆信息id
     * @author Feny
     * @date 2021/10/19
     */
    private List<CarClaimRecord> getClaimRecord(Long carInfoId) {
        List<CarClaimRecord> list = new ArrayList<>();
        // 业务处理......
        // list = 数据库.获取列表
        return list;
    }

    private List<MileageResponse> getMileageRecord(Long carInfoId) {
        List<MileageResponse> list = new ArrayList<>();
        // 业务处理......
        // list = 数据库.获取列表
        return list;
    }
}

5 ThreadPoolExecutor有两种提交方法execute和submit:

  • 无返回值的任务使用public void execute(Runnable command) 方法提交;

  • 有返回值的任务使用public Future submit(Callable) 方法提交。
    Future.get()之前进行异步阻塞,数据返回后才往下执行

6 CountDownLatch 线程计数器

  • 一、介绍
    CountDownLatch 是 Java 中的一个并发工具类,用于协调多个线程之间的同步。其作用是让某一个线程等待多个线程的操作完成之后再执行。它可以使一个或多个线程等待一组事件的发生,而其他的线程则可以触发这组事件。

  • 二、特性

  1. CountDownLatch 可以用于控制一个或多个线程等待多个任务完成后再执行。
  2. CountDownLatch 的计数器只能够被减少,不能够被增加。
  3. CountDownLatch 的计数器初始值为正整数,每次调用 countDown() 方法会将计数器减 1,计数器为 0 时,等待线程开始执行。
  • demo
private static final ThreadPoolExecutor pool = new ThreadPoolExecutor(16,
            32,
            60,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(100),  //任务队列
            new ThreadPoolExecutor.CallerRunsPolicy()); //拒绝策略

    public static void main(String[] args) throws Exception {
//        submitTest();
        submitTest2();
    }

    public static void submitTest2() throws Exception {

        // orderList 即数据订单列表
        List<Integer> orderList = new ArrayList<>();
        for (int i=0;i<10;i++) {
            orderList.add(i);
        }

        List<Future<String>> futureList = new ArrayList<>();
        CountDownLatch latch = new CountDownLatch(orderList.size());
        for (int i=0;i<10;i++) {
            String name = "order_"+i;
            Future<String> future = pool.submit(() -> orderHander(name, latch));
//            Future<String> future = pool.submit(() -> orderHander(name));
            futureList.add(future);
        }

        latch.await();

        for (Future<String> temp : futureList) {
            String s = temp.get();
            System.out.println("打印Future>:" + s);
        }

        System.out.println("主线程执行完成");
    }


//    public static String orderHander(String order) {
    public static String orderHander(String order, CountDownLatch latch) {
        System.out.println("进入 orderHander()"+order);
        try {
            Thread.sleep(500L);
        } catch (InterruptedException e) {
        }
        if (Objects.equals("order_3", order))
            throw new RuntimeException("订单异常");
//        finally {
            // 一定要在finally执行,如果不在finally执行,抛了异常,计数器不归0,主线程一直阻塞
            latch.countDown();
//        }
        System.out.println("orderHander()执行完成" + order);
        return "订单" + order;
    }
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值