线程池

线程池:避免了创建线程和销毁线程的资源损耗。

Executors提供四种线程池:

  • newCachedThreadPool :缓存线程池,如果线程池长度超过处理需要,可回收空闲线程,若无可回收,则新建线程。
  • newFixedThreadPool : 定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
  • newScheduledThreadPool : 计划线程池,支持定时及周期性任务执行。
  • newSingleThreadExecutor :单线程线程池,用唯一的线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

先了解下线程池必备的以下几个属性:

poolSize : 当前程序运行的线程数。

coreSize: 核心线程数。

MaximumPoolSize: 线程池中最大线程数。

keepAliveTime:线程空闲时间超过,则会超时退出。

BlockingQueue :设置一个阻塞用来存放将要执行的等待任务。

(线程安全:保证取队列元素时一个只能取一个,且头元素没有时不能取,满时不能加入)

线程池的运行流程:

当需要执行一个任务时:

poolsize < coresize ->开启一个新的线程执行

poolsize = coresize && 队列未满 -> 这时已经达到了核心线程数,线程不够了,加入到阻塞队列中,慢慢执行。

Poolsize < MaximumPoolSize && 队列已满 -> 这时队列都满了,那不行啊 ,快爆了,在开启一个新线程吧。

(通常超出核心线程的线程是“借”的,也就是说超出核心线程的情况算是一种能够预见的异常情况,并且这种情况并不常常发生(如果常常发生,那我想你应该调整你的核心线程数了)

Poolsize = MaximumPoolSize && 队列满了 -> 已经所有办法用尽了,会根据饱和策略RejectedExecutionHandler拒绝新的任务。

(如果设置了keepAliveTime,可以通过以下配置来确定小于corePoolSize时,是否启动超时处理。

但是对于超出MaximumPoolSize,一定会进行超时处理,因为这些线程本就不是正常情况。

allowCoreThreadTimeOut:是否允许核心线程超时退出。

如果该值为false(默认),且poolSize<=corePoolSize,线程池都会保证这些核心线程处于存活状态,不会超时退出。(所以只要不是超过corePoolSize,线程都会空闲而不是销毁)

如果为true,则不论poolSize的大小,都允许超时退出。

如果poolSize>corePoolSize,则该参数不论true还是false,都允许超时退出。

)

(所以大概流程:执行一个任务,如果线程少于coresize则启动新线程,如果等于了coresize,则加入队列等待。如果队列满了,实再不行了,则在开启新线程。如果线程等于最大线程了,执行饱和策略拒绝任务)

自定义线程:可以发现,其实就是我们说的那几个参数,特别简单。
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) 
                          
/**
*coresieze和maximumPool相同,所以这也是为什么他是固定大小。
并且阻塞LinkedBlockingQueue没有初始化大小,是个无界队列。所以进来的任务,线程不够就会都加入队列等待。
keepAliveTime:0,默认超过最大线程才会启用超时,意味着:一超过最大线程数,立即关闭。
*/
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
    
/**
* coresieze和maximumPool都为1
*/
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
    
/**
* 核心线程为0,最大线程数无限大。意味着,所有线程都有keepalivetime,都有一小段的生命周期。(缓存线程也就是说,执行的线程会缓存一段时间,然后到时间释放)在使用这类无限大的线程池时,非常容易内存消耗殆尽。这也是为什么阿里不建议使用Executors创建线程池。其实自定义线程池非常好用,而且自己看了就会非常明确。
SynchronousQueue(同步队列)一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作(即每个任务都必须有线程接管,否则不加入),否则插入操作一直处于阻塞状态
*/
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
    
/**
*定时线程使用的是ThreadPoolExecutor子类
*/
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
        return new DelegatedScheduledExecutorService
            (new ScheduledThreadPoolExecutor(1));
    }

使用executorService.execute(Runnable)或executorService.submit(Callable)

(线程是线程Thread,任务是任务Runnable。

创建一个Runnable,只是实现了run方法,这是任务,不是线程。他并不能执行,所以需要传入Thread中,然后执行。

启动线程的唯一方法就是通过Thread类的start()实例方法。

)

线程池拒绝策略:

当线程池满了,队列也满了,这时候需要执行拒绝策略。默认策略感觉不太好,如果一满就会报异常停止运行。

AbortPolicy(默认)

默认策略,直接跑出异常阻止系统正常运行

CallerRunsPolicy

“调用者运行”一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将任务回馈至调用方,比如main线程

DiscardOldestPolicy

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

DiscardPolicy

直接丢弃任务,不给予任何处理也不跑出异常,如果允许任务丢失

runable和callable

runable: 使用execute,无返回值

callable:使用submit,有返回值,返回的是future。

(注意如果在任务就获得值,future.get(),将会阻塞主线程,导致线程串行处理。一般的做法是在任务中将所有future用list收集起来,出来线程池的处理,在future.get())

微信公众号也会更新哦!!

posted @ 2020-09-19 21:25  badribbit123  阅读(145)  评论(0编辑  收藏  举报