线程池创建的方式

一、线程的缺点:

  1. 线程的创建会开辟本地方法栈、虚拟机栈、程序计数器等线程私有的内存对象,同时销毁的时候需要销毁以上三个区域,因此频繁的创建和销毁比较消耗系统资源。
  2. 在任务量远远大于线程可以处理的任务量的时候,并不能友好的拒绝任务。

阿里巴巴开发手册规定:线程必须通过线程池提供,不允许在应用中显示的创建线程(因为上面的线程的缺点)

二、线程池:使用池化技术来管理线程和使用线程的方式。

1.线程池的优点

  1. 可以避免频繁的创建和销毁线程,即可以避免性能开销(不管线程是否使用,都放在池子里面)
  2. 可以更好的管理线程的个数和资源的个数(管理和分配任务)
  3. 拥有更多的功能,比如线程池可以进行定时任务的执行
  4. 线程池可以更友好的拒绝不能处理的任务

2.线程池的创建总共分为7种

(1). 线程池创建方式1:创建固定个数的线程池

任务量的个数比线程的个数少的时候,会创建几个线程?
会创建任务量的个数!!!!

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

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

        //执行任务
        //线程池不需要启动
        executorService.execute(new Runnable() {    //传递Runnable任务
            @Override
            public void run() {
                System.out.println("线程名"+Thread.currentThread().getName());
            }
        });

        //执行第二个任务
        //可以执行多个任务
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程名"+Thread.currentThread().getName());
            }
        });

        //当运行的程序比线程池的数量多的时候,就会利用前面已经创建好的线程执行任务
        //实现线程复用
    }
}
(2). 线程池创建方式2:创建带缓存的线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPollDemo48 {
    public static void main(String[] args) {
        // 创建方式2 :创建带缓存的线程池
        ExecutorService executorService = Executors.newCachedThreadPool();

        /**
         * 和创建固定大小线程池的区别:将线程缓存一段时间
         * 当前面的任务已经执行结束(线程空闲),就可以复用前面的空闲的线程了
         * 使用场景:短期有大量任务的时候使用newCachedThreadPool()。
         */

        for (int i = 0; i < 10; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程名:"+Thread.currentThread().getName());
                }
            });
        }
    }
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

/**
 * 自定义线程池规则(线程池的名称,优先级。。。。。。)
 */

public class ThreadPollDemo49 {
    public static void main(String[] args) {
        //设置线程优先级
        //自定义线程工厂
        MyThreadFactor myThreadFactor = new MyThreadFactor();   //线程工厂
        ExecutorService executorService = Executors.newFixedThreadPool(10,myThreadFactor);
        for (int i = 0; i < 10; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    Thread thread = Thread.currentThread();  //获取当前线程
                    System.out.println("线程名:"+thread.getName() + "优先级:"+thread.getPriority());
                }
            });
        }
    }

    //线程命名使用
    private static int count = 1;
    
    static class MyThreadFactor implements ThreadFactory {
        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r);  //注意要将任务传递给线程
            //自定义线程池的名称规则
            thread.setName("mypoll-"+ count++);
            thread.setPriority(10);  //设置线程的优先级
            return thread;
        }
    }
}
线程名:mypoll-1优先级:10
线程名:mypoll-4优先级:10
线程名:mypoll-5优先级:10
线程名:mypoll-2优先级:10
线程名:mypoll-3优先级:10
线程名:mypoll-6优先级:10
线程名:mypoll-7优先级:10
线程名:mypoll-8优先级:10
线程名:mypoll-9优先级:10
线程名:mypoll-10优先级:10
(3). 线程池创建方式3:创建可以执行定时任务的线程池
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ThreadPollDemo50 {
    public static void main(String[] args) {
        /**
         * 创建方式3: 创建可以执行定时任务的线程池
         */
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);  //需要设置参数,线程池的个数   注意接收对象不同了

        System.out.println("设置定时任务:"+new Date());
        //执行定时任务
        /**
         * 1.需要四个参数:
         * 线程执行任务Runnable  long类型的时间(设置了这行代码之后延迟多久去执行任务,单位是后面设置的)  定时任务执行的频率(下面的执行每多久执行)  第二、三个参数的时间的单位
         */
        scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                try {
                    //休眠1秒查看和3的区别
                    //即使休眠了1秒,执行的频率仍然是第三个参数的频率
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("执行任务:"+new Date());
            }
        },1,3, TimeUnit.SECONDS);

        /**
         * 2.传递三个参数  
         * 线程任务   延迟多久开始执行一次    延迟时间的单位
         * ★★★  schedule 只会执行一次(延迟多久以后执行一次)(设置的时间就是多久以后执行一次)
         */
        /*scheduledExecutorService.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("执行任务:"+new Date());
            }
        },1,TimeUnit.SECONDS);*/


        /**
         * 3.传递和scheduleAtFixedRate一样的四个参数
         */
        scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                try {
                    //休眠1秒查看和1的区别
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("执行任务:"+new Date());
            }
        },1,3,TimeUnit.SECONDS);
    }
}
  • 线程未休眠时:
    (1).scheduleAtFixedRate执行的结果
