线程池

来源:互联网 发布:服务器基础知乎 编辑:程序博客网 时间:2024/06/05 21:18

1.什么情况使用线程池

通常需要执行多个任务,每个任务的执行时间比较短。例如:listView,GridView,RecyclerView等,每个子条目进入到屏幕时会执行一个任务为子条目进行赋值,当进行滑动同时有多个子条目进入屏幕,每个子条目的具体内容需要网络请求,这时就需要使用线程池进行请求。

2.为什么使用线程池

new Thread(new Runnable(){   public void run(){      //do.sth   }}).start();

通常使用上述方法创建线程,它在任务结束后,GC会自动回收该线程,因此适用于并发不多的程序中;而如果应用程序需要开启大量的线程处理任务,上述方式将产生如下影响:

1.线程的创建和销毁都需要时间,当有大量的线程创建和销毁时,那么这些时间消耗将比较明显,导致性能缺失2.大量的线程创建、执行、销毁是非常消耗CPU和内存的,这样将影响系统的吞吐量,导致性能极具下降,如果内存资源占用比较多,很可能会造成OOM3.大量的线程创建和销毁容易导致GC频繁执行,容易导致页面卡顿

针对上述问题,我们可以重用已有的线程,从而减少线程的创建,控制线程的数量。

线程池作用就是限制系统中执行线程的数量。根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;用线程池控制线程数量,其他线程排队等候。一个任务执行完毕,再从队列中取最前面的任务开始执行。若队列中没有等待线程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了;否则进入等待队列。线程池的优点可以概括为:

1.重用线程池中的线程,避免因为线程的创建和销毁所带来的性能开销2.能有效控制线程池的最大并发数,避免大量的线程之间因相互抢占系统资源而导致的阻塞现象3.能够对线程进行简单的管理,并提供定时执行以及指定间隔循环执行的功能

ThreadPoolExecutor

ThreadPoolExecutor是线程池的真正实现,它的构造函数提供了一系列参数配置线程池

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)
corePoolSize   线程池的核心线程数,默认情况下,核心线程在线程池中一直存活,即使他们处于闲置状态,直至线程池销毁;而如果配置了allowCoreThreadTimeOut属性为true的话,那么空闲的核心线程也会被销毁了,当它空闲的时间超出了keepAliveTime这个参数规定的时间之后,它就会被销毁掉。 threadPoolExecutor.allowCoreThreadTimeOut(true)值得注意的是,并不是说只有核心线程才能去执行任务,而是核心线程是最稳定的线程,在默认状态下,它们不会销毁,这样在新的任务需要执行的时候,就会很节省时间,所以核心线程数只需要保证大于0就可以了。maximumPoolSize    线程池所能容纳的最大线程数,当活动线程数达到此数值后,后续的新任务会被阻塞keepAliveTime    非核心线程闲置时的超时时长,超过这个时长,非核心线程就会被回收。当ThreadPoolExecuor的allowCoreThreadTimeOut属性为true时,keepAliveTime同样会作用于核心线程。unit    用于指定keepAliveTime参数的时间单位,这是一个枚举,常用的有TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分钟)workQueue    线程池中的任务队列,通过线程池的execute方法提交的Runable对象会储存在这个参数中。threadFactory   线程工厂,为线程池提供创建新线程的功能。ThreadFactory是一个接口,它只有一个方法Thread newThread(Runable r)。RejectedExecutionHandler   当线程池无法执行新任务时,可能由于队列已满或者无法执行任务成功,这个时候ThreadPoolExector会调用handler的rejectedExecution方法来通知调用者,默认情况下rejectedExection方法会抛出一个RejectedExecutionException.

ThreadPoolExectutor 执行任务时大致遵循以下原则:

1.如果线程池中的线程数量未达到核心线程的数量,那么会直接启动一个核心线程来执行任务2.如果线程池中的线程数量已经达到或者超过核心线程数量,那么任务会被插入到任务队列中排队等待执行3.如果步骤2中无法将任务插入到任务队列中,往往是由于队列已满,这时如果线程数量未达到线程池规定的最大值,那么会立即启动一个非核心线程来执行任务4.如果步骤3中线程数量已经达到线程池规定的最大值,那么就拒绝执行此任务,ThreadPoolExecutor会调用RejectedExecutionHandler的rejectedExecution方法通知调用者

线程池分类
FixThreadPool

public static ExecutorService newFixedThreadPool(int nThreads){    return new ThreadPoolExecutor(nThreads,nThreads,0L,TimeUnit.MILLISECOND    S,new LinkedBlockingQueue<Runable>());}

通过它的构造方法可以看出,它是一种线程数量固定的线程池,它的核心线程和最大线程是相等的,即该线程池中的所有线程都是核心线程,当所有的线程都处于活动状态时,新任务都会处于等待状态,直到有线程空闲下来。由于FixedThreadPool只有核心线程并且这些核心线程不会被回收,这意味着它能更加快速的响应外界需求,它没有超时机制,任务队列是无边界的任务队列,也就是可以添加无上限的任务,但是都会排队执行。

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);   for(int i = 0 ;i<10;i++){      final int index = i;      fixedThreadPool.execute(new Runnable() {          @Override          public void run() {             String threadNmae = Thread.currentThread().getName();             Log.v("zxy", "线程:"+threadNmae+",正在执行第"+index+"个任务");             try {                  Thread.sleep(2000);             } catch (InterruptedException e) {                  e.printStackTrace();             }          }      });   }

