JAVA-Web-基础-线程池

35 篇文章 0 订阅
15 篇文章 0 订阅

引子

引入:线程的缺点:
1.线程的创建需要开辟内存资源,

  1. 本地方法栈,

  2. 虚拟机栈

  3. 程序计数器等线程私有变量的内存

频繁的创建和消耗会出现一定的性能开销
2. 使用线程不能很好的管理任务和友好的拒绝任务
3. 强制:线程资源必须通过线程池提供,不允许在应用中自行显示的创建线程,
线程池可以减少在创建和销毁线程上锁消耗的时间以及系统资源的开销,解决资源不足的问题,如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或“过度切换”的问题;

线程池定义:

指的是池化技术来管理和使用线程的技术,
线程池包含的重要内容
1.线程
2.任务队列
自定义线程行为:设置线程池命名规则,线程池的的优先级
线程池的创建:两类共7种

一:Executors创建

1.创建固定个数的线程池;

题目:创建了10个线程来执行2个任务,当前程序创建了几个线程?

 ExecutorService threadPool = Executors.newFixedThreadPool(2);

2个,
解析:当任务数少于设置的线程数时,会根据任务数创建线程,任务数超过线程数时,会复用线程,
代码实例:

ExecutorService threadPool = Executors.newFixedThreadPool(2);
// 执⾏任务 1
threadPool.submit(new Runnable() {
  @Override
   public void run() {
        System.out.println(Thread.currentThread().getName());
   }
 });
 // 执⾏任务 2
 threadPool.execute(new Runnable() {
    @Override
   public void run() {
        System.out.println(Thread.currentThread().getName());
    }
});

自定义线程的名称或优先级:

public static void main(String[] args) throws ExecutionException, InterruptedException {
        //线程工厂--批量的制作线程
        ThreadFactory threadFactory = new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setName("Demo"+r.hashCode());
                thread.setPriority(Thread.MAX_PRIORITY);
                return thread;
            }
        };
        ExecutorService threadPool = Executors.newFixedThreadPool(2,threadFactory);
        //线程返回结果的执行方式:submit
        Future<Integer> result = threadPool.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int num = new Random().nextInt(10);
                System.out.println(Thread.currentThread().getName()+"随机数"+num);
                return num;
            }
        });
        System.out.println("线程执行结果"+result.get());
    }
2.创建带缓存的线程池;

不需要设置线程数量,根据任务设置,
适用于短期大量任务;

ExecutorService threadPool = Executors.newCachedThreadPool(threadFactory);
3.可以执行定时任务的线程池;

1.可以按频率执行的方式

public static void main(String[] args) throws ExecutionException, InterruptedException {
        
        ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
//执行任务
        System.out.println("scheduleWithFixedDelay之前"+new Date());
        //以上次任务的结束时间为开始时间
        service.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                System.out.println("scheduleWithFixedDelay"+new Date());
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },1,3, TimeUnit.SECONDS);
        //以上次任务的开始时间为开始
        service.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("scheduleAtFixedRate"+new Date());
            }
        },3,2,TimeUnit.SECONDS);
    }

参数1:线程池的任务
参数2:定时任务第一次执行时的时间
参数3:此后每次的间隔时间;
参数4:时间间隔单位;
2.延迟执行一次

service.schedule(new Runnable() {
    @Override
    public void run() {
        System.out.println("执行任务"+new Date());
    }
},3, TimeUnit.SECONDS);

schedule和以上的区别:
1.只能执行一次
2.没有延迟执行的时间设置
scheduleAtFixedRate对比service.scheduleWithFixedDelay
scheduleAtFixedRateservice开始时间是以上次任务的开始时间为开始时间的;
scheduleWithFixedDelay任务是以上次任务执行的结束时间作为开始时间,根据任务执行时间定的

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

单个线程池有什么意义?

  • 无需频繁的创建和销毁线程
  • 可以更好的分配和管理以及存储任务–任务队列