设置定时任务:Fri May 21 17:48:05 CST 2021
执行任务:Fri May 21 17:48:06 CST 2021
执行任务:Fri May 21 17:48:09 CST 2021
执行任务:Fri May 21 17:48:12 CST 2021
执行任务:Fri May 21 17:48:15 CST 2021
执行任务:Fri May 21 17:48:18 CST 2021
......

(2).scheduleWithFixedDelay 执行的结果

设置定时任务:Fri May 21 17:51:03 CST 2021
执行任务:Fri May 21 17:51:04 CST 2021
执行任务:Fri May 21 17:51:07 CST 2021
执行任务:Fri May 21 17:51:10 CST 2021
执行任务:Fri May 21 17:51:13 CST 2021
......

我们会发现上面的两种方法没有什么区别,但是下面的情况就不相同了!!!

  • 线程休眠一段时间时:
    (1).scheduleAtFixedRate执行的结果
设置定时任务:Fri May 21 17:56:25 CST 2021
执行任务:Fri May 21 17:56:27 CST 2021
执行任务:Fri May 21 17:56:30 CST 2021
执行任务:Fri May 21 17:56:33 CST 2021
执行任务:Fri May 21 17:56:36 CST 2021
......

(2).scheduleWithFixedDelay 执行的结果

设置定时任务:Fri May 21 17:53:54 CST 2021
执行任务:Fri May 21 17:53:56 CST 2021
执行任务:Fri May 21 17:54:00 CST 2021
执行任务:Fri May 21 17:54:04 CST 2021
执行任务:Fri May 21 17:54:08 CST 2021
执行任务:Fri May 21 17:54:12 CST 2021
......

scheduleAtFixedRate和scheduleWithFixedDelay参数都传递的相同,那么他俩有什么区别???

  • scheduleAtFixedRate 以上次任务的开始时间作为下一次任务的开始时间的
  • scheduleWithFixedDelay 以上次任务的结束时间作为下一次任务的开始时间的