这里写图片描述
可以看到一开始就会执行3个任务,而后面的7个任务都会进入等待状态,当有核心线程空闲时,就会从队列中按照FIFO(先进先出)的策略取出一个任务进行执行,所以除了前三个任务,剩下的任务是按照顺序执行的。

CachedThreadPool

public static ExecutorService newCachedThreadPool(){   return new ThreadPoolExecutor(0,Integer.MAX_VALUE,60L,TimeUnit.SECONDS,n   ew SychronousQueue<Runnable>());}

通过它的实例方法可以看出它的核心线程数是0也就是说该线程池并没有核心线程,而它的最大线程数是int类型的的上限,那么我们可以理解为该线程池的最大线程数是没有上限的,也就是说可以无限的创建线程。那么当新任务向线程池中提交的时候,如果有空闲线程,就会把任务放到空闲线程中去,如果没有空闲线程,就会开启一个新的线程来执行此任务,线程池中的空闲线程有超时机制,这个超时时常是60秒,超过60秒闲置的线程就会被回收,此类线程池比较适合执行大量的耗时比较少的任务,当整个线程池都处于空闲状态,线程池中的线程都会超时而被停止,此时CachedThreadPool之中是没有任何线程的,它几乎不占用系统资源。

ExecutorService cacheThreadPool = Executors.newCachedThreadPool();for(int i = 0;i<10;i++){   final int index = i;   try {      Thread.sleep(1000);   } catch (InterruptedException e) {      e.printStackTrace();   }   cacheThreadPool.execute(new Runnable() {       @Override       public void run() {          String threadName = Thread.currentThread().getName();          Log.v("TAG", "线程:" + threadName + ",正在执行第"+index + "个任务");          long time = index*500;          try {              Thread.sleep(time);          } catch (InterruptedException e) {              e.printStackTrace();          }       }    }); }

这里写图片描述

ScheduledThreadPool

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize){    return new ScheduledThreadPoolExecutor(corePoolSize);}public ScheduledThreadPoolExecutor(int corePoolSize){    super(corePoolSize,Integer.MAX_VALUE,0,NANOSECONDS,new DelayedWorkQueue    ());}

它的核心线程是固定的,而非核心线程没有限制,并且当非核心线程闲置时会被立即回收,ScheduledThreadPool这类线程主要用于执行定时任务和具有固定周期的重复任务,newScheduledThreadPool方法实现:

 scheduledThreadPool = Executors.newScheduledThreadPool(3);     scheduledThreadPool.scheduleAtFixedRate(new Runnable() {        @Override        public void run() {           System.out.println("执行任务");        }     },4,1, TimeUnit.SECONDS);

这里写图片描述
我们设置了在提交任务时,需要延迟4s才会第一次执行,同时在任务执行完毕后每隔1s又会重复的执行一次该任务

SingleThreadExecutor

public static ExecutorService newSingleThreadExecutor(){   return new FinalzableDelegatedExecutorService(new ThreadPoolExecutor(1,1   ,0L,TimeUnit.MILLISECOUNDS,new LinkedBlockingQueue<Runable>()));}

