线程池的学习(一)

转载:Java 线程池

线程池的创建方式

方式一:创建单一线程的线程池 newSingleThreadExecutor

特点:

  • 线程池中只包含 1 个线程,存活时间是无限的
  • 按照提交顺序执行任务
  • 唯一线程繁忙时,新提交的任务会被加入到阻塞队列中的,阻塞队列是无限的
  • 适用场景:要求任务按ti执提交次序执行时
 static class TargetTask implements Runnable {
        public static final int SLEEP_GAP = 1000;
        // AtomicInteger 提供了一种线程安全的方式来对整数进行操作。它可以保证多个线程同时对同一个AtomicInteger对象进行操作时,不会出现数据竞争和不一致的情况
        static AtomicInteger taskNo = new AtomicInteger(0);
        String taskName;

        @Override
        public void run() {
            log.info(taskName + "is running ...");
            try{
                Thread.sleep(SLEEP_GAP);
            }catch (Exception e){
                log.error(taskName + "running error !");
                e.printStackTrace();
            }
            log.info(taskName + "is  end ...");

        }
        TargetTask(){
            taskName = "task_" + taskNo;
            taskNo.incrementAndGet();
        }
    }

    public static void main(String[] args) throws InterruptedException {
//         方式一:创建单一线程的线程池 newSingleThreadExecutor
        ExecutorService poll1 = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 2; i++) {
            poll1.execute(new TargetTask());
            poll1.submit(new TargetTask());
        }
        poll1.shutdown();
    }
  }

线程池的学习(一)_第1张图片

方式二:创建指定线程数量的线程池 newFixedThreadPool

  • 如果线程池中任务没有达到『固定数量』,每次提交任务线程池中就会创建新的线程。
  • 如果线程数量达到『固定数量』,新提交的任务就会加到阻塞队列中,并且阻塞队列是无界的。
  • 如果线程池中的线程因为执行任务而异常了,那么线程池中就会补一个新的线程
  • 适用场景:需要任务长期执行的场景,CPU 密集型场景
  • 缺点:当突增大量任务时,阻塞队列无限增大,服务器资源迅速耗尽
// 方式二:创建指定线程数量的线程池 newFixedThreadPool
        ExecutorService poll2 = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 2; i++) {
            poll2.execute(new TargetTask());
            poll2.submit(new TargetTask());
        }
        poll2.shutdown();

如图:最多只有3个线程同时进行
线程池的学习(一)_第2张图片

方式三:创建可缓存的线程池 newCachedThreadPool

  • 新增加任务时,如果线程池内的线程均繁忙,则会新增加线程来执行
  • 如果部分线程空闲,超过60s则会进行线程回收,则被终止并移出缓存
  • 适用场景:需要快速处理突发性强,耗时短的线程,如Netty的NIO处理场景、REST API接口的瞬时削峰场景
  • 缺点:当突增大量任务时,创建线程过多导致资源耗尽
ExecutorService poll3 = Executors.newCachedThreadPool();
        for (int i = 0; i < 2; i++) {
            poll3.execute(new TargetTask());
            poll3.submit(new TargetTask());
        }
        poll3.shutdown();

如图:只要有新的任务,就会开辟线程
线程池的学习(一)_第3张图片

方式四:创建可调度线程池 newScheduledThreadPool

  • 首次执行任务可以设置延迟时间
  • 具有周期性,主线程睡眠时间越长,线程池中的周期次数越多
ScheduledExecutorService poll4 = Executors.newScheduledThreadPool(3);
        for (int i = 0; i < 5; i++) {
            // 参数1: task任务
            // 参数2: 首次执行任务的延迟时间
            // 参数3: 周期性执行的时间
            // 参数4: 时间单位
            poll4.scheduleAtFixedRate(new TargetTask(),0,500, TimeUnit.MILLISECONDS);
        }
        Thread.sleep(4000); //主线程睡眠时间越长 周期次数越多
        poll4.shutdown();

如图:周期性的执行任务
线程池的学习(一)_第4张图片

方式五: ThreadPoolExecutor 标准的创建方式

  • 最原始的线程池创建,上面1-3创建方式都是对ThreadPoolExecutor的封装
// 方式五: ThreadPoolExecutor 标准的创建方式,上面1-3 种方式都是ThreadPoolExecutor的变种
        // 参数1: 核心线程数量
        // 参数2: 最大线程数量
        // 参数3: 核心线程之外的空闲线程的存活时间
        // 参数4: 存活时间的单位
        // 参数5: 阻塞队列
        
        // 下面这种形式类似于方式二,创建固定数量的线程数
        ExecutorService poll5 = new ThreadPoolExecutor(3, 3,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>());

        for (int i = 0; i < 2; i++) {
            poll5.execute(new TargetTask());
            poll5.submit(new TargetTask());
        }
        poll5.shutdown();

如图:最多启动3个线程执行任务
线程池的学习(一)_第5张图片

总结:

  • newFixedThreadPool和newSingleThreadExecutor: 阻塞队列无界,会堆积大量任务导致OOM(内存耗尽)
  • newCachedThreadPool和newScheduledThreadPool: 线程数量无上界,会导致创建大量的线程,从而导致OOM
  • 建议直接使用线程池ThreadPoolExecutor的构造器

你可能感兴趣的:(#,线程,java,开发语言,线程池)