深入理解 java 多线程核心知识:跳槽面试必备 -买球官网平台

多线程相对于其他 java 知识点来讲,有一定的学习门槛,并且了解起来比较费劲。在平时工作中如若使用不当会出现数据错乱、执行效率低(还不如单线程去运行)或者死锁程序挂掉等等问题,所以掌握了解多线程至关重要。

本文从基础概念开始到最后的并发模型由浅入深,讲解下线程方面的知识。

概念梳理

本节我将带大家了解多线程中几大基础概念。

并发与并行

并行,表示两个线程同时做事情。
并发,表示一会做这个事情,一会做另一个事情,存在着调度。单核 cpu 不可能存在并行(微观上)。

临界区
临界区用来表示一种公共资源或者说是共享数据,可以被多个线程使用。但是每一次,只能有一个线程使用它,一旦临界区资源被占用,其他线程要想使用这个资源,就必须等待。

阻塞与非阻塞

阻塞和非阻塞通常用来形容多线程间的相互影响。比如一个线程占用了临界区资源,那么其它所有需要这个资源的线程就必须在这个临界区中进行等待,等待会导致线程挂起。这种情况就是阻塞。此时,如果占用资源的线程一直不愿意释放资源,那么其它所有阻塞在这个临界区上的线程都不能工作。阻塞是指线程在操作系统层面被挂起。阻塞一般性能不好,需大约8万个时钟周期来做调度。非阻塞则允许多个线程同时进入临界区。

死锁

死锁是进程死锁的简称,是指多个进程循环等待他方占有的资源而无限的僵持下去的局面。

活锁

假设有两个线程1、2,它们都需要资源 a/b,假设1号线程占有了 a 资源,2号线程占有了 b 资源;由于两个线程都需要同时拥有这两个资源才可以工作,为了避免死锁,1号线程释放了 a 资源占有锁,2号线程释放了 b 资源占有锁;此时 ab 空闲,两个线程又同时抢锁,再次出现上述情况,此时发生了活锁。简单类比,电梯遇到人,一个进的一个出的,对面占路,两个人同时往一个方向让路,来回重复,还是堵着路。如果线上应用遇到了活锁问题,恭喜你中奖了,这类问题比较难排查。

饥饿

饥饿是指某一个或者多个线程因为种种原因无法获得所需要的资源,导致一直无法执行。

线程的生命周期

在线程的生命周期中,它要经历创建、可运行、不可运行几种状态。

创建状态

当用 new 操作符创建一个新的线程对象时,该线程处于创建状态。
处于创建状态的线程只是一个空的线程对象,系统不为它分配资源。

可运行状态

执行线程的 start() 方法将为线程分配必须的系统资源,安排其运行,并调用线程体——run()方法,这样就使得该线程处于可运行状态(runnable)。
这一状态并不是运行中状态(running),因为线程也许实际上并未真正运行。

不可运行状态

当发生下列事件时,处于运行状态的线程会转入到不可运行状态:
  • 调用了 sleep() 方法;
  • 线程调用 wait() 方法等待特定条件的满足;
  • 线程输入/输出阻塞;
  • 返回可运行状态;
  • 处于睡眠状态的线程在指定的时间过去后;
  • 如果线程在等待某一条件,另一个对象必须通过 notify() 或 notifyall() 方法通知等待线程条件的改变;
  • 如果线程是因为输入输出阻塞,等待输入输出完成。
线程的优先级

线程优先级及设置

线程的优先级是为了在多线程环境中便于系统对线程的调度,优先级高的线程将优先执行。一个线程的优先级设置遵从以下原则:
  • 线程创建时,子继承父的优先级;
  • 线程创建后,可通过调用 setpriority() 方法改变优先级;
  • 线程的优先级是1-10之间的正整数。
线程的调度策略

线程调度器选择优先级最高的线程运行。但是,如果发生以下情况,就会终止线程的运行:
  • 线程体中调用了 yield() 方法,让出了对 cpu 的占用权;
  • 线程体中调用了 sleep() 方法,使线程进入睡眠状态;
  • 线程由于 i/o 操作而受阻塞;
  • 另一个更高优先级的线程出现;
  • 在支持时间片的系统中,该线程的时间片用完。
单线程创建方式

单线程创建方式比较简单,一般只有两种方式:继承 thread 类和实现 runnable 接口;这两种方式比较常用就不在 demo 了,但是对于新手需要注意的问题有:
  • 不管是继承 thread 类还是实现 runable 接口,业务逻辑是写在 run 方法里面,线程启动的时候是执行 start() 方法;
  • 开启新的线程,不影响主线程的代码执行顺序也不会阻塞主线程的执行;
  • 新的线程和主线程的代码执行顺序是不能够保证先后的;
  • 对于多线程程序,从微观上来讲某一时刻只有一个线程在工作,多线程目的是让 cpu 忙起来;
  • 通过查看 thread 的源码可以看到,thread 类是实现了 runnable 接口的,所以这两种本质上来讲是一个;
