多线程(六):线程池

Posted 头发都哪去了

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程(六):线程池相关的知识,希望对你有一定的参考价值。

多线程(六):线程池

线程的缺点:

  1. 线程的创建需要开辟内存资源:本地方法栈、虚拟机栈、程序计数器等线程私有变量的内存。频繁地创建和销毁线程,会带来一定的性能开销。
  2. 使用线程不能很好的管理任务和友好的拒绝任务。

在《Java开发手册》中,也有提到。

线程池的定义

使用池化技术来管理和使用线程的技术,就叫做线程池。

线程池创建线程是懒加载的

在有任务的时候才会创建线程,并不是创建线程池的时候就创建了线程。

线程池优点

线程池优点

  1. 避免频繁创建和销毁线程所带来的性能开销。
  2. 可以优化地拒绝任务。
  3. 更多功能,例如:执行定时任务。

线程池的创建方式(7种)

线程池的创建方式①:创建固定个数的线程池

public class ThreadPoolDemo45 
    public static void main(String[] args) 

        //创建固定个数线程的线程池,参数为线程个数。
        ExecutorService service = Executors.newFixedThreadPool(5);

        for (int i = 0; i < 5; i++) 
            //执行任务
            service.execute(new Runnable() 
                @Override
                public void run() 
                    System.out.println("线程名:" + Thread.currentThread().getName());
                
            );
        
    

该代码的执行结果如下:

注意:创建5个线程来执行2个任务如下面这段代码代码,问当前程序创建了几个线程?

public class ThreadPoolDemo45 
    public static void main(String[] args) 

        //创建固定个数线程的线程池,参数为线程个数。
        ExecutorService service = Executors.newFixedThreadPool(5);

        for (int i = 0; i < 2; i++) 
            //执行任务
            service.execute(new Runnable() 
                @Override
                public void run() 
                    System.out.println("线程名:" + Thread.currentThread().getName());
                
            );
        
    

使用 jconsole 工具可见,当前程序只创建了两个线程。

有这个题目进而引出线程池的执行流程:
当拿到一个任务之后,会判断当前线程池里面的线程数量是否达到了最大值,如果没有达到,就会创建新的线程执行任务;当获得任务之后,线程池的数量已经是最大值,并且没有空闲的线程,当前任务会被放到线程池的任务队列里面等待执行。

自定义线程池行为

例如,设置线程池的命名规则和优先级,代码如下:

public class ThreadPoolDemo46 
    private static int count = 1;

    static class MyThreadFactory implements ThreadFactory 

        @Override
        public Thread newThread(Runnable r) 
            Thread thread = new Thread(r);
            //设置线程池命名
            thread.setName("myThreadPool-" + count++);
            //设置线程池优先级
            thread.setPriority(10);
            return thread;
        
    

    public static void main(String[] args) 
        //线程工厂
        MyThreadFactory myThreadFactory = new MyThreadFactory();
        //创建线程池
        ExecutorService service =
                Executors.newFixedThreadPool(10, myThreadFactory);
        for (int i = 0; i < 10; i++) 
            service.execute(new Runnable() 
                @Override
                public void run() 
                    System.out.println("线程名:" + Thread.currentThread().getName()
                            + ",线程优先级:" + Thread.currentThread().getPriority());
                
            );
        
    

该代码的执行结果为:

缺点:任务数趋向无限大,从而线程数不可控。

线程池的创建方式②:创建带缓存的线程池

根据任务的数量生成对应的线程数,所以适用于:有大量的短期任务。
创建线程池代码如下:

public class ThreadPoolDemo47 
    public static void main(String[] args) 
        //创建带缓存的线程池
        ExecutorService service = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) 
            service.execute(new Runnable() 
                @Override
                public void run() 
                    System.out.println("线程名:" + Thread.currentThread().getName());
                
            );
        
    

该代码的执行结果如下:

线程池的创建方式③:创建可以执行定时任务的线程池

使用场景:需要定时执行任务,线程池的创建方法如下:

使用service.scheduleWithFixedDelay执行任务:

public class ThreadPoolDemo48 
    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);//延迟1秒,每3秒执行一次
    

其中service.scheduleWithFixedDelay(参数1,参数2,参数3,参数4)参数1 为线程池需要执行的任务;参数2 为定时任务的延迟时间;参数3 为定时任务的执行频率;参数4 为配合参数2和参数3使用的时间单位。

该代码的执行效果如下:

使用service.scheduleAtFixedRate执行任务:

public class ThreadPoolDemo50 
    public static void main(String[] args) 
        //创建一个执行定时任务的线程池
        ScheduledExecutorService service =
                Executors.newScheduledThreadPool(10);
        System.out.println("执行任务之前:" + new Date());
        //执行任务
        service.scheduleAtFixedRate(new Runnable() 
            @Override
            public void run() 
                System.out.println("执行任务:" + new Date());
            
        , 1, 3, TimeUnit.SECONDS);//延迟1秒,每3秒执行一次
    

该代码的执行结果如下:

我们发现使用service.scheduleWithFixedDelay和使用service.scheduleAtFixedRate执行简单任务时,代码的执行效果是一样的。
使用service.scheduleWithFixedDelay执行任务,下一次任务的开始执行时间是以上一次任务执行的结束时间作为开始执行时间的。
而使用sservice.scheduleAtFixedRate执行任务,下一次任务的开始执行时间是以上一次任务执行的开始时间+延时时间作为开始执行时间的;

使用service.schedule执行任务:

