常见线程池详解(一篇足以)

Posted 一位懒得写博客的小学生

tags:

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

线程的缺点

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

线程池

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

线程池的优点
可以避免频繁的创建和消耗线程
可以更好的管理线程的个数和资源的个数
拥有更多的功能,线程池可以进行定时任务;
线程池可以更优化的拒绝不能处理的任务。

线程池的创建方式

  1. 创建固定的线程池(任务数取向无限大);
  2. 创建带缓存的线程(根据对应的数量生成对应的线程数,适用于短期大量任务);
  3. 创建可以执行定时任务的线程池;
  4. 创建单个执行时任务的线程池;
  5. 创建单个线程池;
  6. 根据当前的工作环境(CPU、任务量)生成对应的线程池。

第一种

创建固定个数的线程池

    public static void main(String[] args) {
        //创建固定个数的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);//创建10个线程

        //执行任务
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        });

    }

//执行结果
pool-1-thread-1

第二种

创建带缓存的线程池

//使用场景:短期有大量任务的时候使用
    public static void main(String[] args) {
        //创建带缓存的线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        });
    }

//执行结果
pool-1-thread-1

自定义线程池规则

private static int count = 1;
    //自定义线程工厂
    static class  MyThreadFactory implements ThreadFactory {

        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r);
            //自定义线程池的名称
            thread.setName("myPool-" + count++);

            //设置优先级
            thread.setPriority(10);
            return thread;
        }
    }
    public static void main(String[] args) {
        //创建线程工厂
        ThreadFactory threadFactory = new MyThreadFactory();
        ExecutorService executorService = Executors.newFixedThreadPool(10,threadFactory);
        for (int i = 0; i < 10; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程名" + Thread.currentThread().getName() + ",优先级:" + Thread.currentThread().getPriority());
                }
            });
        }
    }

//执行结果
线程名myPool-1,优先级:10
线程名myPool-5,优先级:10
线程名myPool-3,优先级:10
线程名myPool-4,优先级:10
线程名myPool-2,优先级:10
线程名myPool-6,优先级:10
线程名myPool-7,优先级:10
线程名myPool-8,优先级:10
线程名myPool-9,优先级:10
线程名myPool-10,优先级:10

第三种

可以执行定时任务的线程池

public static void main(String[] args) {
        ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
        //方法一:每三秒执行一次
//        service.scheduleAtFixedRate(new Runnable() {
//            @Override
//            public void run() {
//                System.out.println("执行任务:" + new Date());
//            }
//        },1,3, TimeUnit.SECONDS);//1,线程执行的任务 2.延迟执行 3.定时任务执行的频率 4.配合2,3使用的,规定了时间的单位

        //方法二:
//        service.schedule(new Runnable() {
//            @Override
//            public void run() {
//                System.out.println("执行任务:" + new Date());
//            }
//        },1,TimeUnit.SECONDS);//只会执行一次

        //方法三:每三秒执行一次
        service.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                System.out.println("执行任务:" + new Date());
            }
        },1,3,TimeUnit.SECONDS);
    }

scheduleAtFixeRate:以上次任务开始的时间作为下一次任务的开始时间
scheduleWithFixedDelay:以上次任务的结束时间作为下一次任务的开始时间的

第四种

public static void main(String[] args) {
        //创建单个执行定时任务的线程池
        //是第三种的单机版本
        ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
        service.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                System.out.println("执行任务:" + new Date());
            }
        },1,2, TimeUnit.SECONDS);
    }
//执行结果
执行任务:Thu May 20 22:14:56 CST 2021
执行任务:Thu May 20 22:14:58 CST 2021
执行任务:Thu May 20 22:15:00 CST 2021
执行任务:Thu May 20 22:15:02 CST 2021
执行任务:Thu May 20 22:15:04 CST 2021
执行任务:Thu May 20 22:15:06 CST 2021
执行任务:Thu May 20 22:15:08 CST 2021
执行任务:Thu May 20 22:15:10 CST 2021
执行任务:Thu May 20 22:15:12 CST 2021
执行任务:Thu May 20 22:15:14 CST 2021
执行任务:Thu May 20 22:15:16 CST 2021
            。
            。
            。

第五种

创建单个线程

    public static void main(String[] args) {
        ExecutorService service = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            service.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程名:" + Thread.currentThread().getName());
                }
            });
        }
    }

//执行结果   
线程名:pool-1-thread-1
线程名:pool-1-thread-1
线程名:pool-1-thread-1
线程名:pool-1-thread-1
线程名:pool-1-thread-1
线程名:pool-1-thread-1
线程名:pool-1-thread-1
线程名:pool-1-thread-1
线程名:pool-1-thread-1
线程名:pool-1-thread-1

创建单个线程池有什么用?

  1. 可以避免频繁创建和消耗带来的性能开销;
  2. 有任务队列可以储存多余的任务;
  3. 有大量的任务不能处理的时候, 可以友好的执行拒绝策略;
  4. 可以更好的管理任务

第六种 (JDK8+)

根据当前的硬件CPU生成对应个数的线程池,并且是异步。

    public static void main(String[] args) {
        //创建一个异步(根据当前CPU生成的线程池)
        ExecutorService service = Executors.newWorkStealingPool();
        for (int i = 0; i < 10; i++) {
            service.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程池:" + Thread.currentThread().getName());
                }
            });
        }
        //等待线程池执行完成
        while(!service.isTerminated()) {
        }
    }

