线程池

线程池的创建方式
1.线程池的创建方式1:创建固定个数的线程池
(任务数趋向无限大、建议谨慎使用)

//创建固定个数的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 2 ; i++) {
            //执行任务
            service.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程名:" +
                            Thread.currentThread().getName());
                }
            });
        }

经典易错题:创建了10个线程来执行2个任务,问当前程序创建了几个线程?
答:2个

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

线程池包含的两个重要内容
1:命名
2:任务队列

自定义线程池的行为:(设置线程池的命名,线程池的优先级)

//线程工厂
        MyThreadFactory myThreadFactory = new MyThreadFactory();
        //创建线程池
       ExecutorService service =
               Executors.newFixedThreadPool(10,myThreadFactory);

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

//创建带缓存的线程池
        ExecutorService service = Executors.newCachedThreadPool();

适用场景:根据任务的数量生成对应的线程数,适用于大量的短期任务的时候

3.线程池的创建方式3:创建一个执行定时任务的线程池
方法1:service.scheduleWithFixedDelay
(以上一个任务的结束时间作为开始时间)

//执行任务
        service.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                System.out.println("执行任务:"+new Date());
            }
        },1,3, TimeUnit.SECONDS);//延迟1s开始执行定时任务,每隔3s执行一次
    }

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

方法2:service.schedule

service.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("执行任务:"+new Date());
            }
        },3, TimeUnit.SECONDS);//即时每隔3s执行一次

schedule区别:
1:没有延迟执行的时间设置
2:定时任务只能执行一次

方法3:service.scheduleAtFixedRate

//执行任务
        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);
    }

方法3:scheduleAtFixedRate:开始时间是以上次任务的开始时间做起始时间的(固定频率执行)
方法1:service.scheduleWithFixedDelay:任务开始时间是以上次任务执行的结束时间作为开始时间的(时间不固定,根据任务执行的时间来定)

4.线程池的创建方式4:创建单个执行定时任务的线程池

 //创建单个执行定时任务的线程池
        ScheduledExecutorService service =
                Executors.newSingleThreadScheduledExecutor();

5.线程池的创建方式5:创建单个线程的线程池

//创建单个线程的线程池
        ScheduledExecutorService service =
                Executors.newSingleThreadScheduledExecutor();
        //执行任务(一个线程执行多个任务)
        for (int i = 0; i < 10; i++) {
            service.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程名:"+
                            Thread.currentThread().getName());
                }
            });
        }

经典面试题:单个线程的线程池有什么意义?
答:1.无需频繁的创建和销毁线程
2.可以更好地分配和管理以及存储任务(将任务存放在任务队列)

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

synchronized:同步
同步:按照某种规则按序执行就叫同步
异步:根据当前工作环境来创建线程

同步执行的流程:
1.main调用线程池
2.线程池执行完之后
3.关闭线程池,main也会随之关闭
异步执行的流程:
1.main调用异步线程池
2.异步线程池后台执行,对于main线程来说异步线程池已经执行完成,关闭main线程

7线程池的创建方式7:

前六种Executors创建线程池可能存在的的问题:
1.线程数量不可控( Integet.MAX_VALUE)(线程的过度切换和争取 -> 程序执行比较慢)
2.任务数量不可控(任务数无限大 Integet.MAX_VALUE),当前任务量比较大的情况下就会造成内存溢出异常(OOM)

//原始的创建线程池的方法
        ThreadPoolExecutor executor =
                new ThreadPoolExecutor(5,10,
                        60, TimeUnit.SECONDS,
                        new LinkedBlockingDeque<>(1000));
        for (int i = 0; i < 2 ; i++) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程名:" +Thread.currentThread().getName());
                }
            });
        }

线程池创建线程是懒加载,当执行任务的时候才创建线程,并不是new的时候

1、问:假设:核心线程数:5 最大线程数:5 任务队列:5

当任务 = 11:
第一步:11-5 = 6
第二步:6-5 = 1
第三步:最大线程数-线程池的线程数量 = 0;线程池执行拒绝策略

拒绝策略:
(1)自定义拒绝策略

ThreadPoolExecutor executor = new ThreadPoolExecutor(
                5, 5, 0, TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(5),
                new RejectedExecutionHandler() {
                    @Override
                    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                        //自 定义拒绝策略
                        System.out.println("执行了自定义拒绝策略");
                    }
                });

(2)JDK四种策略:

  • 默认的拒绝策略
  • 使用线程池的线程来执行任务,使用主线程来执行任务
  • new ThreadPoolExecutor.DiscardPolicy()
    忽略新的任务
    new ThreadPoolExecutor.DiscardOldestPolicy()
    忽略老任务