ps:平时在工作中也可以借鉴这种代码结构,对上层调用来讲提供更多的选择,作为服务提供方核心业务归一维护

为什么要用线程池

通过上面的介绍,完全可以开发一个多线程的程序,为什么还要引入线程池呢。主要是因为上述单线程方式存在以下几个问题:
  • 线程的工作周期:线程创建所需时间为 t1,线程执行任务所需时间为 t2,线程销毁所需时间为 t3,往往是 t1 t3 大于 t2,所有如果频繁创建线程会损耗过多额外的时间;
  • 如果有任务来了,再去创建线程的话效率比较低,如果从一个池子中可以直接获取可用的线程,那效率会有所提高。所以线程池省去了任务过来,要先创建线程再去执行的过程,节省了时间,提升了效率;
  • 线程池可以管理和控制线程,因为线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控;
  • 线程池提供队列,存放缓冲等待执行的任务。
大致总结了上述的几个原因,所以可以得出一个结论就是在平时工作中,如果要开发多线程程序,尽量要使用线程池的方式来创建和管理线程。

通过线程池创建线程从调用 api 角度来说分为两种,一种是原生的线程池,另外该一种是通过 java 提供的并发包来创建,后者比较简单,后者其实是对原生的线程池创建方式做了一次简化包装,让调用者使用起来更方便,但道理都是一样的。所以搞明白原生线程池的原理是非常重要的。

threadpoolexecutor

通过 threadpoolexecutor 创建线程池,api 如下所示:
public threadpoolexecutor(int corepoolsize,int maximumpoolsize,                          long keepalivetime,timeunit unit,blockingqueue workqueue); 

先来解释下其中的参数含义(如果看的比较模糊可以大致有个印象,后面的图是关键)。
  • corepoolsize
  • 核心池的大小。
在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了 prestartallcorethreads() 或者 prestartcorethread() 方法,从这两个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建 corepoolsize 个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到 corepoolsize 后,就会把到达的任务放到缓存队列当中。
  • maximumpoolsize
线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程。
  • keepalivetime
表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于 corepoolsize 时,keepalivetime 才会起作用,直到线程池中的线程数不大于 corepoolsize,即当线程池中的线程数大于 corepoolsize 时,如果一个线程空闲的时间达到 keepalivetime,则会终止,直到线程池中的线程数不超过 corepoolsize。
但是如果调用了 allowcorethreadtimeout(boolean) 方法,在线程池中的线程数不大于 corepoolsize 时,keepalivetime 参数也会起作用,直到线程池中的线程数为0。
  • unit
参数 keepalivetime 的时间单位。
  • workqueue
一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下这几种选择:arrayblockingqueue、linkedblockingqueue、synchronousqueue。
  • threadfactory
线程工厂,主要用来创建线程。
  • handler
表示当拒绝处理任务时的策略,有以下四种取值:
  • threadpoolexecutor.abortpolicy:丢弃任务并抛出 rejectedexecutionexception 异常;
  • threadpoolexecutor.discardpolicy:也是丢弃任务,但是不抛出异常;
  • threadpoolexecutor.discardoldestpolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程);
  • threadpoolexecutor.callerrunspolicy:由调用线程处理该任务。
上面这些参数是如何配合工作的呢?请看下图:

注意图上面的序号。

简单总结下线程池之间的参数协作分为以下几步:
  • 线程优先向 corepool 中提交;
  • 在 corepool 满了之后,线程被提交到任务队列,等待线程池空闲;
  • 在任务队列满了之后 corepool 还没有空闲,那么任务将被提交到 maxpool 中,如果 maxpool 满了之后执行 task 拒绝策略。
流程图如下:

以上就是原生线程池创建的核心原理。除了原生线程池之外并发包还提供了简单的创建方式,上面也说了它们是对原生线程池的一种包装,可以让开发者简单快捷的创建所需要的线程池。
executors

newsinglethreadexecutor
创建一个线程的线程池,在这个线程池中始终只有一个线程存在。如果线程池中的线程因为异常问题退出,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

newfixedthreadpool
创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

newcachedthreadpool
可根据实际情况,调整线程数量的线程池,线程池中的线程数量不确定,如果有空闲线程会优先选择空闲线程,如果没有空闲线程并且此时有任务提交会创建新的线程。在正常开发中并不推荐这个线程池,因为在极端情况下,会因为 newcachedthreadpool 创建过多线程而耗尽 cpu 和内存资源。

