Java线程池看懂这一篇就够了!

本文介绍了Java线程池的概念,分析了使用线程池的原因,如避免频繁创建线程的开销。详细讲解了线程池的创建方法,包括固定线程数、带缓存、定时任务等类型,并指出Executors创建线程池的问题。还探讨了开发中如何选择合适的线程池技术,以及线程池的特征和关闭策略。

1.概念

概念:使用池化技术来管理和使用线程的技术,就叫做线程池。
线程池里面包含的重要内容:

  1. 线程
  2. 任务队列

2.为什么要使用线程池?

2.1线程的缺点

  1. 线程的创建需要开辟内存资源:本地方法栈、虚拟机程序计数器等线程私有变量的内存频繁的创建和消耗会带来一定的性能开销
  2. 使用线程不能很友好的管理任务和友好的拒绝任务
  3. 强制:线程资源必须使用线程池创建,不允许在应用中字形显示创建线程

2.2使用线程池的原因

创建线程要花费昂贵的资源和时间,如果任务来了才创建线程那么响应时间会变长,而且一个进程能创建的线程数有限。为了避免这些问题,在程序启动的时候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程。从JDK1.5开始,Java API提供了Executor框架让你可以创建不同的线程池。比如单线程池,每次处理一个任务;数目固定的线程池或者是缓存线程池(一个适合很多生存期短的任务的程序的可扩展线程池)。

3.线程池的创建方法

线程池的创建总共有7种2类,JDK1.8之后为7种,在此之前为5~6种。

3.1创建固定个数的线程

public class Test01 {
    public static void main(String[] args) {
        //创建固定个数的线程池(来自juc包)
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        //不用start,只需设置任务,自动执行
        for (int i = 0; i < 10; i++) {
            //设置任务
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程名:" + Thread.currentThread().getName());
                }
            });
        }
    }
}
  • 经典面试题:创建10个线程来执行2个任务,问创建了几个线程?

答:2个,线程池是懒加载的,是经过优化后的。任务数小于线程数,会新启线程数。

  • 线程池的执行流程:当拿到一个任务后,会判断当前线程池里面的线程数量是否达到最大值,如果没有达到创建新的线程执行任务;如果线程池的数量已经是最大值,并且没有空闲线程,当前的任务会被放到线程池中的任务队列中,等待执行。

3.2创建带缓存的线程池

public class Test03 {
    public static void main(String[] args) {
        //创建带缓存的线程池(不用传参)
        ExecutorService service = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            service.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());
                }
            });
        }
    }
}

使用场景:有大量短期任务的时候

3.3创建能执行定时任务的线程池

public class Test04 {
    public static void main(String[] args) {
        //创建能执行定时任务的线程池
        ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
        System.out.println(new Date());
        //执行任务
        service.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                System.out.println("执行时间:" + new Date());
            }
        },1,3, TimeUnit.SECONDS);
        //即时执行
        service.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("执行时间:" + new Date());
            }
        },3, TimeUnit.SECONDS);
        service.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("执行时间:" + new Date());
            }
        },1,3, TimeUnit.SECONDS);
    }
}

参数1:线程池的任务
参数2:定时任务延迟多长时间执行
参数3:定时任务的执行频率
参数4:配合参数2和3使用的时间单位

  • 执行任务和即时任务的区别

    1. 没有延迟执行时间设置
    2. 定时任务只能执行一次
      scheduleWithFixedDelay任务开始时间是以上次任务的结束时间为开始时间(应用场景:时间间隔不稳定)
      scheduleAtFixedRate任务开始时间是以上次任务的开始时间做起始时间的(应用场景:执行时间间隔稳定,任务简单)固定频率执行

3.4创建单个执行定时任务的线程池

public class Test05 {
    public static void main(String[] args) {
        //创建单个执行定时任务的线程池
        ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
    }
}

