并发编程之线程池

线程使用上的问题

  • 线程的频繁创建好销毁
  • 线程的数量过多,会造成CPU资源的开销。
  • 上下文切换 (消耗CPU资源)。

线程的复用

连接池、对象池、内存池、线程池 。

  • 池化技术的核心: 复用

线程池

  • 提前创建一系列的线程,保存在这个线程池中。
  • 有任务要执行的时候,从线程池中取出线程来执行。
  • 没有任务的时候,线程池放回去。

Excutors类中创建线程的方法(不建议使用这种,使用ThreadPoolExecutor最好,规避资源浪费

  • newFixedThreadPool 固定线程数量,任务队列采用的是LinkedBlockingQueue
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
  • newSingleThreadExecutor 只有一个线程的线程池,任务队列采用的是LinkedBlockingQueue
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
  • newCachedThreadPool 可以缓存的线程池 ->理论上来说,有多少请求,该线程池就可以创建多
    少的线程来处理。
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
  • newScheduledThreadPool //提供了按照周期执行的线程池. ->Timer
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

使用小Demo

public class ThreadPoolExample {
    public static void main(String[] args) {
        ExecutorService executorService= Executors.newFixedThreadPool(4);
        for(int i=0;i<10;i++){
            //把一个实现了Runnable接口的任务交给线程池执行
            executorService.execute(new Task());
        }
        executorService.shutdown();
    }

    static class Task implements Runnable{

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"-开始执行任务");
            try {
                Thread.sleep(new Random().nextInt(1000));
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}

在这里插入图片描述

执行任务的两种方式

execute() 和 submit() 都是用来执行线程池任务的,它们最主要的区别是,submit() 方法可以接收线程池执行的返回值,而 execute() 不能接收返回值。
submit和Callable以及Future有关

ThreadPoolExecutor

猜想参数

  • 线程池的实现原理的过程推演
    • 需求: 实现线程的重复使用.
    • 分解:
      如何线程的复用.
      让线程实现复用的唯一的方法,就是让线程不结束。
    • 那如何让线程执行新的任务呢?也就是说,任务怎么给他执行?
      线程传递参数通过[共享内存],比如把任务放在一个集合里面,线程自己去取
    • 线程一直处于运行状态,合理吗?
      • 有任务来的时候,执行
      • 没有任务的时候,阻塞

结论: 通过阻塞队列的方式,来实现线程池中线程的复用。

有这些猜想可以自己写个线程池中线程的简易版代码

public class ThreadExample implements Runnable {


    private static BlockingDeque<Object> tasks=new LinkedBlockingDeque<>();

    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()){
            Object take = tasks.take();//阻塞方法,有任务返回一个Object,无任务阻塞

            ((Runnable)take).run();//这个地方就应该调用run方法,它本身就是线程在执行
        }
    }

    public static void main(String[] args) {
        tasks.add(new Task());
    }

    static class Task implements Runnable{

        @Override
        public void run() {
            System.out.println("干活");
        }
    }
}

猜想总结图

在这里插入图片描述

源码验证


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.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;//核心线程数
        this.maximumPoolSize = maximumPoolSize;//最大线程数,不使用后要让它们结束
        this.workQueue = workQueue;//阻塞队列(任务队列)
        this.keepAliveTime = unit.toNanos(keepAliveTime);//时间
       this.threadFactory = threadFactory;//线程工厂,用来创建工作线程的,可以不传这个参数,有默认实现
       //如果想自定义线程的名字那么就需要穿这个参数
        this.handler = handler;//拒绝策略,可以不传这个参数,有默认实现
    }

execute方法

  • 先初始化核心线程。
  • 调用阻塞队列的方法,把task存进去。(offer() -> true/false)
    • 如果true ,说明当前的请求量不大, 核心线程就可以搞定。
    • false,增加工作线程(非核心线程)
      • 如果添加失败,说明当前的工作线程数量达到了最大的线程数,直接调用拒绝策略。
public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
      
        int c = ctl.get();
        //判断当前工作线程数是否小于核心线程数(延迟初始化)
        //注意这个ctl是AtomicInteger类型
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))//添加工作线程的同时,执行command
                return;
            c = ctl.get();
        }
        //workQueue.offer 添加到阻塞队列
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        // 如果阻塞队列满了,则添加工作线程(扩容的线程)
        else if (!addWorker(command, false))
            reject(command);//拒绝策略
    }

如何关闭线程池

关闭线程池
线程池关闭有两种方法即shutdown或shutdownNow方法。原理都是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法被终止。

区别

  • shutdownNow首先将线程池的状态设置成STOP,然后尝试停止所有正在执行或暂停任务的线程,并返回等待执行任务的列表

  • shutdown只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程

  • 调用任意一个方法,isShutdown方法就会返回true所有任务都关闭后,才表示线程池关闭成功,这时调用isTerminaed返回true。

  • 通常调用shutdown方法来关闭线程池,如果任务不一定要执行完,则可以调用shutdownNow方法。

线程数量(千古难题)

IO密集型 CPU 2core+1(CPU利用率不高)
CPU密集型 CPU +1(CPU利用率很高,会增加上下文切换)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值