线程池的创建方式

线程池的工作原理
线程池可以减少创建和销毁线程的次数,从而减少系统资源的消耗,当一个任务提交到线程池时
a. 首先判断核心线程池中的线程是否已经满了,如果没满,则创建一个核心线程执行任务,否则进入下一步
b. 判断工作队列是否已满,没有满则加入工作队列,否则执行下一步
c. 判断线程数是否达到了最大值,如果不是,则创建非核心线程执行任务,否则执行饱和策略,默认抛出异常

线程池的创建⽅法总共有 7 种,但总体来说可分为 2 类:

通过 Executors 创建(6种)
通过 ThreadPoolExecutor 创建
方式 作用
Executors.newFixedThreadPool 创建⼀个固定⼤⼩的线程池,可控制并发的线程数,超出的线程会在队列中等待
Executors.newCachedThreadPool 创建⼀个可缓存的线程池,若线程数超过处理所需,缓存⼀段时间后会回收,若线程数不够,则新建线程
Executors.newSingleThreadExecutor 创建单个线程数的线程池,它可以保证先进先出的执⾏顺序
Executors.newScheduledThreadPool 创建⼀个可以执⾏延迟任务的线程池
Executors.newSingleThreadScheduledExecutor 创建⼀个单线程的可以执⾏延迟任务的线程池
Executors.newWorkStealingPool 创建⼀个抢占式执⾏的线程池(任务执⾏顺序不确定)【JDK1.8 添加】
ThreadPoolExecutor 最原始的创建线程池的⽅式,它包含了 7 个参数可供设置
  1. FixedThreadPool 创建⼀个固定⼤⼩的线程池,可控制并发的线程数,超出的线程会在队列中等待
	private static void main(String[] args) {

		ExecutorService fixedThreadPool = Executors.newFixedThreadPool(2);//创建的线程数

		System.out.println(Thread.currentThread().getName() + " , thread id: " + Thread.currentThread().getId());

		fixedThreadPool.submit(() -> System.out.println(Thread.currentThread().getName() + " , thread id: " + Thread.currentThread().getId()));

		fixedThreadPool.submit(() -> System.out.println(Thread.currentThread().getName() + " , thread id: " + Thread.currentThread().getId()));

		// 注释1
//		fixedThreadPool.submit(() -> System.out.println(Thread.currentThread().getName() + " , thread id: " + Thread.currentThread().getId()));


	}

	
	执行结果
	main , thread id: 1
	pool-1-thread-1 , thread id: 22
	pool-1-thread-2 , thread id: 23
	
	当注释1处放开执行该代码时,打印的结果是:
	main , thread id: 1
	pool-1-thread-1 , thread id: 22
	pool-1-thread-2 , thread id: 23
	pool-1-thread-1 , thread id: 22

  • 返回结果

      **使用submit可以执行有返回值的任务或者是无返回值的任务;而execute只能执行不带返回值的任务。** 
      
       Future < T > submit(Callable < T > task);
       Future < T > submit(Runnable task, T result);
      Future submit (Runnable task);
    
	private static void main(String[] args) {
		ExecutorService fixedThreadPool = Executors.newFixedThreadPool(2);//创建的线程数
		Future<Integer> submit = fixedThreadPool.submit(() -> {
			int nextInt = new Random().nextInt(30);
			System.out.println("nextInt:" + nextInt);
			return nextInt;
		});

		try {
			System.out.println("submit:" + submit.get());
		} catch (InterruptedException e) {
			throw new RuntimeException(e);
		} catch (ExecutionException e) {
			throw new RuntimeException(e);
		}
	}