newscheduledthreadpool
此线程池可以指定固定数量的线程来周期性的去执行。比如通过 scheduleatfixedrate 或者 schedulewithfixeddelay 来指定周期时间。
ps:另外在写定时任务时(如果不用 quartz 框架),最好采用这种线程池来做,因为它可以保证里面始终是存在活的线程的。

推荐使用 threadpoolexecutor 方式

在阿里的 java 开发手册时有一条是不推荐使用 executors 去创建,而是推荐去使用 threadpoolexecutor 来创建线程池。
这样做的目的主要原因是:使用 executors 创建线程池不会传入核心参数,而是采用的默认值,这样的话我们往往会忽略掉里面参数的含义,如果业务场景要求比较苛刻的话,存在资源耗尽的风险;另外采用 threadpoolexecutor 的方式可以让我们更加清楚地了解线程池的运行规则,不管是面试还是对技术成长都有莫大的好处。
改了变量,其他线程可以立即知道。保证可见性的方法有以下几种:
  • volatile
加入 volatile 关键字的变量在进行汇编时会多出一个 lock 前缀指令,这个前缀指令相当于一个内存屏障,内存屏障可以保证内存操作的顺序。当声明为 volatile 的变量进行写操作时,那么这个变量需要将数据写到主内存中。
由于处理器会实现缓存一致性协议,所以写到主内存后会导致其他处理器的缓存无效,也就是线程工作内存无效,需要从主内存中重新刷新数据。

阅读全文请
扫描下方二维码,
还可以加入读者圈与作者聊天~:

  • 大小: 20.3 kb
  • 大小: 31.8 kb
  • 大小: 30.5 kb
  • 大小: 166.4 kb
  • 大小: 188.9 kb
  • 大小: 22.2 kb
来自:
5
0
评论 共 5 条 请登录后发表评论
5 楼 2018-04-17 08:57
要是有根据机器或容器(如:tomcat)如何定义最大并发连接数介绍就更完美了
4 楼 2018-04-12 21:07
   
