JAVA线程池介绍及主要原理实现

Tip:基于JDK1.8

一、线程

1:如何实现多线程:继承Thread类 ,实现Runnable接口,实现Callable<T>接口(通过FutureTask创建线程)。

2:线程的状态:

(1)初始状态(NEW):当线程实例化以后,就进入到初始状态

  (2) 运行状态(RUNNABLE):线程实例启动[调用线程实例start方法],进入到可运行状态。该状态线程随时准备被系统进行调度。可运行状态下的线程被操作系统选中,进入到运行状态,执行线程。【两种状态笼统称为运行状态】

(3)阻塞状态(BLOCKED):线程被阻塞,如等待锁。

(4)等待状态(WAITING):等待状态,表示该状态下需要其他线程做出一定动作唤醒【如通知或中断】

(5)等待状态(TIME_WAITING):超时等待状态,该状态下线程能自动苏醒。如Sleep(100)

(6)终止状态(TERMINATED):表示该线程执行完毕

实例:使用jstack观察线程状态

启动线程


线程sleep



线程wait


线程block


线程快照

分析:BlockThreadOne获取类锁,处于RUNNABLE状态

           BlockThreadTwo等待BlockThreadOne释放类锁,处于BLOCKED状态

           WaitingThread等待其他线程Notify,处于WAITING状态

           SleepThread一直sleep(100),处于TIMED_WAITTING状态

其他tips:①Daemon线程随虚拟机终止而终止,finally语句块不一定执行

              ②安全的终止线程可以为线程设置一个volatile变量作为 中断状态

二、线程池


1.ThreadPoolExecutor

  Java中想要定制线程池,只需要继承ThreadPoolExecutor类即可,在ThreadPoolExecutor的构造函数中有七个参数,具体描述如下:


(1).corePoolSize核心线程数:向线程池提交任务后,线程池会新建一个线程去执行任务。当当前线程池中的线程数达到核心线程数时,将不再创建线程,而是将任务放入阻塞队列【Q:当线程池中的线程还没达到核心线程数时,提交的任务是新启线程执行还是复用之前的核心线程?A:新启动线程】

(2).maximumPoolSize线程池中最大线程数:当线程池中的线程数达到核心线程数后,将不再创建线程,而是将任务放入阻塞队列。当阻塞队列满的时候,如果没有达到maximumPoolSize,将创建普通线程执行任务,直到核心线程与普通线程总数达到线程池中的最大线程数。

(3).keepAliveTime普通线程存活时间:当线程池中存在普通线程空闲时。将存活该时间后死亡【可以通过allowCoreThreadTimeout设置核心线程是否存活】。

(4).unit存活时间单位:枚举类型NANOSECONDS(纳秒),MICROSECONDS(微秒),MILLISECONDS(毫秒),SECONDS(秒),MINUTES(分),HOURS(时),DAYS(天)

(5).workQueue:阻塞队列:保存等待的任务。不同的阻塞队列效果不同,具体有如下阻塞队列  :

  ①SynchronousQueue 该阻塞队列不存储元素,也就是说当当前线程池线程数超过核心线程数且小于最大线程数时,将直接创建普通线程执行任务

  ②ArrayBlockingQueue该阻塞队列为基于数组FIFO的有界队列,在构造函数中需传入队列长度

  ③LinkedBlockingQueue该阻塞队列为基于链表的FIFO的无界队列【也可以传入队列长度使其有界】

  ④PriorityBlockingQueue该队列为基于任务优先级排序的无界队列

(6).threadFactory 线程工厂:可以定制创建线程的行为,如给线程命名等

(7).handler饱和策略:当线程池中的线程数超过最大线程数后,新添加的任务将通过规定饱和策略进行处理,JDK中有四种默认的饱和策略

  ①AbortPolicy: 终止策略,该策略抛出未检查的RejectedExecutionException

  ②CallerRunsPolicy调用者运行策略,将任务回退给调用者【场景:当线程池满了,任务回退给主线程,主线程处理任务需要时间,这段时间线程池可以处理正在执行的任务。如在TCP传输中,主线程执行任务不调用accept,请求缓存到TCP层队列,慢慢的TCP层队列填满,开始抛弃请求。这种情况可以使服务器性能平缓的降低】

  ③DiscardOldestPolicy丢弃阻塞队列中最旧的任务策略,并尝试重新提交任务【当阻塞队列为基于任务优先级队列时,将抛弃优先级最高的】

  ④DiscardPolicy丢弃策略,直接丢弃任务

2.Exectors工厂类