线程池的两种执行方式:
1、execute执行(new Runnable)无返回值的
2、submit执行(new Runnable无返回值/new Callable有返回值)

//返回返回值
        Future<Integer> future = executor.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int num = new Random().nextInt(10);
                System.out.println("线程池生成了随机数"+num);
                return num;
            }
        });

        System.out.println("main得到返回值" + future.get());
    }

线程池

  • 区别:
    1、execute只能执行Runnable任务,他是无返回值的;submit他既能执行Runnable无返回值的任务,也可以执行Callable有返回值的任务
    2、execute知心人跟吴如果有OOM异常 会将异常打印到控制台;submit执行任务出现OOM异常 不会打印异常

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

  • 线程池的关闭:
    1、executor.shutdown() :拒绝新任务加入,等待线程池中的任务队列执行完后,再停止线程池
    2、executor.shutdownNow(): 拒绝执行新任务,不会等待任务队列中的任务执行完成,就停止线程池

  • 线程池状态:(只是给开发者可见的 ,客户机是不可见的)
    注意:线程池状态不是线程状态(6种)
    线程池

ThreadLocal:
1、set()将变量存放在线程中
2、get()从线程中取得私有变量
3、remove()从线程中移除私有变量

线程池
问:什么时候不会执行initialValue?为什么不会执行?

  • set之后就不执行了 ThreadLocal是懒加载的,当调用了get()方法之后,

初始化方法2:withInitial
1、

static ThreadLocal<String> threadLocal =
            ThreadLocal.withInitial(new Supplier<String>() {
                @Override
                public String get() {
                    System.out.println("执行了初始化方法");
                    return "Java";
                }
            });

    public static void main(String[] args) {
        String result = threadLocal.get();
        System.out.println("结果是:" + result);
    }

2、

static ThreadLocal<String> threadLocal =
            ThreadLocal.withInitial(() -> "Java");

    public static void main(String[] args) {
        String result = threadLocal.get();
        System.out.println("结果是:" + result);
    }

ThreadLocal 使用场景:
1、解决线程安全问题
2、实现线程级别的数据传递
(以下示例)

public static void main(String[] args) {
        //模拟用户的登录
        User user = new User();
        user.setUsername("Java");
        ThreadLocalUtil.threadLocal.set(user);
        //调用订单系统
        Order order = new Order();
        order.getOrder();

        //调用日志系统
        Logger logger = new Logger();
        logger.addLog();
    }
    static class ThreadLocalUtil{
        static ThreadLocal<User> threadLocal = new ThreadLocal<>();
    }
    static class User{
        private String username;

        public void setUsername(String username) {
            this.username = username;
        }

        public String getUsername() {
            return username;
        }
    }
    /*
    * 日志系统*/
    static class Logger {
        public void addLog(){
            //得到用户信息
            User user = ThreadLocalUtil.threadLocal.get();
            //
            System.out.println("添加日志:"+user.getUsername());
        }
    }
    static class Order{
        public void getOrder(){
            User user = ThreadLocalUtil.threadLocal.get();
            //
            System.out.println("订单列表:" + user.getUsername());
        }
    }

ThreadLocal缺点:
1:不可继承(子线程不能访问父线程的值)

static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        //set ThreadLocal
        threadLocal.set("Java");

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                //get ThreadLocal
                String result = threadLocal.get();
                System.out.println("结果是:" + result);
            }
        });
        t1.start();

使用ThreadLocal.InheritableThreadLocal可继承:但是同级线程不可继承
线程池
2:脏读(脏数据):在一个线程内读到了不属于自己的数据

  • 线程使用ThreadLocal 不会出现脏读 -> 每个线程都是用自己的变量值和ThreadLocal
  • 线程池里面使用 ThreadLocal 就会出现脏读,线程池会复用线程,复用线程之后,也会复用线程中的静态属性,从而导致了某些方法不会被执行,于是就出现了脏数据问题

脏数据的解决方案:

  • 避免使用静态属性(静态属性在线程池中会复用)
  • 使用remove
    线程池

3:内存溢出问题:(最常出现的问题)、打开数据连接但未关闭
内存溢出:当一个线程执行完之后,不会释放这个线程所占用的内存,或者释放内存不及时的情况都叫做内存溢出。->线程不用了,但线程相关的内存还得不到相关的释放

上一篇:线程基础、线程之间的共享和协作


下一篇:【Java技术探索】ThreadLocal深入浅出的源码分析(核心源码)