//执行结果
线程池:ForkJoinPool-1-worker-1
线程池:ForkJoinPool-1-worker-3
线程池:ForkJoinPool-1-worker-2
线程池:ForkJoinPool-1-worker-2
线程池:ForkJoinPool-1-worker-5
线程池:ForkJoinPool-1-worker-3
线程池:ForkJoinPool-1-worker-2
线程池:ForkJoinPool-1-worker-1
线程池:ForkJoinPool-1-worker-4
线程池:ForkJoinPool-1-worker-5
同步执行的流程:
main调用线程池
线程池执行完之后
关闭线程池,main也会随之关闭
异步线程执行的流程
main调用异步线程池
异步线程池后台执行,对于main线程来说异步线程池已经执行完成,关闭main线程

线程池两个重要的对象:

  1. 线程
  2. 队列

Executors 创建线程池的问题

  1. 线程数量不可控(线程的过度切换和争取);
  2. 任务数量不可控(Integer.MAX_VALUE)可能导致内存溢出

第七种

    static  class MyOOMClass {
        private byte[] bytes = new byte[1024 * 1024 * 1];
    }
    public static void main(String[] args) {
        ExecutorService service = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            final int finalI = i;
            service.execute(new Runnable() {
                @Override
                public void run() {
                    MyOOMClass oomClass = new MyOOMClass();
                    System.out.println("任务:" + finalI);
                }
            });
        }
    }

线程池创建线程是懒加载,有任务的时候才会创建线程,并不是new的时候就会创建线程。

拒绝策略:

  1. 默认的拒绝策略
new ThreadPoolExecutor.AbortPolicy()
  1. 使用调用线程池的线程来执行任务,使用主线程来执行任务;
new ThreadPoolExecutor.CallerRunsPolicy()
  1. 忽略新任务;
new ThreadPoolExecutor.DiscardPolicy()
  1. 忽略老任务;
new ThreadPoolExecutor.DiscardOldestPolicy()
  1. 自定义拒绝策略;
ThreadPoolExecutor executor =
                new ThreadPoolExecutor(5, 5, 0, TimeUnit.SECONDS, new LinkedBlockingDeque<>(0), new RejectedExecutionHandler() {
                    @Override
                    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {

                    }
                });

在这里插入图片描述

线程池的2种执行方式:

  1. execute 执行 (new Runnable)无返回值的 ;
  2. submit 执行 (new Runnable/new Callable 有返回值的);
public static void main(String[] args) throws ExecutionException, InterruptedException {
        ThreadPoolExecutor executor =
                new ThreadPoolExecutor(5, 5, 0, TimeUnit.SECONDS, new LinkedBlockingDeque<>(5));
        Future<Object> future = executor.submit(new Callable<Object>() {
            @Override
            public Object call() throws Exception {
                //生成随机值
                int num = new Random().nextInt(10);
                System.out.println("生成随机数" + num);
                //返回参数
                return num;
            }
        });
        System.out.println(future.get());
    }

//执行结果
生成随机数6
6

执行方法的区别:

  1. Execute 只能执行 Runnable 任务,它是无返回值的;submit 它能执行 Runnable 又能执行Callable。
  2. execute 执行任务如果有OOM异常会将异常打印到控台;submit 执行任务出现了OOM 异常时不会打印异常。

线程池的特征:线程池相比较于线程来说是长生命周期,即使没有任务了,也会运行并等待任务

线程池的关闭:

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

线程池状态

在这里插入图片描述

ThreadPoolExecutor 参数:

  1. 核心线程数;
  2. 最大线程数;
  3. 生存时间;
  4. 时间单位;
  5. 任务队列;
  6. 线程工厂;
  7. 拒绝策略;

ThreadLocal

线程级别的私有变量

使用
Set(T) : 将变量存放到线程中;
get():从线程中取得私有变量;
remove() : 从线程中移除私有变量(脏读、内存溢出)。
initialValue :初始化
withInitial:初始化

面试题:什么情况下不会执行 initialValue? 为什么不执行?
set 之后就不会执行了,ThreadLocal 是懒加载的,当调用了get方法之后,才会尝试执行 intialValue(初始化)方法,尝试获取一下 ThreadLocal set 的值,如果获取到了值,那么初始化方法永远不会执行。

ThreadLocal 使用场景:

  1. 解决线程安全问题;
  2. 实现线程级别的数据传递;

ThreadLocal 缺点:

  1. 不可继承性:
static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        threadLocal.set("Java");
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                String result = threadLocal.get();
                System.out.println(result);
            }
        });
        t1.start();
    }

//执行结果
null


//解决方案
static ThreadLocal<String> threadLocal = new InheritableThreadLocal<>();

    public static void main(String[] args) {
        threadLocal.set("Java");
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                String result = threadLocal.get();
                System.out.println(result);
            }
        });
        t1.start();
    }

//执行结果
Java
  1. 脏数据:在一个线程中读取到了不属于自己的值
static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        ThreadPoolExecutor executor =
                new ThreadPoolExecutor(1, 1, 0, TimeUnit.SECONDS,
                        new LinkedBlockingQueue<>(1000));
        for (int i = 0; i < 2; i++) {
            MyTask t = new MyTask();
            executor.execute(t);
        }
    }

    static class MyTask extends Thread{
        static boolean fir = true;

        @Override
        public void run() {
            if(fir) {
                threadLocal.set(this.getName());
                fir = false;
            }

            String result = threadLocal.get();
            System.out.println(this.getName() +
                    ":" + result);
        }
    }

//执行结果
Thread-0:Thread-0
Thread-1:Thread-0

/*
解决办法:
1.避免使用静态属性(静态属性在线程池中会复用)
2.使用 remove 解决
*/
static class MyTa

以上是关于常见线程池详解(一篇足以)的主要内容,如果未能解决你的问题,请参考以下文章

Java线程池详解

Java线程池详解

Java 线程池详解

Java线程池详解

Java线程池详解

多线程中线程池常见7个参数的详解