Java拾遗(ThreadPool)

本文详细探讨了进程和线程的调度机制,包括状态转换、调度算法及上下文切换等内容。对比了Windows与Linux下的线程调度差异,并深入剖析了Java中的线程实现及其与任务的交互方式。

a.进程调度 VS 线程调度

  • 进程调度

    • 三种状态:等待态,就绪态,运行态
    • 方式:非剥夺式 剥夺式
    • 进程上下文:正文段 + 数据段 + 硬件寄存器 + 相关数据结构(如PCB等)
    • 算法:先进先出 短进程优先 时间片轮转 多级反馈队列
  • 线程调度

    • 六种状态:new、runnable (ready->running)、timed waiting (sleep or wait)、waiting (wait)、blocked(synchronized)、terminated
    • 方式:分时调度、抢占式调度(jvm采用)
    • 线程上下文:包括线程调用栈,程序计数器等.  jvm控制,运行完了自动销毁.
    • java的线程调度不是跨平台的,不仅取决于jvm,还依赖于os(某些os的机制是只要运行的线程没有阻塞就不会放弃cpu,某些os是即使没有阻塞线程运行一段时间后也会放弃cpu)

b.Windows VS Linux

  • Windows

    • windows是以线程为最低粒度调度,而非进程
    • windows线程优先级32级
  • Linux

    • linux是以进程(线程)为最低粒度调度,在linux里,线程是轻量级进程(线程在内核态是以进程方式创建,通过设置共享内存、文件描述符等为相同的方式完成线程的创建)
    • linux线程优先级100级

c.线程 VS 任务

  • Thread

    • thread对象:堆内对象,若置为null,GC可自动清理
    • thread对象的start()方法会调用native的库创建一个真正的线程(与主线程并行的执行流),其run()方法只是在线程创建成功之后执行的逻辑代码
    • 线程/堆栈相关分析可参考https://my.oschina.net/igooglezm/blog/757587
  • Runnable

    •  runnable对象:堆内对象,若置为null,GC可自动清理        