此线程池的核心线程和最大线程都是固定的且均为1,每次只能执行一个任务,多余的任务保存到一个队列中,等到执行完成后,再按照顺序依次执行队列中的任务。

ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();    for(int i=0;i<10;i++){        final int index = i;        singleThreadPool.execute(new Runnable() {            @Override            public void run() {               string threadName = Thread.currentThread().getName();               Log.v("TAG", "线程:" + threadName + ",正在执行第" + index + "              个任务");            }        });}

这里写图片描述

仔细观察可以发现上面不同的线程池,其实是有不同的队列,决定不同的线程池执行不同的功能是由不同的队列决定的,它是一个实现了BlockingQueue<Runable>对象,泛型设置是只可以存放Runable对象,在这个接口里规定了加入或取出的方法,其中常见的有

LinkedBlockingQueue:无界的队列-----FixedThreadPool,SingleThreadExecutorSynchronousQueue:直接提交的队列-----CachedThreadPoolDelayedWorkQueue:等待队列-----ScheduledThreadPoolPriorityBlockingQueue:优先级队列ArrayBlockingQueue:有界的队列

无界队列:使用无界队列(例如 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙的情况下将新任务加入队列。这样,创建的线程就不会超过 corePoolSize,(因此,maximumPoolSize 的值也就无效了。),此队列按照FIFO(先进先出)策略,排列元素,当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列。

有界队列:当使用有限的 maximumPoolSizes 时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O 边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量,此队列也按照FIFO(先进先出)策略,排列元素。

直接提交:这个队列会把任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。简单说来,这种队列就是没什么用,走个过场而已,所以使用这个队列的时候,线程池的最大线程数一般是无上限的,也就是int类型的最大值

优先级队列:这种队列在向线程池中提交任务的时候会检测每一个任务的优先级,会先把优先级高的任务扔到线程池中

等待队列:DelayQueue(基于PriorityQueue来实现的)是一个存放Delayed 元素的无界阻塞队列,只有在延迟期满时才能从中提取元素。该队列的头部是延迟期满后保存时间最长的 Delayed 元素。如果延迟都还没有期满,则队列没有头部,并且poll将返回null。当一个元素的 getDelay(TimeUnit.NANOSECONDS) 方法返回一个小于或等于零的值时,则出现期满,poll就以移除这个元素了。此队列不允许使用 null 元素。

自定义线程池:

自定义线程池主要取决于使用不同的BlockingQueue,以下是使用优先级队列的自定义线程池,首先我们写个抽象类:

public abstract class  PriorityRunnable implements Runnable,Comparable<PriorityRunnable> {    private int priority;    public PriorityRunnable(int priority){        if(priority<0){            throw new IllegalArgumentException();        }        this.priority = priority;    }    @Override    public int compareTo(@NonNull PriorityRunnable another) {        int num = another.getPriority()-priority;        return num;    }    private int getPriority(){        return priority;    }}

首先创建一个实现Runnable接口的类,并实现Comparable接口,实现这个接口主要就是进行优先级的比较,来告诉队列到底谁大谁小,这里我们自己写了一个int类型的变量代表每一个任务的优先级,然后复写compareTo方法来写我们的比较条件,这里要注意的是,当我们自己去写的时候,不一定非要指定一个int类型的变量,也可以是其他的例如String等的,只要实现了Comparable接口就可以了。接下来我们就可以使用这个任务了,代码如下

ExecutorService priorityThreadPool = new ThreadPoolExecutor(3,3,0L,TimeUnit.SECONDS,new PriorityBlockingQueue<Runnable>());for(int i = 1;i<=10;i++){    final int priority = i;    priorityThreadPool.execute(new PriorityRunnable(priority) {        @Override        public void run() {            String threadName = Thread.currentThread().getName();            Log.v("TAG", "线程:" + threadName + ",正在执行优先级为:" + priori            ty + "的任务");            try {               Thread.sleep(2000);            } catch (InterruptedException e) {               e.printStackTrace();            }        }    });}

这里写图片描述
我们这里创建的是核心线程为3的线程池,因此前三个任务就直接在线程池中运行,后面7个任务加入到优先级队列中排队,通过按照设置的比较方式进行比较,优先级高的会先执行任务。
优点:创建一个优先级线程池非常有用,它可以在线程池中线程数量不足或系统资源紧张时,优先处理我们想要先处理的任务,而优先级低的则放到后面再处理,这极大改善了系统默认线程池以FIFO方式处理任务的不灵活。

线程池的其他方法:

beforeExecute()--------任务执行前执行的方法afterExecute() ---------任务执行结束后执行的方法terminated() :线程池关闭后执行的方法
public class MyThreadPoolExecutor extends ThreadPoolExecutor {    public MyThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long     keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);    }    @Override    protected void beforeExecute(Thread t, Runnable r) {        super.beforeExecute(t, r);        String threadName = t.getName();        Log.v("TAG", "线程:" + threadName + "准备执行任务!");    }    @Override    protected void afterExecute(Runnable r, Throwable t) {        super.afterExecute(r, t);        String threadName = Thread.currentThread().getName();        Log.v("TAG", "线程:" + threadName + "任务执行结束!");    }    @Override    protected void terminated() {        super.terminated();        Log.v("TAG", "线程池结束!");    }}

