java 线程池

       推荐 java多线程系列 ,一气呵成!

        

        使用线程的时候就去创建一个线程(new Thread),这样实现起来非常简便,但是就会有一个问题:如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。

        所以就有了Java中可以通过线程池来使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务。合理的使用线程池能够带来3个很明显的好处:

        1.降低资源消耗:通过重用已经创建的线程来降低线程创建和销毁的消耗
        2.
提高响应速度:任务到达时不需要等待线程创建就可以立即执行。

        3.提高线程的可管理性:线程池可以统一管理、分配、调优和监控。 


        了解线程池得先了解三个类:Executors ,ExecutorService与ThreadPoolExecutor。Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService而ThreadPoolExecutor是ExecutorService的具体实现类。其中Executors 相当于一个创建线程池的工具类。



一.Executors :创建线程池的工具类,在该类中提供了许多静态方法来创建一个线程池,该类中的重要方法如下

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

 public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

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

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

newFixedThreadPool(int nThreads) :通过传入的int类型整数创建一个固定大小的线程池,每次提交一个任务就创建一个线程,当线程达到线程池的最大大小后提交的线程会在队列中等待。

newSingleThreadExecutor() :创建一个单线程的线程池。

newCachedThreadPool() :创建一个可以缓存的线程池,当无线程空闲时创建一个新线程,但先前创建的线程空闲时重用先前创建的线程。

newScheduledThreadPool(int corePoolSize) :创建一个可以定时执行或周期性执行的线程池。



       

二.ThreadPoolExecutor

        java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类,因此如果要透彻地了解Java中的线程池,必须先了解这个类。

        在ThreadPoolExecutor类中提供了四个构造方法:

public class ThreadPoolExecutor extends AbstractExecutorService {
    .....
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
        BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
    ...
}

ThreadPoolExecutor重要参数:

  • volatile int runState;   //当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值,用来保证线程之间的可见性。
  • static final int RUNNING    = 0; //当创建线程池后,初始时,线程池处于RUNNING状态;
  • static final int SHUTDOWN   = 1; //线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕;
  • static final int STOP       = 2; //线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务;
  • static final int TERMINATED = 3; //处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态。
  • private final BlockingQueue<Runnable> workQueue;              //任务缓存队列,用来存放等待执行的任务
  • private final ReentrantLock mainLock = new ReentrantLock();   //线程池的主要状态锁,对线程池状态(比如线程池大小、runState等)的改变都要使用这个锁
  • private final HashSet<Worker> workers = new HashSet<Worker>();  //用来存放工作集
  • private volatile long  keepAliveTime;    //线程池空闲时,线程存活的时间   
  • private volatile boolean allowCoreThreadTimeOut;   //是否允许为核心线程设置存活时间
  • private volatile int   corePoolSize;     //核心池的大小(即线程池中的线程数目大于这个参数时,提交的任务会被放进任务缓存队列)
  • private volatile int   maximumPoolSize;   //线程池最大能容忍的线程数
  • private volatile int   poolSize;       //线程池中当前的线程数
  • private volatile RejectedExecutionHandler handler; //任务拒绝策略
  • private volatile ThreadFactory threadFactory;   //线程工厂,用来创建线程
  • private int largestPoolSize;   //用来记录线程池中曾经出现过的最大线程数 
  • private long completedTaskCount;   //用来记录已经执行完毕的任务个数


三.任务的执行


线程池的流程图

step1.调用ThreadPoolExecutor的execute提交线程,首先检查CorePool,如果CorePool内的线程小于CorePoolSize,新创建线程执行任务。
step2.如果当前CorePool内的线程大于等于CorePoolSize,那么将线程加入到BlockingQueue。
step3.如果不能加入BlockingQueue,在小于MaxPoolSize的情况下创建线程执行任务。

step4.如果线程数大于等于MaxPoolSize,那么执行拒绝策略。(转自点击打开链接 )



四.线程初始化

默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程。
在实际中如果需要线程池创建之后立即创建线程,可以通过以下两个方法办到:
        prestartCoreThread():初始化一个核心线程;
        prestartAllCoreThreads():初始化所有核心线程
下面是这2个方法的实现:
        public boolean prestartCoreThread() {
            return addIfUnderCorePoolSize(null); //注意传进去的参数是null
        }
        public int prestartAllCoreThreads() {
            int n = 0;
            while (addIfUnderCorePoolSize(null))//注意传进去的参数是null
                ++n;
            return n;
        }
注意上面传进去的参数是null,根据第2小节的分析可知如果传进去的参数为null,则最后执行线程会阻塞在getTask方法中的
        r = workQueue.take();

即等待任务队列中有任务。



五.任务缓存队列及排队策略

任务缓存队列,即workQueue,它用来存放等待执行的任务。
workQueue的类型为BlockingQueue<Runnable>,通常可以取下面三种类型:
        1)ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;
        2)LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;
        3)synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。


六.任务拒绝策略

当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,常有以下四种策略:
        ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
        ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
        ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
        ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务


七.线程池的关闭

ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:
        shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务。
        shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务。


八.线程池容量的动态调整

ThreadPoolExecutor提供了动态调整线程池容量大小的方法:setCorePoolSize()和setMaximumPoolSize(),
        setCorePoolSize:设置核心池大小
        setMaximumPoolSize:设置线程池最大能创建的线程数目大小
当上述参数从小变大时,ThreadPoolExecutor进行线程赋值,还可能立即创建新的线程来执行任务。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值