Java线程池

36 阅读6分钟

一. 线程池

1.1 线程池概述

什么是线程池?

  • 线程池它是一种池化技术,像线程池、连接池、内存池、对象池这些都是属于池化技术的应用。
  • 线程池就是一个可以复用线程的技术,一个线程可以反复使用,以此提高了系统的整体性能。
  • 创建线程池,复用线程可以避免突然创建很多线程而引起资源耗尽的风险,所以线程池的优点是非常突出的。

线程池存在的意义:

        系统创建一个线程的成本是比较高的,因为它涉及到与操作系统交互,当程序中需要创建大量生存期很短暂的线程时,频繁的创建和销毁线程对系统的资源消耗有可能大于业务处理时对系统资源的消耗,这样就有点"舍本逐末"了。针对这一种情况,为了提高性能,我们就可以采用线程池。线程池在启动的时,会创建大量空闲线程,当我们向线程池提交任务的时,线程池就会启动一个线程来执行该任务。等待任务执行完毕以后,线程并不会死亡,而是再次返回到线程池中称为空闲状态,等待下一次任务的执行。

不使用线程池的问题

  • 如果用户每发起一个请求,后台就创建一个新线程来处理,下次新任务来了又要创建新线程,而创建新线程的开销是很大的(占用内存空间,并且竞争CPU资源) ,这样会严重影响系统的性能!

  1. 线程池使用固定的核心线程,去重复利用这些线程去解决问题,可以提高系统的整体性能,也不容易引起资源的耗尽!
  2. 线程能够处理的任务有Runnable任务和Callable任务。
  3. 核心线程:固定的线程,代表长久不死亡的线程,也叫核心线程 / 工作线程!
  4. 核心线程可以理解成正式工,临时线程 / 空闲线程可以理解为临时工!

1.2 线程池实现的API、参数说明

  • JDK5.0起提供了代表线程池的接口:ExecutorService(执行服务)
  • ExecutorService就代表线程池,它是线程池的一个规范!

  • ExecutorService是接口,不能直接创建对象,要使用ExecutorService的实现类ThreadPoolExecutor(线程池执行器) 创建一个线程池对象!

ThreadPoolExecutor构造器的参数说明(重点!)

  • ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(核心线程数量,最大线程数量,空闲线程最大存活时间,任务队列,创建线程工厂,任务的拒绝策略);
package com.itheima.mythreadpool;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class MyThreadPoolDemo3 {
//    参数一:核心线程数量
//    参数二:最大线程数
//    参数三:空闲线程最大存活时间
//    参数四:时间单位
//    参数五:任务队列
//    参数六:创建线程工厂
//    参数七:任务的拒绝策略
    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(2,5,2,TimeUnit.SECONDS,new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());

        pool.shutdown();
    }
}

参数5:workQueue:指定任务队列,任务的阻塞队列,被提交但是尚未被执行的任务。

参数7:handler:线程饱和策略/任务的拒绝策略,线程池中所有线程都在执行任务,并且等待队                               列也满了的情况下,新任务来了指定的处理方法。

 线程池常见面试题

1. 临时线程什么时候创建?

  • 新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程

2. 什么时候会开始拒绝任务?

  • 核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝!

1.3 线程池处理Runnable任务

package com.gch.d8_threadpool;

/**
   任务类
 */
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "输出了 HelloWorld===>" + i);
        }
        try {
            // 每个线程霸占一个任务
            System.out.println(Thread.currentThread().getName() + "该线程与本任务绑定了,线程进入休眠了~~~");
            Thread.sleep(100000);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
package com.gch.d8_threadpool;

import java.util.concurrent.*;

/**
   目标:自定义一个线程池对象,并测试其特性
 */
public class ThreadPoolDemo1 {
    public static void main(String[] args) {
        // 核心线程可以理解为正式工  临时线程 / 空闲线程可以理解为临时工
        // 1.创建线程池对象
        /* public ThreadPoolExecutor(
        int corePoolSize,  核心线程的数量
        int maximumPoolSize, 最大线程数量
        long keepAliveTime, 临时线程 / 空闲线程的等待时间,指的是临时线程一旦没有任务处理了,在哪里空闲多长时间的意思
        TimeUnit unit, 存活时间的单位
        BlockingQueue<Runnable> workQueue, 指定任务队列,任务的阻塞队列,先把没有处理的任务缓存起来
        ThreadFactory threadFactory, 默认的线程工厂,指定用哪个线程工厂创建线程
        RejectedExecutionHandler handler 任务的拒绝策略,指定线程忙,任务满的时候线程来了怎么办
        )*/
        ExecutorService pool = new ThreadPoolExecutor(3,5,6,
                TimeUnit.SECONDS,new ArrayBlockingQueue<>(5),Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

        // 2.给任务线程池处理
        Runnable target = new MyRunnable();
//        void execute(Runnable command);
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);

        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);

        // 创建临时线程
        pool.execute(target);
        pool.execute(target);
        // 不创建线程,拒绝策略被触发
//        pool.execute(target); // RejectedExecutionException

        // 关闭线程池(开发中一般不会使用)。
        pool.shutdownNow(); // 立即关闭,即使任务没有完成,会丢失任务的!
        pool.shutdown(); // 会等待全部任务执行完毕之后再关闭(建议使用的)
    }
}