这里写图片描述

在使用线程池时,任务的提交方式是由于队列决定的,但是任务队列里的方法比较多,复写起来比较麻烦,而且官方也建议我们使用它提供给我们的几种线程池,有时我们的队列需要动态的调整FIFO和LIFO的策略,或者我们提交的策略非常复杂,系统默认的满足不了我们怎么办,通常我们的做法是自己完全重新写一个任务队列,并不实现线程池的接口,也不作为任务队列参数放到线程池中,而我们所有的任务都会先提交到我们自己定义的这个队列中来,然后当线程池空闲的时候再提交到线程池中去。

public class MyThreadPool extends ThreadPoolExecutor {    private volatile Semaphore semaphore;    private List runnableList;    private boolean flag;    private LoopThread loopThread;    private OutWay outWay;    public MyThreadPool(int corePoolSize) {        super(corePoolSize,corePoolSize,0L,TimeUnit.SECONDS,new LinkedBlock        ingQueue<Runnable>());        semaphore = new Semaphore(corePoolSize);        runnableList = new LinkedList();        flag = true;        //默认先进后出        outWay = OutWay.FIFO;        loopThread = new LoopThread();        loopThread.start();    }    enum OutWay{        FIFO,LIFO;    }    @Override    public synchronized void execute(Runnable command) {        runnableList.add(command);        if(runnableList.size()<2){            //如果这是队列中的第一个任务,那么就去唤醒轮询线程            synchronized (loopThread){                loopThread.notifyAll();            }        }    }    //设置是FIFO/LIFO    public void setWay(OutWay outWay){       this.outWay = outWay;    }    @Override    protected void afterExecute(Runnable r, Throwable t) {        super.afterExecute(r, t);        //释放信号量        semaphore.release();    }    @Override    protected void terminated() {        super.terminated();        //关闭线程        flag = false;    }    class LoopThread extends Thread{        @Override        public void run() {            super.run();            while (flag){                if(runnableList.size() == 0){                    try {                        //如果没有任务,线程就等待                        synchronized (this){                            wait();                        }                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }else {                    try {                        //请求信号量                        semaphore.acquire();                        int index = runnableList.size();                        switch (outWay){                            case FIFO:                                //先进先出                                index = 0;                                break;                            case LIFO:                                //先进后出                                index = runnableList.size()-1;                                break;                        }                        //调用父类的添加方法,将任务添加到线程池中                        MyThreadPool.super.execute((Runnable) runnableList.                        get(index));                        runnableList.remove(index);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }            }        }    }}
        handler = new Handler(new Handler.Callback() {            @Override            public boolean handleMessage(Message msg) {                mainTv.setText("任务:"+msg.what);                return false;            }        });        myThreadPool = new MyThreadPool(1);        for(int i = 0; i<100;i++){            final int index = i;            myThreadPool.execute(new Runnable() {                @Override                public void run() {                    handler.sendEmptyMessage(index);                    try {                        Thread.sleep(100);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }            });        }        FIFO.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                myThreadPool.setWay(MyThreadPool.OutWay.FIFO);            }        });        LIFO.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                myThreadPool.setWay(MyThreadPool.OutWay.LIFO);            }        });
原创粉丝点击