可以使用Exectors创建一些默认的线程池【本质也是通过传入不同参数构建线程池】,下面介绍下JDK中默认的线程池:


  ①CachedThreadPool:当线程池中线程不够时即创建线程,线程池中最大创建Integer.MAX_VALUE【JAVA中int 占4个字节,故值最高能达到2的31次方减一 ,最高位作为符号位】个,线程空闲60S即死亡【该线程池可能会创建大量线程导致性能问题】


  ②SingleThreadExecutor:线程池中只存在一个线程,当该线程在执行时则将任务放入无界阻塞队列【该线程池可能导致阻塞队列越来越长导致性能问题】


  ③ScheduledThreadPool:可以在指定时间周期性的执行任务,可以作为定期任务执行的线程池。

三、线程池原理 (ThreadPoolExecutor源码解析) 

  在ThreadPool中 用一个AtomicInteger变量ctl代表当前线程池的状态和线程池中线程数,其中ctl高三位代表线程池状态,低29位代表线程池中线程个数

    线程池状态:

      ① RUNNING:运行中,该状态下线程池可以接受新的任务,并处理阻塞队列中的任务;

      ② SHUTDOWN:停止状态,该状态下线程池不接受新的任务,但是可以处理阻塞队列中的任务;

      ③STOP:强行停止状态,该状态下线程池不接受新的任务,也不处理阻塞队列中的任务,并强行终止正在运行的任务;

      ④TIDYING:当线程池STOP或SHUTDOWN后,所有任务终止完成后执行线程池清理方法terminated(),进入TIDYING状态;

      ⑤TERMINATED:清理方法执行完毕,线程池彻底终止 进入TERMINATED状态;


    线程池提交任务方法execute():

    ①从execute()方法看出当当前线程数少于核心线程数时,会调用addWorker()方法执行任务;

      ②当当前线程数不少于核心线程数,且线程池处于Running状态,执行offer(command)方法向阻塞队列中添加任务;当添加成功后,再次检查线程池状态,如果线程池不处于Running或ShutDown状态则将阻塞队列中的任务取出执行reject()拒绝方法。再次检查时如果线程池处于运行状态且线程池中不存在线程时,则执行addWorker()方法;

    ③当当前线程数不少于核心线程数,且无法将当前任务加入阻塞队列。则调用addWordker()方法执行任务。如果addWorker执行失败,则执行拒绝策略;

      addWorker():

    从上面可以看出,addWoker是负责执行任务,是线程池的核心。

  首先介绍一下线程池中的两个变量


①mainLock:线程池主锁;

②workers:线程池worker工作集;


addWorker方法前半部分

 

addWorker方法后半部分


  addWoker方法: 首先判断线程池状态,如果线程池状态值大于或等于SHUTDOWN【即不为RUNNING】,则不提交任务直接返回false;如果当前需要创建的线程为核心线程且当前线程数小于核心线程数或需要创建的线程为普通线程小于最大线程数,则跳出循环,执行addWorker后半部分。新建一个Worker【将任务作为构造函数参数】,获取线程池主锁检查线程池状态将worker插入到工作集Workers中。添加成功后执行Woker的t线程【传进去的为一个Runnable任务,启动的是一个Thread线程】。


Worker类【部分】

Woker类实现了Runnable接口,继承了AQS类【这里可以方便的实现工作线程的中止操作,待了解】,可以方便的实现工作线程的中止操作将自身作为一个工作任务传入线程工厂构造线程。因此启动Woker 线程,执行Worker的run()方法,即执行RunWoker(this)。


RunWorker方法(部分)


RunWoker方法:

1、线程启动之后,通过unlock方法释放锁,设置AQS的state为0,表示运行中断;

2.while循环如果firsttask【提交的任务】,或getTask【从阻塞队列中获取任务,如果没有任务getTask会被阻塞挂起,不会占用cpu资源】不为空,则依次执行beforeExecute方法【可以重写线程池该方法】,任务的Run方法,afterExecute()方法。


getTask方法

getTask方法【自旋】:

1、workQueue.take:如果阻塞队列为空,当前线程会被挂起等待;当队列中有任务时,则返回

2、workQueue.poll:如果在keepAliveTime时间内,阻塞队列还是没有任务,则返回null;

四、其他

这篇文章大概介绍了一下线程池及其主要实现,也是自己对线程池加深理解的一种方法。线程池中还有一些其他的非常有用的方法有机会在介绍。另外文章中涉及到了一些其他知识点如 JAVA中的各种锁原理,CAS原理和自旋原理,这也是之后需要加深理解的地方。如果你发现文章中有不清楚地方或错误,欢迎指正!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,186评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,858评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,620评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,888评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,009评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,149评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,204评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,956评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,385评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,698评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,863评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,544评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,185评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,899评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,141评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,684评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,750评论 2 351

推荐阅读更多精彩内容