java中的线程池初步了解

java学习之线程池

为什么要使用线程池?
在java中创建和销毁线程是非常损耗系统性能的,在开发过程中尽可能的少去创建线程,使用复用的方式达到性能优化的效果。线程的创建需要开辟虚拟机栈,本地方法栈,程序计数器这部分属于线程私有的内存空间,不同于方法区,堆这部分属于线程共享的内存空间。在线程销毁时,需要回收这部分的系统资源,频繁地创建销毁线程会浪费大量的系统资源,复用已有的线程可以更好地管理和协调线程的工作。

线程池主要解决两个问题:

一、当执行大量异步任务时线程池能提供更好的性能

二、线程池提供了一种资源限制和管理的手段,比如可以限制线程的个数,动态新增线程等

线程的五种状态

  1. 开始状态(new):线程刚创建时就是new状态

  2. 就绪状态(runnable):线程调用了start()方法后,进入runnable状态,此时并未真正执行,需要和其他线程竞争cpu资源。说到竞争cpu资源,有些代码里可能会看到Thread.sleep(0),或者Thread.sleep(50)一些简短的sleep操作,实际意思就是不让该线程长时间占用cpu,释放时间片,注意sleep不会释放锁。即抱着锁睡觉

  3. 运行状态(running):当该线程竞争到了cpu资源,则进入running状态

  4. 阻塞状态(blocked):线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态之间处于blocked状态

    (1)等待阻塞:运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中,
    进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒。

    (2)同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中。

    (3)其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。
    当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

    这个状态在Android应用出现anr时可能会经常看到,诸如构成死锁,binder远端阻塞时。

  5. 结束状态(dead):当线程正常执行结束会进入dead状态(一个未捕获的异常也会使线程终止)

注:

yield()只是使当前线程重新回到runnable状态
sleep()会让出cpu,不会释放锁,join()会让出cpu,释放锁

wait() 和 notify() 方法与suspend()和 resume()的区别在于wait会释放锁,suspend不会释放锁。wait() 和 notify()只能运行在Synchronized代码块中,因为wait()需要释放锁,如果不在同步代码块中,就无锁可以释放

当线程调用wait()方法后会进入等待队列(进入这个状态会释放所占有的所有资源,与阻塞状态不同),进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用 notify()或notifyAll()方法才能被唤醒

线程池体系

线程池体系.png
  • Executor是线程池最顶层的接口,在Executor中只有一个execute方法,用于指向任务。线程的创建、调度等细节由子类实现
  • ExecutorService继承并拓展了Executor,在ExecutorService内部提供了更全面的任务提交机制(submit)以及线程池的关闭方法(shutdown)
  • ThreadPoolExecutor是ExecutorService的默认实现,线程池的核心部分封装在此类中
  • ScheduledExecutorService继承自ExecutorService,增加了定时任务的方法schedule,scheduleAtFixedRate等
  • ScheduledThreadPoolExecutor继承自ThreadPoolExecutor并实现了ScheduledExecutorService接口。

创建线程池

JDK中提供了一个线程池的工厂类Executors,在Executors定义了多个静态方法,用来创建不同配置的线程池

newSingleThreadExecutor

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

创建一个单线程化的线程池,它只会有一个唯一的工作线程来执行任务,保证所有的任务按先见先出的顺序串行执行

newCachedThreadPool

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

创建一个可缓存的线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程,注意第二个参数maximumPoolSize为Integer.MAX_VALUE。

newFixedThreadPool

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

创建一个固定数目,可重用的线程池。所有的任务都会在固定的工作线程中执行

newScheduledThreadPool

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

创建一个定时线程池,支持定时及周期性任务执行。业务需要轮询任务时,且可以放在子线程中进行运算,推荐使用这种方式,相比Handler提供主线程消息队列,不会占用主线程资源,避免log出现The application may be doing too much work on its main thread.

ThreadPoolExecutor

构造方法参数解析

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}
  • corePoolSize:表示线程池的核心线程数量
  • maximumPoolSize:表示线程池最大能够容纳同时执行的线程数,必须大于等于1,如果和corePoolSize相等就是固定大小线程池
  • keepAliveTime:表示线程池中线程的空闲时间,当空闲时间达到此值时,线程会被销毁直到剩下corePoolSize个线程
  • unit:表示keepAliveTime的时间单位,主要有毫秒,秒,分钟,小时单位
  • workQueue:等待队列,BlockingQueue类型。当请求的任务数大于corePoolSize时,任务会被缓存在此BlockingQueue队列中
  • threadFactory:线程工厂,线程池中的线程使用它来创建线程,如果传入null,则使用默认工厂类DefaultThreadFactory。通常我们自定义线程工厂可以指定线程的优先级setPriority
  • handler:执行拒绝策略的对象,当workQueue满了之后并且执行的任务数大于maximumPoolSize时,线程池通过该策略处理请求

注意:当ThreadPoolExecutor的allowCoreThreadTimeOut设置为true时,核心线程超时也会被销毁。

线程池流程解析

当我们调用execute或者submit,把一个任务提交给线程池,线程池收到这个任务请求后,有以下几种处理情况:

  1. 当线程池中运行的线程数量还没有达到corePoolSize大小时,线程池会创建一个新的线程执行提交的任务,无论之前创建的线程是否处于空闲状态。
  2. 当前线程池中运行的线程数量已经达到corePoolSize大小时,线程池会把任务添加到等待队列中,直到某一个核心线程空闲了,线程池会根据设置的等待队列规则,从队列中取出一个新的任务执行
  3. 如果线程数大于corePoolSize数量,但是还没有达到maximumPoolSize,并且等待队列已满,则线程池会创建新的线程来执行任务
  4. 最后如果提交的任务,无法被核心线程直接执行,又无法加入等待队列,又无法创建非核心线程直接执行,线程池会根据定义的拒绝策略来处理这个任务

线程池的四种拒绝策略

当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize时,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:

  • ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 这是线程池的默认拒绝策略,在任务不能被提交的时候抛出异常,及时反馈程序运行状态
  • ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。 使用此策略我们无法感知系统运行的异常状态,不建议使用此种策略
  • ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务
  • ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务

线程池的五种状态

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
  1. RUNNING
    • 状态说明:线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。
    • 状态切换:线程池的初始化状态是RUNNING。换句话说,线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0!
  2. SHUTDOWN
    • 状态说明:线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。
    • 状态切换:调用线程池的shutdown()接口时,线程池由RUNNING -> SHUTDOWN
  3. STOP
    • 状态说明:线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。
    • 状态切换:调用线程池的shutdownNow()接口时,线程池由(RUNNING or SHUTDOWN ) -> STOP。
  4. TIDYING
    • 状态说明:当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。
    • 状态切换:当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。
      当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。
  5. TERMINATED
    • 状态说明:线程池彻底终止,就变成TERMINATED状态。
    • 状态切换:线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269