输出结果
nextInt:25
submit:25

  • ⾃定义线程池名称或优先级
	private static void main(String[] args) {
		// 创建线程工厂
		ThreadFactory threadFactory = r -> {
			//注意:要把任务Runnable设置给新创建的线程
			Thread thread = new Thread(r);
			//设置线程的命名规则
			thread.setName("张三的线程:" + r.hashCode());
			//设置线程的优先级
			thread.setPriority(Thread.MAX_PRIORITY);
			return thread;
		};
		ExecutorService threadPool = Executors.newFixedThreadPool(2, threadFactory);
		//执行任务1
		Future<Integer> result = threadPool.submit(() -> {
			int num = new Random().nextInt(10);
			System.out.println(Thread.currentThread().getPriority() + ",  name: " + Thread.currentThread().getName() + ", 随机数:" + num);
			return num;
		});
		//打印线程池返回结果
		try {
			System.out.println("返回结果:" + result.get());
		} catch (InterruptedException e) {
			throw new RuntimeException(e);
		} catch (ExecutionException e) {
			throw new RuntimeException(e);
		}
    }
    
	输出结果:
	10,  name: 张三的线程:682376643, 随机数:3
	返回结果:3
  1. newCachedThreadPool 创建⼀个可缓存的线程池,若线程数超过处理所需,缓存⼀段时间后会回收,若线程数不够,则新建线程
    优点:线程池会根据任务数量创建线程池,并且在一定时间内可以重复使用这些线程,产生相应的线程池。
    缺点:适用于短时间有大量任务的场景,它的缺点是可能会占用很多的资源。
    使用场景:适合短时间内有突发大量任务的处理场景。
	public static void main(String[] args) {
		ExecutorService executorService = Executors.newCachedThreadPool();
		for (int i = 0; i < 15; i++) {
			int finalI = i;
			executorService.submit(() -> {
				System.out.println("i : " + finalI + ", 线程名称:" + Thread.currentThread().getName());
			});
		}
	}
	输出结果:
	i : 0, 线程名称:pool-1-thread-1
	i : 7, 线程名称:pool-1-thread-8
	i : 6, 线程名称:pool-1-thread-7
	i : 5, 线程名称:pool-1-thread-6
	i : 4, 线程名称:pool-1-thread-5
	i : 2, 线程名称:pool-1-thread-3
	i : 3, 线程名称:pool-1-thread-4
	i : 1, 线程名称:pool-1-thread-2
	i : 12, 线程名称:pool-1-thread-13
	i : 11, 线程名称:pool-1-thread-12
	i : 10, 线程名称:pool-1-thread-11
	i : 9, 线程名称:pool-1-thread-10
	i : 8, 线程名称:pool-1-thread-9
	i : 14, 线程名称:pool-1-thread-15
	i : 13, 线程名称:pool-1-thread-14	
  1. newSingleThreadExecutor 创建单个线程数的线程池,它可以保证先进先出的执⾏顺序
    public static void main(String[] args) {
		ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
		for (int i = 0; i < 5; i++) {
			int finalI = i;
			singleThreadExecutor.execute(() -> {
				try {
					Thread.sleep(1000);
					System.out.println("i:" + finalI + " , time:" + LocalDateTime.now() + " , name:" + Thread.currentThread().getName());
				} catch (InterruptedException e) {
					throw new RuntimeException(e);
				}
			});
		}
   }
	输出结果:
	i:0 , time:2023-06-14T09:57:42.504660900 , name:pool-1-thread-1
	i:1 , time:2023-06-14T09:57:43.513563300 , name:pool-1-thread-1
	i:2 , time:2023-06-14T09:57:44.515762300 , name:pool-1-thread-1
	i:3 , time:2023-06-14T09:57:45.524664800 , name:pool-1-thread-1
	i:4 , time:2023-06-14T09:57:46.532360900 , name:pool-1-thread-1

  1. newScheduledThreadPool 创建⼀个可以执⾏延迟任务的线程池
  • 延迟执行任务,并执行一次
		ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
		System.out.println("添加的时间    :" + LocalDateTime.now());
		//执行定时任务(延迟3s执行)只执行一次
		service.schedule(new Runnable() {
			@Override
			public void run() {
				System.out.println("执行子任务时间 :" + LocalDateTime.now());
			}
		}, 3, TimeUnit.SECONDS);
		
		输出结果:
		添加的时间    :2023-06-14T10:06:08.407704800
		执行子任务时间 :2023-06-14T10:06:11.413525800

  • 以一定频率执行,多次
	public static void main(String[] args) {
		ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
		System.out.println("添加的时间    :" + LocalDateTime.now());
		//2s之后开始执行定时任务,定时任务每隔2s执行一次
		scheduledThreadPool.scheduleAtFixedRate(() -> System.out.println("执行子任务时间 :" + LocalDateTime.now()), 3, 2, TimeUnit.SECONDS);

		try {
			Thread.sleep(60000);
			scheduledThreadPool.shutdown();//任务停止
		} catch (InterruptedException e) {
			throw new RuntimeException(e);
		}
	}
	输出结果:
	添加的时间    :2023-06-14T10:14:10.654523300
	执行子任务时间 :2023-06-14T10:14:13.667103
	执行子任务时间 :2023-06-14T10:14:15.659165900
	执行子任务时间 :2023-06-14T10:14:17.664069700
	执行子任务时间 :2023-06-14T10:14:19.662755200
	执行子任务时间 :2023-06-14T10:14:21.660791400
	执行子任务时间 :2023-06-14T10:14:23.670349700
	执行子任务时间 :2023-06-14T10:14:25.671597300
	执行子任务时间 :2023-06-14T10:14:27.656823800
	执行子任务时间 :2023-06-14T10:14:29.662409500
	.......

