JAVA线程池

线程池

为什么采用线程池

在Java中创建线程看着就像创建一个对象,继承Thread或者实现Runnable接口都可以实现,但其实并不是那么简单,创建对象需要JVM分配内存空间,而创建线程则需要调用操作系统API来实现,同时操作系统还需要分配资源给线程使用,非常的消耗资源同时线程的销毁也是如此,所以线程是一个重量级的对象,应该避免经常性的创建和销毁。

所以这时候就需要用到线程池这个概念,池化思想在很多中间件都有使用数据库连接池,常量池等等,池化思想的主要目的就是复用,减少资源的重复创建。

提到池化思想,大多数池化思想都是先向资源池acquire申请资源,使用完毕后release释放资源

// 资源池
class XXXPool{
  // 获取资源池中的资源
  XXX acquire() {
      
  }
  // 释放资源
  void release(XXX x){
      
  }
}

按照这种思想,线程池的使用应该是如下逻辑,但是很明显Thread没有这种用法

ThreadPool pool;
Thread T1=pool.acquire();
// 传入Runnable对象
T1.execute(()->{ 
    // 省略业务逻辑
});

JAVA实现池化思想

java采用的池化思想实现方式其实是生产者-消费者模式,其中生产者是线程池的使用方,消费者是线程池本身,实现原理如下。

public class Test {
    public static void main(String[] args) {
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(2);
        MyThreadPool myThreadPool = new MyThreadPool(10,blockingQueue);
        myThreadPool.execute(()->{
            System.out.println("test");
        });
    }
}

class MyThreadPool{
    // 阻塞队列
    private BlockingQueue<Runnable> workQueue;

    // 保存内部工作线程 线程销毁时使用这里是占个坑位
    List<WorkerThread> threads = new ArrayList<>();

    // 构造方法
    public MyThreadPool(int poolSize,BlockingQueue<Runnable> workQueue){
        this.workQueue = workQueue;
        for (int i = 0; i <poolSize ; i++) {
            WorkerThread workerThread = new WorkerThread();
            workerThread.start();
            threads.add(workerThread);
        }
    }

    // 提交任务 就是简单的入队到阻塞队列中
    public void execute(Runnable command){
        try {
            workQueue.put(command);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    class WorkerThread extends Thread{
        @Override
        public void run() {
            //循环取任务并执行
            while(true){
                Runnable task = null;
                try {
                    task = workQueue.take();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                task.run();
            }
        }
    }
}

MyThreadPool是自定义的线程池,内部保存属性阻塞队列workQueue,工作线程集合threads(在这里无它用,在线程销毁时会使用),线程池构造方法内部构造poolSize个线程,同时启动线程,线程的run方法都是去阻塞队列中出队(在没有执行execute方法前阻塞队列都是空,所以都会阻塞),调用execute提交任务,就是入队到阻塞队列中,所有的线程就能自动竞争获取任务执行task.run()方法。

线程池创建

线程池的创建主要依赖ThreadPoolExecutor方法,这个方法配置了七个常用参数,说明如下

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {}
  • corePoolSize 核心线程数,线程池保留的最少线程数,就算线程空闲也不会回收。
  • maximumPoolSize 最大线程数,线程池在超过核心线程数后,需要扩充,但是不能无限制的扩充最大值就是maximumPoolSize,当线程空闲下来又将扩充的线程回收。
  • keepAliveTime & unit 线程存活时间和单位
  • workQueue 工作队列。
  • threadFactory 线程工厂,可以自定义如何创建线程,例如你可以给线程指定一个有意义的名字。
  • handler 阻塞策略,当请求线程数超过(最大线程数+工作队列数)之和后,线程池采用什么策略处理请求的线程。
    • AbortPolicy:默认的拒绝策略,会 throws RejectedExecutionException。
    • DiscardPolicy:直接丢弃任务,没有任何异常抛出。
    • DiscardOldestPolicy:丢弃最老的任务,其实就是把最早进入工作队列的任务丢弃,然后把新任务加入到工作队列。
    • CallerRunsPolicy:提交任务的线程自己去执行该任务(哪里来回哪里去)。

ThreadPoolExecutor的构造方法属实有点复杂,对于初学者不太友好,考虑到这点Java并发包提供了静态类Executors

public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newCachedThreadPool() 
public static ExecutorService newSingleThreadExecutor()
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)

并发包的静态类Executors虽然好用,但是生产中却并不建议采用Executors创建线程池,还是推荐采用ThreadPoolExecutor

线程池使用的注意点

  • 静态类创建线程池,其中任务队列默认采用无界队列LinkedBlockingQueue(没有指定队列大小),生产中容易造成OOM。
  • 静态类创建线程池拒绝策略一般采用默认的AbortPolicy,丢弃线程后抛出异常RejectedExecutionException,线程池的调用方无法感知,会导致某些重要业务丢失。
  • 通过ThreadPoolExecutor 对象的 execute() 方法提交任务时,如果任务在执行的过程中出现运行时异常,会导致执行任务的线程终止,这时线程池收不到任何异常通知,导致业务异常。

线程池补充点

当线程池中的线程执行方法异常时,会重新启动新的线程执行任务。

public class Test3 {
    // 线程池初始化时只有一个核心线程
    private static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
            1,
            1,
            1,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue(5),
            new ThreadPoolExecutor.AbortPolicy());
    // 线程池某个线程执行任务出现异常,线程终止,那么线程池会重新启动一个线程加入线程队列
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("<<<<" + Thread.currentThread().getName());
                    int a = 1 / 0;
                }
            });
        }
    }
}

image-20220303191101228

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值