(4). 线程池创建方式4:创建单线程执行定时任务的线程池
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ThreadPollDemo51 {
    public static void main(String[] args) {
        /**
         * 创建线程池方式4: 创建单个执行定时任务的线程池
         * 以上一次任务结束时间作为这次任务的开始时间的!!!!!!!
         * 第三种创建方式的单机版本
         */
        ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();  //创建单个线程执行定时任务
        service.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("执行任务:"+new Date());
            }
        },1,3, TimeUnit.SECONDS);
    }
}
执行任务:Fri May 21 18:08:40 CST 2021
执行任务:Fri May 21 18:08:44 CST 2021
执行任务:Fri May 21 18:08:48 CST 2021
执行任务:Fri May 21 18:08:52 CST 2021
......
(5). 线程池创建方式5:创建单个线程
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPollDemo52 {

    public static void main(String[] args) {
        //创建方式5: 创建单个线程
        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.线程池可以更好的管理和分配任务(可以保证任务的顺序)
(6). 线程池创建方式6:JDK8以后才有的,根据当前的硬件CPU和任务量生成对应个数的线程池,并且是异步执行的
     异步的方式:(执行即结束)
     1.发请求
     2.执行完成
     3.另一个程序异步处理,处理完成后进行回调返回结果
     
     同步的方式:
     1.发请求
     2.等待执行完成
     3.有结果返回
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPollDemo53 {
    public static void main(String[] args) {
        /**
         * JDK8以后才有的创建方式
         * 创建方式6: 根据当前的硬件CPU和任务量生成对应个数的线程池,并且是异步执行的。
         * 生成的最大的线程数 = 当前电脑CPU核数(创建太多的线程可能导致线程争抢和线程切换的问题,降低了执行效率)
         */
        ExecutorService executorService = Executors.newWorkStealingPool();  //根据当前硬件配置创建线程池
        for (int i = 0; i < 10; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程名:"+Thread.currentThread().getName());
                }
            });
        }

        //等待线程池执行完成,就可以打印上面的内容了
        while (!executorService.isTerminated()){

        }
    }
}
线程名:ForkJoinPool-1-worker-1
线程名:ForkJoinPool-1-worker-3
线程名:ForkJoinPool-1-worker-4
线程名:ForkJoinPool-1-worker-4
线程名:ForkJoinPool-1-worker-2
线程名:ForkJoinPool-1-worker-2
线程名:ForkJoinPool-1-worker-2
线程名:ForkJoinPool-1-worker-3
线程名:ForkJoinPool-1-worker-4
线程名:ForkJoinPool-1-worker-1

阿里巴巴手册规定:线程池不允许使用Executors去创建,因为不可控

    线程池的两个重要的组件:
    1.线程
    2.任务队列
    
   前六种线程池可能会存在的问题:
   1.线程的数量不可控(比如创建带缓存的线程池的数量的时候)-> OOM
   2.工作任务队列不可控(前六种的工作队列的默认大小是Integer.MAX_VALUE) -> OOM(内存溢出)
(7). 线程池创建方式7:(原始的线程创建方式)

import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPollDemo55 {
    private static int count = 1;  //计数器

    public static void main(String[] args) {
        /**
         * 参数1:核心线程的数量,线程池正常情况下的数量(正式员工的数量)
         * 参数2:最大的线程数量,当有大量的任务的时候,可以创建的最多的线程数(这个值不能小于 参数1 的值,必须大于等于)(任务太多时临时工+正式员工的数量)
         * 参数3:最大线程存活时间(临时工的存活时间)
         * 参数4:配合参数3一起使用,时间单位
         * 参数5:任务队列(一定要设置初始化大小,不然就是默认大小,可能会导致内存溢出)    //5个参数就可以创建一个线程池了
         * 参数6:线程工厂(设置线程池命名规则,优先级等)
         * 参数7:拒绝策略(默认的拒绝策略是不执行任务,然后将错误抛出) 
         */

       //创建线程工厂
        ThreadFactory threadFactory = new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);  //传递任务!!!!!!!
                thread.setName("myThreadPoll-"+count++);
                return null;
            }
        };

        //原始的创建线程池的方式
        ThreadPoolExecutor threadPoolExecutor =
                new ThreadPoolExecutor(5,5,60,
                        TimeUnit.SECONDS,new LinkedBlockingDeque<>(100),threadFactory); //任务队列建议设置初始值,不设置默认队列时还是Ineger.MAX_VALUE
        threadPoolExecutor.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程名:"+Thread.currentThread().getName());
            }
        });
    }
}

等待队列 LinkedBlockingDeque<>() 可以按序执行任务