scheduleAtFixedRate VS scheduleWithFixedDelay区别

  • scheduleAtFixedRate 是以上⼀次任务的开始时间,作为下次定时任务的参考时间的(参考时间+延迟任务=任务执⾏)。
  • scheduleWithFixedDelay 是以上⼀次任务的结束时间,作为下次定时任务的参考时间的。
  1. newSingleThreadScheduledExecutor 创建⼀个单线程的可以执⾏延迟任务的线程池
  • 单线程的定时任务
	public static void main(String[] args) {
		ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
		System.out.println("添加任务的时间:" + LocalDateTime.now());
		service.schedule(new Runnable() {
			@Override
			public void run() {
				System.out.println("执行任务的时间:" + LocalDateTime.now()+" , 线程名:" + Thread.currentThread().getName());
			}
		},2, TimeUnit.SECONDS );
	}
	输出结果:
	添加任务的时间:2023-06-14T10:25:25.332814
	执行任务的时间:2023-06-14T10:25:27.347112700 , 线程名:pool-1-thread-1

  • 单线程
	public static void main(String[] args) {
		ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
		System.out.println("添加任务的时间:" + LocalDateTime.now());
		for (int i = 0; i < 5; i++) {
			service.submit(new Runnable() {
				@Override
				public void run() {
					System.out.println("执行任务的时间:" + LocalDateTime.now() + " , 线程名:" + Thread.currentThread().getName());
				}
			});
		}
	}
	输出结果:
	添加任务的时间:2023-06-14T10:28:27.596905300
	执行任务的时间:2023-06-14T10:28:27.598899 , 线程名:pool-1-thread-1
	执行任务的时间:2023-06-14T10:28:27.598899 , 线程名:pool-1-thread-1
	执行任务的时间:2023-06-14T10:28:27.598899 , 线程名:pool-1-thread-1
	执行任务的时间:2023-06-14T10:28:27.598899 , 线程名:pool-1-thread-1
	执行任务的时间:2023-06-14T10:28:27.598899 , 线程名:pool-1-thread-1
  1. newWorkStealingPool 创建⼀个抢占式执⾏的线程池(任务执⾏顺序不确定)【JDK1.8 添加】
	public static void main(String[] args) {
		ExecutorService newWorkStealingPool = Executors.newWorkStealingPool();
		for (int i = 0; i < 10; i++) {
			newWorkStealingPool.submit(() -> {
				System.out.println("线程名:" + Thread.currentThread().getName());
			});
			//确保任务执行完成
			while (!newWorkStealingPool.isTerminated()) {
			}
		}
	}
	输出结果:
	线程名:ForkJoinPool-1-worker-19
  1. ThreadPoolExecutor 最原始的创建线程池的⽅式,它包含了 7 个参数可供设置