3 楼 2018-04-08 10:23
写的不错,学习了
2 楼 2018-04-04 14:41
1 楼 2018-04-04 10:45

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • 深入理解-java-多线程核心知识:跳槽面试必备.docx

  • 多线程相对于其他 java 知识点来讲,有一定的学习门槛,并且了解起来比较费劲。在平时工作中如若使用不当会出现数据错乱、执行效率低(还不如单线程去运行)或者死锁程序挂掉等等问题,所以掌握了解多线程至关重要。...

  • 多线程相对于其他 java 知识点来讲,有一定的学习门槛,并且了解起来比较费劲。在平时工作中如若使用不当会出现数据错乱、执行效率低(还不如单线程去运行)或者死锁程序挂掉等等问题,所以掌握了解多线程至关重要。...

  • 多线程相对于其他 java 知识点来讲,有一定的学习门槛,并且了解起来比较费劲。在平时工作中如若使用不当会出现数据错乱、执行效率低(还不如单线程去运行)或者死锁程序挂掉等等问题,所以掌握了解多线程至关重要。...

  • 多线程相对于其他 java 知识点来讲,有一定的学习门槛,并且了解起来比较费劲。在平时工作中如若使用不当会出现数据错乱、执行效率低(还不如单线程去运行)或者死锁程序挂掉等等问题,所以掌握了解多线程至关重要。...

  •     因为今天收到了工资条,才发觉不知不觉已经进新公司一个月了。感受颇多,原来在九城做的时候感觉公司很大,做事情不能随心所欲,心里一直有一种畏惧感。我刚进九城3个月的试用期几乎没有做太多事情。主要是由于没有工作经验,不能很快领会leader的要求,因此经常挨批:)现在的公司在上海一共也就十几个人,技术人员就3个。没有开发人员。因此,感觉很不一样,不管是线上服务器还是公司内部其他员工的台式机都需要

  • 一直对开源软件的倡导着有着非同反响的好感,疯狂的崇拜richard stallman(一头大胡子,从一个mit人工智能实验室的试验员到84年成立gnu,他的一些做法确实值得我们去敬佩他。还有一点不清楚的是到底他不喜欢女人呢?还是怎么回事,居然到现在还不结婚,这点我估计做不到)。我想我从事unix系统管理员这个职位很大程度是受他的影响,可以“肆无忌惮”、“疯狂”的使用gnu的软件。工作时间可以研究g

  •     今天买了《中国国家地理》杂志,拿到手,好厚。比较了前面几期,将近多了100页。而且这次只有一个主题--大香格里拉。分了以下几个篇章:序篇,婚姻篇,民族篇,民居篇,历史篇,雪山篇,江河篇。对于想了解这个神秘的地方绝对是一本好的资料。     现代的婚姻法确实有太多太多的弊端体现出来了。离婚率越来越高。男人的压力越来越大。这些都是很大的缺点。而走婚制完全改变了这样的现象。先来了解一下什么叫走

  • 今天中午想做点有意义的事情,结果一个朋友给我发了一个地址,让我测试智商,结果唉...差2分就非常非常了! ttp://www.hao1.com/01-zhishangceshi.htm 大家测试后说下得分  

  • 欢迎访问我的个人买球官网平台主页。由于精力有限,以后这里将不更新。大家可以通过访问我的买球官网平台主页来看最新的内容http://lupeiqing.3322.org

  • 我6.29号刚在我得blog上面发表一条自认为很令人振奋得消息。特好消息:cnforyou & dearbook合并了。但实际情况是:我6.25从新风雨上定了3本书,周五下午定得,按理最晚下周一也就可以收到了。可是我到现在(7.3日13:50)才收到3本书中得1本。其他2本告诉我说要从北京调,会很慢很慢,送货的人特地加重了“很慢”这个词语。新风雨不像chinapub会把缺货写明,我下订单的时候根本

  •     我的手机是索爱的t312,其他都非常好,就有一点非常不爽,就是输入汉子特慢,导致我发用手机发短信都特慢。因此一直寻求网上发短信的东东。从5月到现在我一直用的是腾讯通讯簿,到6月底前是免费的,用的一直不错,可是现在收费了,自然对他热情减半。寻寻觅觅中突然发觉msn可以免费发短信,兴奋之余立刻写下自己的感受:)其实严谨一点不能说msn可以发,只不过现在上面有msn机器人可以免费发短信,当然最后

  • 请访问http://lupeiqing.3322.org/blog今后这里的blog将只贴我写的技术文章,其他所有的文章都可以从上面的网址进行访问:)?

  • 今天小狼通过msn发了一个链接过来,委婉的表达了这辈子她是我这辈子最重要的人,我也是她这辈子最重要的人。要尽量体谅对方。挺有道理的。相信我们都能做到的! 原文如下: 故事发生在美国的一所大学。    在快下课时教授对同学们说?"我和大家做个游戏,谁愿意配合我一下。"    一女生走上台来。      教授说?"请在黑板上写下你难以割舍的二十个人的名字。"    女生照做了。有她的邻居、朋友以、亲人

  •     昨晚由于升级系统才睡了4小时,但是现在还是想发表一下自己的想法再去睡~~~     今天公司来了新的总经理。同时到来的有从北京来的2个公司的2把手,人事总监,销售总监。我想这段时间可能会比较惨。公司的氛围一定会和前段时间群龙无首的情况有些不同。再加上还有2位总监级别的人物呆在上海,痛苦啊!上海总经理到位后才给公司所有员工简单的介绍了一下自己叫什么,然后大家同时自我介绍。就留下销售开了几个小

  •     这段时间非常火热的重大事件,有非常多的说法在外面乱传,我将我认为最可信的分析写下来。     “我有个小站,色情的,被封了1个多星期,每次我一指到一个ip,当天中午那ip就会被中国电信隐蔽,所以我一气之下,ping 了china.com的ip,然后把ip输入到我那被封域名的a记录里,当天中午,china.com不能访问了.”    --在绿盟中有人发帖(http://bbs.nsfocus

  • 对于在上海的人来说,我买书基本上是去china-pub和新风雨的。去这些网站看书评也是我的一大爱好,呵呵。不过里面真的是鱼龙混杂,有盲目崇拜的,有只要是别人翻译的作品就大骂得人。我本人很少参与进去,但是我看得很多。china-pub有一点不好,优惠只限在总站,这样我在上海就需要邮购,对于我这样的懒人来说,这个实在过于麻烦了。而新风雨总体价格比china-pub稍贵,但是它是免送货费的,因此很值得

  • 今天是ipod mini的中国首发日。从ipod的诞生到现在,应该已经有3年了吧。从第一代到现在的第四代,再到mini的发布。我能够抵挡住一再的诱惑,实在是佩服自己。但是发觉现在真的控制不住自己了,一定要得到一部ipod。不是mini也无所谓。毕竟mini才4g,而且价格现在还要2488,太贵了点。今天刚收到招行的信用卡广告,里面居然有分期付款购买ipod的活动,口水阿。先放出部分mini的图片大

  • 今天上班有3个小时左右的时间在折腾2台sun的工作站,分别是ultra60和ultra80,对我来说能够亲手折腾这2台几年前价值不菲的机器,开始别提多兴奋了。而且是属于随便折腾都行,就更爽了。2台服务器都是从线上光荣退役下来的服务器。看看他们英俊的脸蛋和背部吧^_^ 80里面已经没有内存和硬盘了。早就被拿光了。而60里面插满了内存,还有2块硬盘。里面居然有16根内存条,全部插满。别被吓倒了,其实

global site tag (gtag.js) - google analytics