zoukankan      html  css  js  c++  java
  • 线程池

    在一个应用程序中,我们需要多次使用线程,也就意味着,我们需要多次创建并销毁线程。而创建并销毁线程的过程势必会消耗内存。而在Java中,内存资源是及其宝贵的,所以,我们就提出了线程池的概念。

    线程池:Java中开辟出了一种管理线程的概念,这个概念叫做线程池,从概念以及应用场景中,我们可以看出,线程池的好处,就是可以方便的管理线程,也可以减少内存的消耗。

    线程池的优势:

    线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量超出数量的线程排队等待,等其它线程执行完毕,再从队列中取出任务来执行。

    主要特点:线程复用、控制最大并发数、管理线程。

    1.降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

    2.提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。

    3.提高线程的课管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

    Java中的线程池是通过Executor框架来实现的,而我们创建时,一般使用它的子类:ThreadPoolExecutor。

     

    java中提供了一个静态工厂方法来创建不同的线程池:Executors

    通过静态方法创建出的线程都实现了ExecutorService接口

    常用的方法包括:

    FixedThreadPool(int threads):定长的线程池,有核心线程,核心线程的即为最大的线程数量,没有非核心线程

    SingleThreadPool():只有一条线程来执行任务,适用于有顺序的任务的应用场景。

    CachedThreadPool():可缓存的线程池,该线程池中没有核心线程,非核心线程的数量为Integer.max_value,就是无限大,当有需要时创建线程来执行任务,没有需要时回收线程,适用于耗时少,任务量大的情况。

    SecudleThreadPool:周期性执行任务的线程池,按照某种特定的计划执行线程中的任务,有核心线程,但也有非核心线程,非核心线程的大小也为无限大。适用于执行周期性的任务。

    WorkStealingPool:java8新增,使用目前机器上可用的处理器作为它的并行级别。

    常用的线程池创建方法示例如下:

    /**
     * 第四种获取线程的方式-线程池
     */
    public class ThreadPoolDemo {
    
        public static void main(String[] args) {
            ExecutorService threadPool=Executors.newFixedThreadPool(5);//一池5个线程
            //ExecutorService threadPool=Executors.newSingleThreadExecutor();//一池一个线程
            //ExecutorService threadPool=Executors.newCachedThreadPool();//一池N线程,根据情况来创建
    
            try{
                //模拟十个用户来办理业务
                for(int i=1;i<=10;i++){
                    //submit有返回值
                    Future future=threadPool.submit( () -> {
                        System.out.println(Thread.currentThread().getName()+" 办理业务");
                        return "办理业务成功";
                    });
                    System.out.println("获取结果"+future.get());
                    //execute无返回值
                    threadPool.execute(()->{
                        System.out.println(Thread.currentThread().getName()+" 办理业务");
                    });
                }
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                threadPool.shutdown();
            }
        }
    }

    线程池的底层就是ThreadPoolExecutor类。

    1.Executors.FixedThreadPool(int),执行长期的任务,性能好很多

     主要特点如下:

    1).创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

    2).newFixedThreadPool创建的线程池corePoolSize和maximumPoolSize值是相等的,它使用的是LinkedBlockingQueue。

    2.Executors.SingleThreadPool( ),一个任务一个任务执行的场景

     主要特点如下:

    1).创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序执行。

    2).newSingleThreadExecutor将corePoolSize和maximumPoolSize都设置为1,它使用的LinkedBlockingQueue。

    3.Executors.CachedThreadPool( ),适用:执行很多短期异步的小程序或者负载较轻的服务器。

     主要特点如下:

    1).创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

    2).newCachedThreadPool将corePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,使用的SynchronousQueue,也就是说来了任务就创建线程运行,当线程空闲超过60秒,就销毁线程。

     七大参数

    1.corePoolSize:线程池中的常驻核心线程数

    在创建了线程池后,当有请求任务来之后,就会安排池中的线程去执行请求任务,近似理解为今日当值线程;

    当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中。

    2.maximumPoolSize:线程池能够容纳同时执行的最大线程数,此值必须大于等于1

    3.keepAliveTime:多余的空闲线程的存活时间。当前线程池数量超过corePoolSize时,当空闲时间达到keepAliveTime值时,多余空闲线程会被销毁直到只剩下corePoolSize个线程为止。

    默认情况下:只有当线程池中的线程数大于corePoolSize时keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize。

    4.unit:keepAliveTime的单位

    5.workQueue:任务队列,被提交单尚未被执行的任务。

    6.threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程一般用默认的即可。

    7.handler:拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数(maximumPoolSize)时如何来拒绝请求执行的runnable的策略。

    拒绝策略是当等待队列也已经排满了,再也塞不下新任务了,同时,线程池中的max线程也达到了了,无法继续为新任务服务。这时候我们就需要拒绝策略机制合理的处理这个问题。

    JDK内置的拒绝策略

    1)AbortPolicy(默认):直接抛出RejectedExecutionException异常阻止系统正常运行。

    2)CallerRunsPolicy:"调用者运行"一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。

    3)DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务。

    4)DiscardPolicy:直接丢弃任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种方案。

     以上内置拒绝策略均实现了RejectedExecutionHandler接口

    线程池的底层工作原理

    1.在创建了线程池后,等待提交过来的任务请求。

    2.当调用execute()方法添加一个请求任务时,线程池会做如下判断:

    1)如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;

    2)如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列;

    3)如果这时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;

    4)如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。

    3.当一个线程完成任务时,它会从队列中取下一个任务来执行。

    4.当一个线程无事可做超过一定的时间(keepAliveTime)时,线程池会判断:

      如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。

      所以线程池的所有任务完成后它最终会收缩到corePoolSize的大小。

     实际开发中一般手动写线程池,这样合理利用线程资源,避免资源耗尽的。

     手写线程池示例:

    public class MyThreadPoolDemo {
    
        public static void main(String[] args) {
            //自定义线程池
            ExecutorService threadPool= new ThreadPoolExecutor(
                    2,
                    5,
                    1L,
                    TimeUnit.MILLISECONDS,
                    new LinkedBlockingQueue<Runnable>(3),//自定义阻塞队列长度为3
                    Executors.defaultThreadFactory(),
                    new ThreadPoolExecutor.AbortPolicy()//自定义拒绝策略,默认为AbortPolicy,CallerRunsPolicy,DiscardOldestPolicy,DiscardPolicy
            );
    
           try{
               //模拟十个用户来办理业务,每个用户都是一个来自外部的请求线程
               for(int i=1;i<=10;i++){
                   //无返回值
                  threadPool.execute(()->{
                       System.out.println(Thread.currentThread().getName()+"	 办理业务");
                   });
               }
           }catch (Exception e){
               e.printStackTrace();
           }finally {
               //关闭线程池
               threadPool.shutdown();
           }
        }  

    使用默认的拒绝策略,能够访问的上限数=当最大线程数(maximumPoolSize) + 任务队列长度,当超过就会报java.util.concurrent.RejectedExecutionException异常。

    死锁编码及定位分析

    死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉那它们都将无法推进下去。如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。

    死锁示例:

    /**
     * 死锁是指两个或者两个以上的进程在执行过程中,
     * 因争夺资源而造成的一种相互等待的现象,
     * 若无外力干涉,那它们就无法推进下去,从而造成死锁现象。
     */
    public class DeadLockDemo {
    
        public static void main(String[] args) {
            String lockA = "lockA";
            String lockB = "lockB";
    
            new Thread(new DeadLockResource(lockA, lockB), "ThreadA").start();
            new Thread(new DeadLockResource(lockB, lockA), "ThreadB").start();
        }
    }
    
    /**
     * 线程操作资源类
     */
    class DeadLockResource implements Runnable {
    
        private String lockA;
        private String lockB;
    
        public DeadLockResource(String lockA, String lockB) {
            this.lockA = lockA;
            this.lockB = lockB;
        }
    
        @Override
        public void run() {
            synchronized (lockA) {
                System.out.println(Thread.currentThread().getName() + "	 拥有" + lockA + ",试图获取" + lockB);
                System.out.println("----------*****************----------");
                synchronized (lockB) {
                    System.out.println(Thread.currentThread().getName() + "	 拥有" + lockB + ",试图获取" + lockA);
                }
            }
        }
    }

    ThreadA持有lockA,试图获取lockB,而ThreadB持有lockB,试图获取lockA。造成两个线程相互等待,又没有外力干涉,无法推进下去,最终导致死锁现象。

    产生死锁的主要原因

    1.系统资源不足

    2.进程运行推进的顺序不合适

    3.资源分配不当

    死锁解决

    Linux环境下查看进程:ps -ef | grep java

    Windows环境下查看java进程:jps  -l

    Windows环境下查看死锁栈信息:jstack +进程号

    1.jps命令定位进程号

    2.jstack找到死锁查看

     

     只有停下程序,找到对应业务代码进行修改。

    合理配置线程池:

    首先得知道服务器cpu核心数

    获取cpu核心数

    Runtime.getRuntime().availableProcessors()

    业务分为cpu密集型还是IO密集型,根据实际业务来配置

    1.CPU密集型

    CPU密集的意思是该任务需要大量的运算,而没有阻塞,CPU一直全速运行。

    CPU密集任务只有在真正的多核CPU上才可能得到加速(通过多线程),而在单核CPU上,无论你开几个模拟的多线程,该任务都不可能得到加速,因为CPU总的运算能力就那些。

    CPU密集型任务配置尽可能少的线程数量:

    一般公式:CPU核数+1个线程的线程池

    2.IO密集型

    1) 由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如CPU核数*2

    2) IO密集型,即该任务需要大量的IO,即大量的阻塞。在单线程上运行IO密集型的任务会导致浪费大量的CPU运算能力浪费在等待。所以在IO密集型任务中使用多线程可以大大的加速程序运行,即时在单核CPU上,这种加速主要就是利用了被浪费掉的阻塞时间。

    IO密集型时,大部分线程都阻塞,故需要多配置线程数:

    参考公式:CPU核数/1-阻塞系数(阻塞系数在0.8~0.9之间),比如8核CPU:8/1-0.9=90个线程数

     

  • 相关阅读:
    Java-IO之DeflaterOutputStream和InflaterOutputStream
    myeclipse 破解步骤
    通过Nginx,Tomcat访问日志(access log)记录请求耗时
    关于封装了gevent的request grequest库的使用与讨论
    关于python性能相关测试cProfile库
    python threading模块使用 以及python多线程操作的实践(使用Queue队列模块)
    python格式化字符串Type Error: Format Requires Mapping 的问题
    关于flask自带web应用服务器Werkzeug 使用requests请求时出现的错误。
    关于python 自带csv库的使用心得 附带操作实例以及excel下乱码的解决
    pycharm5.0 快捷键大全osx
  • 原文地址:https://www.cnblogs.com/mabaoying/p/13426675.html
Copyright © 2011-2022 走看看