public class ThreadPoolDemo49 
    public static void main(String[] args) 
        //创建一个执行定时任务的线程池
        ScheduledExecutorService service =
                Executors.newScheduledThreadPool(10);
        System.out.println("执行任务之前:" + new Date());
        //执行任务
        service.schedule(new Runnable() 
            @Override
            public void run() 
                System.out.println("执行任务:" + new Date());
            
        ,  3, TimeUnit.SECONDS);//3秒后执行一次
    

该代码的执行结果如下:

service.schedule的区别:

  1. 没有延迟执行的时间设置。
  2. 定时任务只能执行一次

适用场景:任务定时启动一次

线程池的创建方式④:创建单一执行定时任务的线程池

与 线程池的创建方式③类似,这个创建方式,也有三种任务执行方式,我是用service.scheduleAtFixedRate开始任务执行的,示例代码如下:

public class ThreadPoolDemo51 
    public static void main(String[] args) 
        //创建单个执行定时任务的线程池
        ScheduledExecutorService service =
                Executors.newSingleThreadScheduledExecutor();
        //开始定时任务
        service.scheduleAtFixedRate(new Runnable() 
            @Override
            public void run() 
                try 
                    Thread.sleep(1000);
                 catch (InterruptedException e) 
                    e.printStackTrace();
                
                System.out.println("执行任务:" + new Date());
            
        , 1, 3, TimeUnit.SECONDS);
    

该代码的执行结果如下:

单一执行定时任务的线程池有什么意义

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

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

创建单一线程的线程池执行任务,示例代码如下:

public class ThreadPoolDemo52 
    public static void main(String[] args) 
        //创建单一线程的线程池
        ExecutorService service =
                Executors.newSingleThreadExecutor();
        //执行任务
        for (int i = 0; i < 5; i++) 
            service.execute(new Runnable() 
                @Override
                public void run() 
                    System.out.println("线程名:" + Thread.currentThread().getName());
                
            );
        
    

该代码的执行结果如下;

单一执行任务的线程池有什么意义

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

线程池的创建方式⑥(JDK8+):根据当前的工作环境(CPU核心数、任务量)异步线程池

异步执行流程:

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

示例代码如下:

public class ThreadPoolDemo53 
    public static void main(String[] args) 
        //根据当前工作环境来创建线程池
        ExecutorService service =
                Executors.newWorkStealingPool();
        for (int i = 0; i < 5; i++) 
            service.execute(new Runnable() 
                @Override
                public void run() 
                    System.out.println("线程名:" +
                            Thread.currentThread().getName());
                
            );
        
    

该代码的执行结果如下:

对上述代码做出如下修改,即可看到异步线程池的运行效果:

public class ThreadPoolDemo53 
    public static void main(String[] args) 
        //根据当前工作环境来创建线程池
        ExecutorService service =
                Executors.newWorkStealingPool();
        for (int i = 0; i < 5; i++) 
            service.execute(new Runnable() 
                @Override
                public void run() 
                    System.out.println("线程名:" +
                            Thread.currentThread().getName());
                
            );
        

        //等待异步线程池执行完成(根据线程池的终止状态)
        while (!service.isTerminated()) 
        
    

该代码的执行结果如下:

此线程池的创建方式的优点

  1. 执行速度非常快。
  2. 无须手动设置参数。

在《Java开发手册》中提到以下规则:

Executors 创建线程池的问题

  1. 线程数量不可控(造成线程的过度切换和争抢,过度消耗资源)
  2. 任务数量不可控(任务数量无限大,当任务量比较大的情况下就会造成OOM<内存溢出异常>)

以上的六种方法都使用了 Executors 去创建,所以我们需要去使用线程池的第七种创建方式。

线程池的创建方式⑦:new ThreadPoolExecutor

规避资源过度消耗,使用此创建线程池的方法,示例代码如下:

public class ThreadPoolDemo55 
    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());
                
            );
        
    

该代码的执行结果:

使用此方法创建自定义线程池行为,示例代码如下:

public class ThreadPoolDemo57 
    private static int count = 1;

    public static void main(String[] args) 

        ThreadFactory threadFactory = new ThreadFactory() 
            @Override
            public Thread newThread(Runnable r) 
                Thread t = new Thread(r);
                t.setName("myThreadPool-" + count++);
                return t;
            
        ;

        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                5, 5, 0, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(1000), threadFactory
        );

        //执行任务
        for (int i = 0; i < 5; i++) 
            executor.execute(new Runnable() 
                @Override
                public void run() 
                    System.out.println("线程名:" + Thread.currentThread().getName());
                
            );
        
    

该代码的执行结果如下:

拒绝策略(4+1)

JDK提供四种拒绝策略,还可以自定义拒绝策略。

JDK提供的四种拒绝策略+用户自定义拒绝策略:

  1. 默认的拒绝策略
  2. 自定义拒绝策略

JDK提供的四种拒绝策略:

①默认的拒绝策略,示例代码如下:

public class ThreadPoolDemo59 
    public static void main(String[] args) 
        //创建线程池
        ThreadPoolExecutor executor =
                new ThreadPoolExecutor(5, 5, 0, TimeUnit.SECONDS,
                        new LinkedBlockingQueue<>(5), new ThreadPoolExecutor.AbortPolicy());
        for (int i = 0; i < 11; i++) 
            int finalI = i;
 

以上是关于多线程(六):线程池的主要内容,如果未能解决你的问题,请参考以下文章

多线程(六):线程池

多线程(六):线程池

多线程系列六:线程池

深入浅出多线程编程实战六种常见线程池讲解

深入浅出多线程编程实战六种常见线程池讲解

okhttp(二)之线程池分析