1.4 线程池处理Callable任务

package com.gch.d8_threadpool;

import java.util.concurrent.Callable;

public class MyCallable implements Callable<String> {
    private int n;
    public MyCallable(int n){
        this.n = n;
    }

    /**
       2.重写call方法(任务方法)
     */
    @Override
    public String call() throws Exception {
        int sum = 0;
        for(int i = 1;i <= n;i++){
            sum += i;
        }
        return Thread.currentThread().getName() + "执行1-" + n + "的和结果是:" + sum;
    }
}
package com.gch.d8_threadpool;

import java.util.concurrent.*;

public class ThreadPoolDemo2 {
    public static void main(String[] args) throws Exception {
        // 1.创建线程池对象
        /*  public ThreadPoolExecutor(
        int corePoolSize,  核心线程的最大数量
        int maximumPoolSize, 最大线程数量
        long keepAliveTime, 空闲线程最大存活时间
        TimeUnit unit,  存活时间的单位
        BlockingQueue<Runnable> workQueue, 指定任务队列,任务的阻塞队列
        ThreadFactory threadFactory, 指定用哪个线程工厂创建线程
        RejectedExecutionHandler handler 指定线程忙,任务满的时候线程来了怎么办 / 终止策略
        )*/
        ExecutorService pool = new ThreadPoolExecutor(3,5,6,
                TimeUnit.SECONDS,new ArrayBlockingQueue<>(5),Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

        // 2.给任务线程池处理
        Future<String> f1 = pool.submit(new MyCallable(100));
        Future<String> f2 = pool.submit(new MyCallable(200));
        Future<String> f3 = pool.submit(new MyCallable(300));
        Future<String> f4 = pool.submit(new MyCallable(400));
        Future<String> f5 = pool.submit(new MyCallable(500));

//        String rs = f1.get();
//        System.out.println(rs);

        // get方法会等待call方法执行完,直到线程跑完才提取结果
        System.out.println(f1.get()); // pool-1-thread-1执行1-100的和结果是:5050
        System.out.println(f2.get()); // pool-1-thread-2执行1-200的和结果是:20100
        System.out.println(f3.get()); // pool-1-thread-3执行1-300的和结果是:45150
        System.out.println(f4.get()); // pool-1-thread-3执行1-300的和结果是:45150
        System.out.println(f5.get()); // pool-1-thread-2执行1-500的和结果是:125250
    }
}

 

 

1.5 Executors工具类实现线程池

  • Executors:线程池的工具类,调用方法返回不同类型的线程池对象。
  • 注意:Executors的底层其实也是基于线程池的实现类ThreadPoolExecutor创建线程池对象的!

package com.gch.d8_threadpool;

/**
   任务类
 */
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "输出了 HelloWorld===>" + i);
        }
        try {
            // 每个线程霸占一个任务
            System.out.println(Thread.currentThread().getName() + "该线程与本任务绑定了,线程进入休眠了~~~");
            Thread.sleep(100000);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
package com.gch.d8_threadpool;

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

/**
   目标:使用Executors(线程池的工具类)的工具方法直接得到一个线程池对象!
 */
public class ThreadPoolDemo3 {
    public static void main(String[] args) {
        // 1.创建固定线程数量的线程池
        ExecutorService pool = Executors.newFixedThreadPool(3);

        pool.execute(new MyRunnable());
        pool.execute(new MyRunnable());
        pool.execute(new MyRunnable());
        pool.execute(new MyRunnable()); // 已经没有多余线程了
    }
}

  1.  Executors工具类底层是基于线程池ExecutorService的实现类:ThreadPoolExecutor来创建线程池对象的。
  2. Executors不适合做大型互联网场景的线程池方案,建议使用ThreadPoolExecutor来指定线程池参数,这样可以明确线程池的运行规则,规避资源耗尽的风险。