参数

  • corePoolSize 线程池核心线程数,也是线程池中常驻的线程数,线程池初始化时默认是没有线程的,当任务来临时才开始创建线程去执行任务,即使这些线程处理空闲状态,他们也不会被销毁,除非设置了allowCoreThreadTimeOut。这里的最小线程数量即是corePoolSize。任务提交到线程池后,首先会检查当前线程数是否达到了corePoolSize,如果没有达到的话,则会创建一个新线程来处理这个任务。

  • maximumPoolSize 线程池最大线程数量
    最大线程数,在核心线程数的基础上可能会额外增加一些非核心线程,需要注意的是只有当workQueue队列填满时才会创建多于corePoolSize的线程(线程池总线程数不超过maxPoolSize)

  • keepAliveTime 空闲线程存活时间
    非核心线程的空闲时间超过keepAliveTime就会被自动终止回收掉 注意当corePoolSize=maxPoolSize时,keepAliveTime参数也就不起作用了(因为不存在非核心线程);

  • unit 空闲线程存活时间单位 keepAliveTime的计量单位

  • workQueue 工作队列
    新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务。jdk中提供了四种工作队:ArrayBlockingQueue、LinkedBlockingQuene、SynchronousQuene、PriorityBlockingQueue

  • threadFactory 线程工厂
    创建一个新线程时使用的工厂,默认使用Executors.defaultThreadFactory(),也可以使用guava库的ThreadFactoryBuilder来创建,可以用来设定线程名、是否为daemon线程等等

  • handler 拒绝策略
    当工作队列中的任务已到达最大限制,并且线程池中的线程数量也达到最大限制,这时如果有新任务提交进来,该如何处理呢。这里的拒绝策略,就是解决这个问题的,jdk中提供了4中拒绝策略:CallerRunsPolicy、AbortPolicy、DiscardPolicy、DiscardOldestPolicy

源码:

    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)
            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;
    }

使用

public final class ThreadPoolManager {
	//线程池中最少的线程的数量
	private static final int CORE_POOL_SIZE = 8; 
	//线程池中最多的线程的数量
    private static final int MAXIMUM_POOL_SIZE = 15; 
    //空闲线程的最长存活时间
    private static final int KEEP_ALIVE = 10; 
    //工作队列-阻塞队列
    private static final BlockingQueue<Runnable> mWorkQueue = new LinkedBlockingQueue<Runnable>(MAXIMUM_POOL_SIZE);
	//线程池中线程的创建工厂
    private static final ThreadFactory mThreadFactory = new ThreadFactory() { 
        //原子操作级别的整数,初始值是1
		private final AtomicInteger mCount = new AtomicInteger(1); 
 
		@Override
        public Thread newThread(Runnable r) {
            PcmRecorder.bandCpu();
        	Thread thread = new Thread(r, "Task #" + mCount.getAndIncrement()); //线程名,前缀固定,后缀自增长
        	thread.setPriority(Thread.NORM_PRIORITY - 1);   //正常优先级
            return thread; 
        } 
    }; 
    //线程池中线程的执行的管理器
	public static final ExecutorService EXECUTOR = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, 
																			 KEEP_ALIVE, TimeUnit.SECONDS, 
																			 mWorkQueue, mThreadFactory, 
																			 new ThreadPoolExecutor.DiscardPolicy());
}


调用
ThreadPoolManager.EXECUTOR.submit(()->{
//todo someting  
//通过handler进行线程间通信
});

封装好的线程池链接:
妈妈再也不用担心你不会使用线程池了(ThreadUtils)

你可能感兴趣的:(java,Android,线程池,Executors)