public static void main(String[] args) throws ExecutionException, InterruptedException {

        ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
//执行任务
        System.out.println("定时任务"+new Date());
        service.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("schedule"+new Date());
            }
        },2,TimeUnit.SECONDS);
    }
5.单个线程的线程池:
  • a.频繁的创建和消耗
  • b.更好的分配和执行任务
  • c.将任务存放到任务队列
ExecutorService service= Executors.newSingleThreadExecutor();
6.JDK8+支持;根据CPU和任务生成对应的线程池

异步线程池;
同步执行:主线程调用线程池,线程池执行完之后关闭线程池,main也随之关闭,
异步执行:main调用异步线程池,异步线程池由后台执行,对于main线程,异步线程池已经执行完成,关闭main线程;

public static void main(String[] args) throws ExecutionException, InterruptedException {

        ExecutorService service = Executors.newWorkStealingPool();
//执行任务
        for (int i = 0; i < 100; i++) {
            int count = i;
            service.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println(count + ":" + Thread.currentThread().getName());
                }
            });
        }
        while(!service.isTerminated());
    }

在这里插入图片描述

二:ThreadPoolExecutor

前6中可能存在的问题:

在《阿里巴巴java开发手册》中指出了线程资源必须通过线程池提供,不允许在应用中自行显示的创建线程,这样一方面使得线程的创建更加规范,可以合理控制开辟线程的数量;另一方面线程的细节管理交给线程池处理,优化了资源的开销。
线程池不允许使用 Executors 去创建,而要通过 ThreadPoolExecutor 方式,这一方面是由于 jdk 中
Executor 框架虽然提供了如
newFixedThreadPool()、newSingleThreadExecutor()、newCachedThreadPool()
等创建线程池的方法,但都有其局限性,不够灵活;另外由于前面几种方法内部也是通过 ThreadPoolExecutor 方式实现,使用
ThreadPoolExecutor 有助于明确线程池的运行规则,创建符合自己业务场景需要的线程池,避免资源耗尽的风险。

7.ThreadPoolExecutor
public static void main(String[] args) throws ExecutionException, InterruptedException {
        ThreadFactory threadFactory = new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setName("Demo"+r.hashCode());
                thread.setPriority(Thread.MAX_PRIORITY);
                return thread;
            }
        };
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10,10,60,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(100),
                threadFactory,
                new ThreadPoolExecutor.AbortPolicy());
        threadPool.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        });


    }

参数说明:

  1. corePoolSize:所谓的核⼼线程数,可以⼤致理解为⻓期驻留的线程数⽬(除⾮设置了
    allowCoreThreadTimeOut)。对于不同的线程池,这个值可能会有很⼤区别,⽐如 newFixedThreadPool 会将其设置为nThreads,⽽对于 newCachedThreadPool 则是为 0。maximumPoolSize:顾名思义,就是线程不够时能够创建的最⼤线程数。同样进⾏对⽐,对于 newFixedThreadPool,当然就是 nThreads,因为其要求是固定⼤⼩,⽽ newCachedThreadPool 则是 Integer.MAX_VALUE
  2. keepAliveTime:空闲线程的保活时间,如果线程的空闲时间超过这个值,那么将会被关闭。注意此值⽣效条件必须满⾜:空闲时间超过这个值,并且线程池中的线程数少于等于核⼼线程数corePoolSize。当然核⼼线程默认是不会关闭的,除⾮设置了allowCoreThreadTimeOut(true)那么核⼼线程也可以被回收。
  3. TimeUnit:时间单位。
  4. BlockingQueue:任务队列,⽤于存储线程池的待执⾏任务的。
  5. threadFactory:⽤于⽣成线程,⼀般我们可以⽤默认的就可以了。
  6. handler拒绝策略:当线程池已经满了,但是⼜有新的任务提交的时候,该采取什么策略由这个来指定。有⼏种⽅式可供选择,像抛出异常、直接拒绝然后返回等,也可以⾃⼰实现相应的接⼝实现⾃⼰的逻辑。
面试题:

核心线程:5,最大线程:10 ,任务队列:5
目前有5个任务,增加了一个,应该将其放在队列还是创建线程执行。
要让任务成本低就要将多出现的任务放到任务队列。
当前增加了11个任务时,
此时会判断最大线程数,如果最大线程数没有达到,就会创建一个线程执行任务。

线程池的5种拒绝策略:

线程池创建线程是懒加载的模式,只有在有任务的时候,才会创建线程,并不是new的时候创建。
拒绝策略:

最大线程数-线程池数量=0,线程池执行拒绝策略。

  1. AbortPolicy默认方式:直接抛出RejectedExecutionException异常阻止系统正常运行。
public  class Main{
    public static void main(String[] args){
        ThreadPoolExecutor executor = new ThreadPoolExecutor(5,5,0, TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(5),new ThreadPoolExecutor.AbortPolicy());
                //核心线程数5,最大线程数5,存活时间0,队列任务数5
        for (int i = 0; i <11 ; i++) {
            int finalI = i;
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("任务"+finalI+"-线程名:"+Thread.currentThread().getName());
                }
            });
        }
    }
}

在这里插入图片描述

  1. CallerRunsPolicy使用-调用线程池的线程-来执行任务,
public static void main(String[] args) {
    ThreadPoolExecutor executor = new ThreadPoolExecutor(
            5,5
            ,0, TimeUnit.SECONDS,
            new LinkedBlockingDeque<>(5),
            new ThreadPoolExecutor.CallerRunsPolicy()
    );
    for (int i = 0; i < 11; i++) {
        int finalI = i;
        executor.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("任务"+finalI+"线程名"+Thread.currentThread().getName());
            }
        });
    }
}

在这里插入图片描述
以下代码值改变了拒绝策略的定义,懒得贴上来了。
3. DiscardPolicy舍弃新任务:直接丢弃任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种方案
在这里插入图片描述
4. DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入到队列中尝试再次提交当前任务,执行结果如下图所示:丢弃了任务5;任务在执行过程中以队列的形式进行逐个执行,任务5在执行时刚好需要等待第一次任务队列中的5个任务执行完成,因此会被拒绝执行,在之后,任务6,7,8,9,10刚好5个任务又进入任务队列,都不需要等待,依次执行即可。
在这里插入图片描述
5. 自定义拒绝策略

public static void main(String[] args) {
    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("自定义");
                }
            }
    );
    for (int i = 0; i < 11; i++) {
        int finalI = i;
        executor.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("任务"+finalI+"线程名"+Thread.currentThread().getName());
            }
        });
    }
}

在这里插入图片描述

线程池的两种执行方式:

1.execute 执行new Ruunable无返回值
2.submit执行Callable得到返回值,通过Future接收
如下:

public static void main(String[] args) throws ExecutionException, InterruptedException {
    ThreadPoolExecutor executor = new ThreadPoolExecutor(
            5,5,0, TimeUnit.SECONDS,
            new LinkedBlockingDeque<>(1000)
    );
    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无返回值的任务,
2.submit可以执行Runnable,也能执行Callable有返回值的任务。
3.execute遇到oom的异常会弹出,submit不会打印
线程池的状态:
在这里插入图片描述
线程池的状态与线程的状态不相关;

  1. RUNNING:线程池创建之后的初始状态,这种状态下可以执行任务。
  2. SHUTDOWN:该状态下线程池不再接收新任务,但是会将工作队列中的任务执行结束
  3. STOP:该状态下线程池不再接收新任务,但是不会处理工作队列中的任务,并且将会中断线程
  4. TIDYING:该状态下所有任务都已终止,将会执行terminated()钩子方法
  5. TERMINATED:执行terninated钩子方法之后。

总结:

线程池的特征:
1.相比于线程是长生命周期的,即使没有任务也会运行,等待任务。
线程池的关闭:
1.shutdown:shutdown拒绝新任务加入。等待线程池中任务队列执行完之后再销毁线程池,
2.shutdownNow:拒绝等待新任务,不会等待任务队列执行完,就会直接停止线程池。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值