经典面试题:单个线程的线程池有什么意义?
答:

  1. 无需频繁的创建和销毁线程
  2. 可以更好的分配和管理以及存储任务(任务队列)

3.5创建单个线程的线程池

public class Test06 {
    public static void main(String[] args) {
        //创建单个线程的线程池
        ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
        for (int i = 0; i < 5; i++) {
            service.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());
                }
            });
        }
    }
}

3.6根据当前工作环境(CPU核心数、任务量)异步线程

这种方式只有JDK8以上才能用

public class Test07 {
    public static void main(String[] args) throws InterruptedException {
        //根据当前工作环境创建线程池
        ExecutorService service = Executors.newWorkStealingPool();
        for (int i = 0; i < 100; i++) {
            service.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());
                }
            });
        }
        //判断当前线程池是否终止,等待异步线程池是否执行完成,根据终止状态判断
        while (!service.isTerminated()){
        }
    }
}

特点:执行快,执行即结束
synchronize:同步
同步:按照某个规则按序进行执行
同步执行流程:

  1. main调用线程池
  2. 线程池执行完之后
  3. 关闭线程池,main也会随之关闭

异步执行流程:

  1. main调用线程池
  2. 异步线程池后台执行,对于main线程来说异步线程池已经执行完成,直接关闭main线程

4.Executors创建线程池的问题

Executors创建线程池问题:

  1. 线程数量不可控(线程的过渡切换和争取 - > 程序执行慢)
  2. 任务数量不可控(任务数无限大),当任务量比较大,就会造成内存溢出异常(OOM)

5.开发中使用的线程池技术

使用ThreadPoolExecutor创建线程池

public class ThreadDemo01 {
    public static void main(String[] args) {
        //原始创建线程池的方式
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                //核心线程数,正常情况下保持的线程数
                5,
                //最大线程数(非正常情况下能创建多少线程,线程+任务队列),要大于等于核心线程数
                10,
                //最大生命周期(临时工),临时线程的最大存活时间
                60,
                //第三个参数的单位
                TimeUnit.SECONDS,
                //任务队列(必须要设置容量,不然就和第一种创建方式无区别)
                new LinkedBlockingQueue<>(1000)
        );
        for (int i = 0; i < 5; i++) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());
                }
            });
        }
    }
}

线程池创建是懒加载,再有任务才会创建线程,并不是执行new的时候就创建线程
拒绝策略触发添加:最大线程数-线程池中的线程数 = 0(任务数>核心线程数)
拒绝策略(4中或5中,一种是自定义拒绝策略):
JDK提供4种
在这里插入图片描述

AbortPolicy:默认拒绝策略,会抛出异常
CallerRunspolicy:重复添加拒绝任务,直到成功,不抛异常
DiscardPolicy:无声抛弃拒绝任务,不抛异常
DiscardOldestPolicy:抛弃任务队列中最老的任务,并将拒绝任务添加到任务队列中,不抛异常
线程池的两种执行方式:

  1. execute执行(new Runnable)无返回值
  2. submit执行(new Runnable)无返回值/(new Callable)有返回值

执行方法区别:

  1. execute只能执行Runnable任务,无返回值;submit既能执行Runnable无返回值的,也能执行Callable有返回值任务
  2. execute执行任务遇到OOM异常,会将异常打印到控制台,而submit并不会打印异常

6.线程池特征

线程池的特征:线程池相比于线程来说是长生命周期,即使没有任务,也会运行并等待任务,线程处于wait状态
Shutdown/shutdownNow关闭线程池方法
线程池的关闭:

  1. shutdown:拒绝新任务加入,等待线程池中的任务队列执行完之后,再停止线程池
  2. shutdownNow:拒绝执行新任务,不等待任务队列执行完成,就停止线程池

线程池状态
注意:线程池状态(五种)不等于线程状态(六种)
在这里插入图片描述
调用shutdownNow方法进入stop状态
线程池的状态只是给开发者使用的,客户机看不见

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值