线程池的5种拒绝策略:

  1. 默认拒绝策略:不执行任务直接抛出一个异常 ThreadPoolExecutor.AbortPolicy()
  2. 把当前的任务交给主线程执行(使用主线程执行存放不下的线程任务) ThreadPoolExecutor.CallerRunsPolicy()
  3. 丢弃任务队列里面最老的任务 ThreadPoolExecutor.DiscardOldestPolicy()
  4. 丢弃最新加入的任务 ThreadPoolExecutor.DiscardPolicy()
  5. 自定义拒绝策略new RejectedExecutionHandler(){}
  • 自定义拒绝策略(舍弃最新的任务)的演示:
import java.util.concurrent.*;

public class ThreadPollDemo58 {
    private static int count = 1;
    public static void main(String[] args) {

       //创建线程工厂
        ThreadFactory threadFactory = new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);  //传递任务
                thread.setName("myThreadPoll-"+count++);
                return null;
            }
        };

        //原始的创建线程池的方式
        ThreadPoolExecutor threadPoolExecutor =
                new ThreadPoolExecutor(1, 1, 60,
                        TimeUnit.SECONDS, new LinkedBlockingDeque<>(2),
                        threadFactory, new RejectedExecutionHandler() {
                    @Override
                    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                        //自定义拒绝策略,舍弃最新的任务,可以写到日志里面,也可以存储到数据库,也可以什么都不做
                        System.out.println("执行了拒绝策略");
                    }
                }); 
        
        for (int i = 0; i < 10; i++) {
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程名:"+Thread.currentThread().getName());
                }
            });
        }
    }
}

面试题:ThreadPoolExecutor 的执行流程 (三个重要的参数:核心线程数,最大线程数,任务队列)!!!!!!!

  1. 当任务量小于核心线程数的时候,他会创建一个线程来执行此任务,当任务量大于核心线程数的时候,并且没有空闲线程的时候,且当前线程池的线程数小于最大线程数,此时会将任务存到任务队列里面!!!!!注意:因为将多出来的任务存储到任务队列成本最小,所以此时线程池会将新任务存在任务队列当中,而非创建新线程来执行任务;
  2. 当前任务量比较大的时候,此时没有空闲的线程,并且任务队列已经满了,此时会判断当前线程池的任务数量是否大于等于最大线数,如果当前线程池的数量小于最大线程数,创建线程来执行任务;当前线程池的线程数量等于最大线程数并且任务队列已经满了,这个时候会去执行拒绝策略。

流程图如下:
在这里插入图片描述


  • 线程池终止的方法:
  1. 线程池名.shutdown(); (1.停止接收新任务 2.等待任务队列中的任务执行结束然后终止)
  2. 线程池名.shutdownNow(); (1.停止接收新任务 2.立即终止线程池,任务队列的任务不会执行完)

线程池的状态:
在这里插入图片描述

  • RUNNING:运行中,默认的状态
  • SHUTDOWN:当前不能添加新任务了,但是会将当前任务队列里面的线程执行完(线程池调用shutdown会进入的状态)
  • STOP:不执行新任务和队列里面的任务了(线程池调用shutdownNow会进入的状态)
  • TIDYING:销毁的前置状态,当前线程池已经执行完了所有的任务(线程池里面线程数为0时)
    TERMINATED:销毁状态

注意: 线程状态 != 线程池状态


执行线程池的两种方式:

  1. 执行任务没有返回值 excute(Runnable()…)
  2. 执行任务有返回值 submit(Runnable 无返回值/Callable() 有返回值)
import java.util.Random;
import java.util.concurrent.*;

public class ThreadPollDemo60 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,2,10,
                TimeUnit.SECONDS,new LinkedBlockingDeque<>());


        threadPoolExecutor.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("执行excute方法");
            }
        });


        //用Future来接收返回值
        Future<Integer> future = threadPoolExecutor.submit(new Callable<Integer>() {  //返回值类型
            @Override
            public Integer call() throws Exception {
                //生成随机数
                int num = new Random().nextInt(10);
                System.out.println("执行submit方法,生成随机数:"+num);
                return num;
            }
        });
        System.out.println("得到线程池的执行结果:"+future.get());
    }
}

好了,线程池的创建方式到这里全部写完了。好好学习,加油!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值