d.线程池

    1.线程池:模型

  • 半同步/半异步模型:任务由任意线程产生,有一个专门的manager thread获取任务并分发给worker thread
  • 领导者/跟随者模型:有一个worker线程是leader thread,其他的worker线程是follower thread. 由leader thread去侦听任务队列,当有新任务加入到任务队列时,leader thread去获取和执行,并从follower threads中选择一个线程做为新的leader thread去侦听任务队列

    2.线程池:普通任务调度ThreadPoolExecutor

  • java.util.concurrent.ThreadPoolExecutor

  • 线程和任务:

    • 调度线程(该线程不是线程池创建的线程,而是调用线程池对象提交任务的线程)
    • 工作线程(worker thread,执行任务的线程)
    • 任务 Runnable/Callable
  • (调度线程)线程池提交任务流程:即ThreadPoolExecutor.execute(runnable),参考ThreadPoolExecutor的Line 1332

    • 按需创建worker线程
      • 若worker线程少于corePoolSize,新建woker线程执行此task
      • 若worker线程等于corePoolSize,此task进入等待队列workQueue
      • 若workQueue队列满了,新建worker线程执行此task
      • 若worker线程少于maximumPoolSize,则依据rejectExecutorHandler处理此task
  • (工作线程)获取并执行任务流程:即Worker.run(), 实际为ThreadPoolExecutor.runWorker(worker),参考ThreadPoolExecutor的Line 1120

    • 无限循环从任务队列获取任务runnable,并执行runnable.run()(此处均为非周期任务)

  • 线程池worker线程重用原理:

    • 调度线程在需要的时候调用线程池execute()方法去创建worker线程执行任务(即start方法不暴露给用户调用,而是由线程池调度线程在需要时调用);
    • worker线程被创建好后,worker线程自己的run()方法不断循环检查是否有新的runnable,有则调用该runnable的run()方法. 即worker线程的run方法把runnable任务的run方法一个个串联起来:对于任务来说,线程被重用了
    • 如果没有新的runnable,则可能销毁这个worker线程
  • 线程池worker线程泄漏

    • 如果线程池里的worker线程出现异常但处理不当,可能导致worker线程无法运行其他任务,即线程池好像少了一个worker线程(注意worker线程只有执行完了同周期上一次的任务后,才会添加新的同周期任务去等待队列,若worker线程被阻塞或者出现异常,则此线程可能之后都不可用)
  • 线程池对象主要控制字段

    • 线程池mainLock 和termination:调度线程和worker线程之间同步和通信
    • 线程池threadFactory:主要为线程池里的worker线程命名,分组,设定优先级等
    • 线程池workers: 即active worker threads, 当前线程池里的worker线程引用
    • 线程池corePoolSize:即max active worker thread size,当前线程池里已有worker线程少于corePoolSize,并且worker线程不是空闲状态(如果worker线程处于空闲状态,便复用已有worker线程).  只要有新任务,便新建worker线程运行该新任务 (即此时线程池大小为n,n<=corePoolSize)
    • 线程池workQueue:即waiting runnables,当前线程池已有worker线程等于corePoolSize,有新任务将被加入到等待任务队列workQueue (即此时线程池大小为n,n=corePoolSize)
    • 线程池maximumPoolSize:当前线程池已有worker线程等于corePoolSize,等待任务队列workQueue已满,当前已有线程数少于maximumPoolSize,则新建worker线程运行任务(即此时线程池大小为n,corePoolSize<n<=maximumPoolSize,设置maximumPoolSize,是作为补救措施,避免workQueue过度占用内存)
    • 线程池RejectExecutorHandler handler:以上三者皆满,对于新任务的处理策略
      • abort      抛出异常(默认)
      • discard  丢弃新任务
      • discardoldest 丢弃老任务
      • callruns  重新调度该任务
    • 线程池keepAliveTime:无任务调度时,worker线程存活时间,默认是0(CachedThreadPool默认是60s),过期则销毁该worker线程
    • 线程池allowCoreThreadTimeOut:是否允许worker线程在空闲时被销毁
    • 线程池的任务状态:RUNNING--1,SHUTDOWN-0,STOP-1,TIDING-2,TERMINATED-3

    3.线程池:延迟或者周期性任务调度ScheduledThreadPoolExecutor

  • java.util.concurrent.ScheduledThreadPoolExecutor

  • 与ThreadPoolExecutor线程池不同

    • 等待任务队列workQueue:DelayedWorkQueue,基于时间排序

    • 因为基于时间延迟,不会由于系统时间改变发生执行变化(与Timer不同点之一)

    • DelayedWorkQueue是无界队列(优先级队列的一种,基于时间优先级,实际上是一个按照执行时延从短到长排序的堆)所以可以缓存无数任务,maximumPoolSize无效,即ScheduledThreadPoolExecutor相当于一个固定线程大小(corePoolSize)的线程池

    • 任务runnable被封装为FutureScheduledTask(内含预期执行时间点)

  • 比ThreadPoolExecutor线程池主要多了线程池任务序列计数器sequencer:AtomicLong,为每次将要执行的任务计数,并防止同一个任务被多个线程执行
  • (调度线程)线程池提交任务流程:即ScheduledThreadPoolExecutor.schedule(runnable),参考ScheduledThreadPoolExecutor的Line 558 and Line 582
    • 封装任务runnable为futureScheduledTask,加入等待任务队列
    • 按需创建worker线程
  • (工作线程)获取并执行任务流程:worker.run(),实际为ScheduledThreadPoolExecutor.runWorker(worker)
    • 无限循环从任务队列获取任务futureScheduledTask,并执行futureScheduledTask.run()
    • (注意与ThreadPoolExecutor的不同,ThreadPoolExecutor中此处调用runnable.run())
    • (任务futureScheduledTask.run()会先执行runnable的run(),再重新计算下一次执行时间点,然后将新任务加入到等待任务队列workQueue里,见代码FutureScheduledTask的run()  Line 288)
    • 参见ThreadPoolExecutor的Line 1120
    • final void runWorker(Worker w) {
              Thread wt = Thread.currentThread();
              Runnable task = w.firstTask;
              w.firstTask = null;
              w.unlock(); // allow interrupts
              boolean completedAbruptly = true;
              try {
                  while (task != null || (task = getTask()) != null) {  //无限取任务
                      w.lock();
                      // If pool is stopping, ensure thread is interrupted;
                      // if not, ensure thread is not interrupted.  This
                      // requires a recheck in second case to deal with
                      // shutdownNow race while clearing interrupt
                      if ((runStateAtLeast(ctl.get(), STOP) ||
                           (Thread.interrupted() &&
                            runStateAtLeast(ctl.get(), STOP))) &&
                          !wt.isInterrupted())
                          wt.interrupt();
                      try {
                          beforeExecute(wt, task);
                          Throwable thrown = null;
                          try {
                              task.run();                              //执行任务FutureScheduledTask.run()
                          } catch (RuntimeException x) {
                              thrown = x; throw x;
                          } catch (Error x) {
                              thrown = x; throw x;
                          } catch (Throwable x) {
                              thrown = x; throw new Error(x);
                          } finally {
                              afterExecute(task, thrown);
                          }
                      } finally {
                          task = null;
                          w.completedTasks++;
                          w.unlock();
                      }
                  }
                  completedAbruptly = false;
              } finally {
                  processWorkerExit(w, completedAbruptly);             //处理worker线程
              }
          }
      参见FutureScheduledTask.run() Line 288
    •  public void run() {
                  boolean periodic = isPeriodic();          
                  if (!canRunInCurrentRunState(periodic))
                      cancel(false);
                  else if (!periodic)
                      ScheduledFutureTask.super.run();                 
                  else if (ScheduledFutureTask.super.runAndReset()) {  //运行任务主体
                      setNextRunTime();                                //计算下一次执行时间点
                      reExecutePeriodic(outerTask);                    //添加任务至等待队列
                  }
              }
  • 采用leader/follower模型
    • 在等待任务时,worker线程中有一个线程成为leader thread,其他的线程为follower threads,只有leader threads会去拿到下一个delay任务,其他follower threads 无限wait();当leader thread获取到任务时,必须singal()其他follower threads
    • leader threads拿到任务后通过等到规定的时间点再执行任务
  • 三种任务(一种非周期任务,两种周期任务)
    • 普通非周期任务,只计算一次执行时间点并执行一次
    • FixedRate周期任务,不需要当前任务执行完成的时间点,可以直接计算下次甚至每次开始执行的时间点,但也是在任务执行完后再计算下一次开始执行的时间点;
    • FixedDelay周期任务,在当前任务执行完后(需要当前任务执行完成的时间点)再计算下一次开始执行的时间点;
  • 等待队列里同周期任务永远只有一个副本
    • 每次成功执行完一个任务后,会计算同序列任务下次的执行时间点,创建一个新任务加入到等待队列里(即任务只有执行后才会添加新的该周期任务至等待队列
  • 基于以上结论,只要不提交新任务,队列最大长度是一个定数,是不会无限增长的
  • 等待任务对列头总是最近将要执行的任务
    • 线程池里的leader线程在向等待队列添加任务时,会计算该任务下次的执行时间,添加到队列的合适位置
  • (任务定时执行原理)线程池里的leader线程从等待队列里取到任务后
    • 如果时间点没到,便会计算现在到对应时间点需要等待的时间段t,调用condition.wait(waited_time),然后再执行该任务(准时执行)
    • 如果时间点已过,则立即执行该任务,并通过condition.singal()通知其他worker线程去检测等待任务(延时执行)
  • 同一个任务不会被多线程同时执行
    • 线程池有个序列计数器sequencer(原子类型,只能由一个线程操作,即只允许一个线程获取到该任务)任务分配给一个线程后会将任务从阻塞队列中移除,执行完毕后才会添加下一个的任务,即任务队列里同一个序列的任务只有一个副本
  • 任务一旦遇到异常,如果处理不当,后续周期性任务都将无法运行.(该worker线程可能被阻塞,以至于后续方法都无法执行,包括计算该任务的下一次执行时间点和添加该任务进入等待队列)

    4.线程池接口ExecutorService

  • ThreadPoolExecutor/ScheduledThreadPoolExecutor
  • Executors
    • newSingleThreadExecutor()
    • newFixedThreadPool()
    • newCachedThreadPool()
    • newScheduledThreadPool()
    • newSingleThreadScheduledExecutor()
  • ForkJoinExecutor/ForkJoinPool
    • JVM internal thread
    • work-stealing
    • like MapReduce,but MR is for ditributed system

e.其他并行或者并发模型

  • Disruptor

    • RingBuffer
  • Coninuation

    • 一种编程模型
    • 不使用后入先出的堆栈保存上下文
    • 一般使用树或者图保存调用上下文(由gc删除不用节点)
  • Coroutine(kilim)

    • Task
    • MailBox
  • Akka/ActorSysrem

    • Actor
    • MainBox

 

转载于:https://my.oschina.net/igooglezm/blog/755508

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值