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线程
-
(工作线程)获取并执行任务流程:即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
-
参见FutureScheduledTask.run() Line 288final 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线程 } }
-
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