• 目录 使用Spring Social开发第三方登陆 OAuth协议简介 OAuth运行流程 授权模式 Spring Social简介 开发QQ登陆 项目地址:链接:https://pan.baidu.com/s/106lZU8P9gHG4A3aP4OpOgQ ...以前的逻辑是通过微信...

    目录

     

    使用Spring Social开发第三方登陆

    OAuth协议简介

    OAuth运行流程

    授权模式

    Spring Social简介

    开发QQ登陆


    项目地址:链接:https://pan.baidu.com/s/106lZU8P9gHG4A3aP4OpOgQ
    提取码:xs3v

    使用Spring Social开发第三方登陆

    OAuth协议简介

     

    以前的逻辑是通过微信用户密码登陆,获取微信的用户数据,但是这种方式存在的数据泄露的危险。

    由于本来该软件中本身就是只需要用户的登陆数据,但是通过密码登陆获取额外的数据。所以不安全。

    所以不是将用户密码交给微信端,而是将一个令牌交过去。

    OAuth运行流程

     

    授权模式

    授权码模式(web系统认证)

    密码模式(app系统认证使用)

    客户端模、简化模式(使用较少)

    Spring Social简介

    简单来说,在第六步获取到用户信息之后,根据用户信息构建Authentication并放入SecurityContext里面,SecurityContext在源码分析的时候应该已经了解了,它的作用就是讲认证成功的Authentication保存到session里面。

    在spring security框架中,就是增加了一个SocialAuthenticationFilter类来拦截处理第三方请求。

     具体的逻辑实现:连接第三方的类是connection,里面实现的serviceProvider中主要是两个接口OAuth2Operations(连接第三方),Api(AbstractOAuth2ApiBinding)用来适配获取的数据。在通过ApiAdapter适配对应的DB数据

    开发QQ登陆

    创建需要的接口

    以下都是右边核心代码(ServiceProvider)

     以上都是应用商需要的代码

    开始编写中间的代码

    现在还需要将数据保存到数据库当中,也就是最左边的数据库那块

    在数据库中创建userconnection表

     表的前缀添加“mxl_”,也就是说数据库那边的表前面加了该前缀就必须写没加就不设置该属性

     接下来如何将上面的userid转换成用户信息呢

    在之前的UserDetailsService中,用来验证用户的密码是否正确,然后就认证的数据放入到session里面

     而第三方认证这里也同样的原理,提供了SocialUserDetailsService

    首先解释一下loadUserByUsername()方法,处理的是表单传进来的数据

    而loadUserByUserId()方法是第三方登陆的时候用的,传进来的是通过第三方传进来的userId

    该方法返回的SocialUserDetails就是UserDetails的实现

     创建QQProperties类

    在连接类那里我们需要使用到这三个参数

    只有imooc.security.social.qq.app-id配置了该配置项才会生效

    在qq互联里面查看

    添加social过滤器

    添加到配置中

    页面修改

    所有的/auth请求都会被SocialAuthenticationFilter拦截

    /qq中的qq就是传入的providerid的值

    这个时候登陆还是有问题的

    展开全文
  • dairy_2018

    2019-02-23 21:31:06
    中午吃饭的时候无聊就看了会算法视频,因为上次我心爱的耳机的丢失,我是开小了听不见,开大了怕影响别人,机房电脑又能蓝牙所以蓝牙耳机也能用。。。 我近2k买的cks1100丢了是真的心痛,听...

    初勘·2018

    在这之前也写过许多次日记,但是那时候因为上学的原因,没有东西可写也就不了了之了,现在上大学了,情况会有所改善吧。

    当然,不是突然哪根神经抽动才想起来写日记的,今天在翻阅FFT的时候就顺着翻到了一位博主的博客,然后看到那位博主整理了很多模板。我今天刚看了会AC自动机,就翻了一下他的模板。后来就看到了他写在博客里面的日记,从上学写到了工作。我觉得很有意义,就决定仿照一下,顺便一提,我以前也是比较喜欢写日记的。

    但是我感觉每天写日记确实会造成时间的浪费,毕竟有太多知识需要去汲取

    说到浪费时间,有时候我还会打会游戏,这个确实难以控制,因为我感觉休息一会也不会怎么样。但是我觉得开学后就很少打游戏了,毕竟竞赛之外还有学业去花时间,而且健身房也开了,可以去健身房度过给自己的放松时间(不放松的话坚持不了几天学习效率就下去了)

    日常篇-大二上

    暑假才是最努力的时候hh

    18.8.20

    今天是8月20,离暑假集训结束还有一个星期,虽然这一个月下来我学的东西比我上个学期学的还多,但是我任然觉得自己知识匮缺。因为从学长口中说出的太多的知识我还不知道,也因为最近比赛感觉自己队表现的都不是很好。

    那种弱于同龄人(当然是周围的同龄人,诸如清华北大,那些人并不是当下的我的目标)的感觉,很扎人,你绝对不会喜欢这种感觉。不过我也得感谢这些强大的同龄人,使我有了坚持下去的动力。

    说到我的队,我觉得确实比隔壁队稍弱,因为第1和第3在隔壁,第2(在下)和第4在我的队。当然这也不是借口,也不能怪队友,可能就是因为我脑子不好使,不能1打3的原因吧,哈哈。(但是话说这个排名好像不太可靠,我感觉那三个人都我厉害的说,我居然不是第四。。。)所以说,如果要找原因的话,找到最后永远可以找到自己身上,毕竟你可以改变的只有自己而已。

    不过这个情况我也是很担心啊,怕之后的比赛发挥失常。争来争去,争得还是那口气啊!

    我把最近接触的但还没有学的东西摘到了笔记本里,整整一页!我看着我那个气叹的啊,只能留到以后慢慢去消化了。不过学这些专题知识感觉对近期的比赛帮助不大,对比赛帮助大的还是。。。一个优秀的脑子啊!题目用到的都不是什么高级的算法,而是思维,像今天那题,使一个矩阵(n行m列,由1~n*m填充)满足只有一个位置是这排这列最大的元素,求方案数。一个dp解决的事比赛的时候死活就是想不到。哎!

    这个东西没法快速提升的,只能靠多刷题了

    今天就到这吧,老头要催了(在机房)

    9.6

    白马过隙,一年辗转,时光不再。再平凡,这一年也会留下些许感触。

    庆幸那几日的忙碌,大学过得还算充实。但是并不是对于课堂,而是闲暇。尝不满大学体制的臃肿,打着全面发展的口号往课表中填充无益于学生发展的课。当今社会,大学生或则碌碌终日消磨时光,或则终日忙碌于自身发展,于前于后,皆不得益。

    好在时间慷慨,大学课表不满,若由衷向学,总是可以挤出时间的。可以说我这一年收获的,大多都是课外的事了。

    9.8

    今天有一场网赛,所以早上在寝室休息。闲来无事,便写起了断了很久的日记。
    想来无限感慨,写时却发现这半月未曾经历什么。也是,整天在机房能有什么感慨,难道一直说自己怎么这么菜吗?

    如此看来,生活确是单调,清波无澜。好在,一直是清波。

    我也希望接下来的几年也一直是这样,生活一直安逸下去,家人和谐安康,一天都可以静下心来学习,学累了去健身房健会身,偶尔和家人通话,聊些家常……这样的大学生活真的很理想,毕竟这里远离社会的喧嚣,不需要怀疑猜忌,勾心斗角。

    9.9

    看着昨天写的那篇日记写今天的日记真的是无限感慨。。。

    昨天比赛垫底真的是要自闭了。去健身房的时候狠狠的发泄了几个小时,二头肌现在还痛着~

    还好今天正常发挥。昨天会不会是前天晚上吃褪黑素的原因?毕竟是用来提升睡眠质量的,昨天的比赛达到后面脑子就开始晕晕的了。不过说来说去还是自身实力的问题,K短路自己没个板子,因为K短路卡太久模拟题又没时间做,哎!回想起来又一阵自闭。。。

    今天我就发挥的比较好,队友之间配合也比较默契,就是最后面被榜带偏没去做另外两道简单题而是压了一道比较难的题,结果还没做出来。

    以后一定要有一个人读剩下来的题目,万一就是一道没被发现的水题呢?

    明天就是正式开学了,又要早起了QAQ,睡不到8点怎么有精神好好学习。。。

    秦皇岛篇

    三题93名铜前,还可以

    9.25

    明天就要去秦皇岛了,感觉好,麻烦啊。。。这里就不激动了,因为要忙好多东西。
    首先是我准备了几天的模板,因为第一次比赛的原因,所以我也是第一次准备这种比较正式的模板。除了把自己的博客里的知识整理出来以外,还有打印一本其他人整理出来的模板库。因为以后还有比赛的原因,我打算把模板库打印成书。当然,自己的模板会逐日完善,所以直接打出来就可以了。
    然后还有换洗衣服啊,食物啊,护肤品啊……
    今天晚上有体育课的补课(昨天中秋落的,不过我也是很无语啊一个体育课还要补课,而且这个老师还是个话多的。。。),我在下午韩老师开会后,便急忙赶去吃了饭
    再赶去上课。
    上完课我回寝室放了球拍再去打印,人又很多,我还要打书比较麻烦,弄了一个多小时弄好后回去随便买了点食物(当然不会忘记买一罐旺仔牛奶压箱底),终于可以回寝室收拾了。
    只能说本来很宽裕的时间,因为一个体育课,大大的缩了水。哎,补课不提前通知的老师我还是第一次见。

    9.26

    早起永远是折磨。7:10起床,整理整理便出门了。这个时候突然肚子痛,扎心。
    到火车站后因为没有身份证的原因,只能去进站口办了临时乘车证明后,去窗口拿票。好麻烦,虽然以前都是这么拿的。所以说科技真的改变了生活,自助取票机这个不起眼的东西大大提升了火车站的效率,人们在体验过就再也离不开了。
    话说我们九个学生出去会不会集体被拐啊hh。
    乘高铁到上海站后,去kfc买了个汉堡填下肚子(只有汉堡原价,其他的貌似都翻倍了),接下来就是漫长的绿皮软卧之旅了。
    环境比我想象的要好,一间房间(无门)左右两排,每排上中下三床。中上床位置有点小,不能坐起来。不过我懒得上去,便在下面的小凳子上度过了一个下午。
    绿皮车没充电的地方,我们几个人也很无聊,就买了副扑克玩了起来。之后又玩了会24点。我的两个队友在后面一个车厢,于是我就时不时走过去走过来,总之很无聊。
    不过至少环境还行,虽然有一丝的烟味,但不是很重。一包猪蹄20,还是可以接受的。床铺也算干净。因为这一片都是自己人的原因也没有多少不适感,搞得和度假一样。
    晚上,用自带的洗漱用品随便洗漱一下,就上床了。不过灯亮着有些睡不着,就拿着模板看了会。
    10点,灯熄了。我感觉这个环境很容易睡着,结果对面下铺的那个人拉了个老同学,拿着肉串准备彻夜长谈了。我那个气,直接教育了一下他们。结果他们虽然有些收敛,但还是吃完串才走(走到其他地方聊天,这两个大老爷们屁话这么多我也是醉了)

    9.27

    还记得那两个屁话的人吗?这两个人大早上就开始bb了。
    洗漱后坐在窗边看了会北国风光,毕竟第一次来北方。
    北方秋至后陆续换耕小麦,现在大多都是玉米。但是这些玉米大多不是用来吃的,耕者耕以卖,而买者买以饲,饲牛羊狗彘。听闻现在北方并不是以麦为主食,而是米粟麦共食。大米进于南方,小米华北地区盛产。小米氨基酸丰富,口感细腻,味道香醇,然而我并没有吃过小米。。。
    下了车后,感到温度略低却不冻人,典型的入秋的凉爽。但过会后太阳便开始普照,温度也自然上去了。若不遮挡,还会觉的稍热,并没有想象中的北方的寒。
    但是空气确实很干燥,没过一会便口干舌燥,脸上的皮肤也有些不太适应。
    入住宾馆时虽然没有带身份证,但是填了个表也就完事了。布丁酒店,房子的装潢比较符合布丁二字给人带来的感觉,很舒适,主色调为黄和橙,窗帘和墙纸都是可爱的漫画。
    收拾后便去学校报到了。这里便不多文饰了,毕竟浙师大已经听好了,除非厦门大学那种真的特别好看的,不然也就只能是新奇带来即时好感罢了。
    到了报到的地方,拿了一件粉色的短袖和参赛牌,拍了几张合照便去吃饭(伙食一般,凑活,免费已经不错了)了。回来的时候躺在舒服的床上写了会日记。再次感慨,这个宾馆的床单滑滑的好舒服,枕头很大个弹性好,额,感觉自己寝室的装备好low。
    又打了盘游戏,所以下午打热身赛的时候有点浑浑噩噩的。但是出单还在铜尾,可能是因为大家都没有认真打吧,主要是熟悉环境。
    打完后和两个队友去吃了披萨(话说还就没吃了,出去吃大多是肯德基),吃完回操场上散了几圈步,就回宾馆了。
    回去后,叫另一个女队友来我们房间一起想了会下午题目,一道是给一棵树,求点权和模k后余1,余2……余k-1的子图的个数;另一题是n数组,q次操作排序lr区间的数,问最后的结果。不过能力有限,暂时没做出来。
    之后艰难的洗了澡,不是一次性的拖鞋嫌弃脏,只能用一次性的。这个一次性的也是奇葩,一层棉上面一层薄纸,不用手基本穿不进去。问题来了,洗完澡地湿的,又嫌弃地上脏,我得用莲蓬头冲一下脚再去穿拖鞋,地上滑我又不敢弯腰,直接用脚穿真的是穿出心理阴影来了。
    睡觉了,队友还和女朋友聊了会天qaq

    9.28

    昨天定的7点的闹钟没听到,睡到了7点半,赶紧准备了一下,求出门了。到比赛地点都又肚子痛,不过还好不是比赛中肚子痛。到学校后,买了草稿本和备用笔,队友都到了后让他们按照平时的座位坐好(感觉这样比赛的时候会习惯好多),比赛在9:10就正式开始了。
    第一道水题是问x点到y点,时针和分针重合了几次(开始时计入,结束时不记入),我直接模拟了一遍,感觉细节还是很多的。
    下一题是树,给出无根树是否为完全K叉树,考虑了一下完全K叉树的度的情况,正常来说root的度为K,中间节点的度为K+1,叶子为1。这样我们直接遍历所有的度,看是不是三种,如果是再从root跑一遍,看看是不是所有的节点有K个儿子,看看叶子节点的deep是不是都一样。除此之外,要特判链的情况(度为1的有两个,其他的度为2)和只有两层的K叉树(两种度K和1,只有一个节点的度为K,其他为1)。因为是双向边但是队友只开了一倍,RE了一发。第二发T了,因为每组案例要memset一个head数组,开的是1e6,而题目说n的总和不超过1e6,如果给出1e6组n=1的就直接1e12了。所以每次memset改成for(int i=1 to n)。
    我们做的还算快的,但是字符串的那道水题留到了后面,所以罚时就上去了。
    字符串的那题队友把按字典序排序的那个单词理解错了,所以想了很久。其实直接做出6个可能的字符串直接去匹配就可以了。
    出三题的时候我们还是银的,这场手速还可以。
    最后我们决定专攻背包那题。在想了很久后,想了个做法就开始敲,结果先是有错误,然后塞了最大案例进去跑了30多秒。本来想直接放弃的,队友激励了几句就继续改,不管T不T至少把bug找出来。后来发现是在遍历set的过程中有set的insert操作,所以也就是说刚塞进去的又会被拿出来做一次。
    最后答案对了,但是不管怎么改怎么优化还是30秒附近,无奈,只能放弃了。
    最后一小时封榜,看不了,那个时候我们是铜顶,差33分钟的罚时进银。那个时候我以为榜就是那个,气得要死。结果公布后93,24金尾,73银尾,147铜尾。结果发现我从77变成93反而开心了hhh。不过我们一队银2,26名,这个是这个感人,如果是我的话肯定气死,不过我现在连银都没有qaq。
    结束后,我在闭幕典礼中上去领奖,一个挺厚实的木制奖牌,铜牌。我其实很想带回家给家人看的,但是按照规定要放到队里面。不过后面会有奖状也还可以。
    之后去吃了个排骨饭,手抓的,超级大的一个排骨,结果手套再次漏了,没地方洗手,纸还擦不干净hhh。打的去秦皇岛北戴河飞机场,第一次啊。还挺远,70公里,开了一个小时左右。
    在飞机场拍了几张照片,九点左右便过安检了。(护肤品,剪刀居然过了不知道该说什么了,没有想象中的严,不过至少我不用邮寄行李了)
    空姐很漂亮hhh,下飞机的时候去拍合照,不过被委婉的拒绝了。好吧,近看也不是很好看,估计靠妆,不过被回绝了好奇哦。
    凌晨一点,下飞机后乘大巴到火车站附近,我们一行人就带着各自的行李在这个城市的夜间走了2公里路。
    凌晨两点,到了火车站。这个地方很破旧吧,我还在想东站怎么变成这样了来着,后来得知原来是另一个火车站。周围全是打地铺的人。进去之后也很简陋,毕竟是旧站。
    好不容易是下铺,结果一个大妈抱着孩子要和我换,我觉的孩子睡上铺是有点危险,结果就换到上铺了。也不盖被子,太累了直接睡了。
    5点到金华,打车开了半个小时终于来到了学校。
    到寝室5:40,还好门没关。回寝室洗了个澡,没有热水,不过还好我习惯用冷水洗澡。洗完后吃了点面包就上床睡觉了。

    日常篇-大二上

    生活得好杂~
    不过这就是生活吧。

    9.30

    前两天因为ccpc的原因熬了一夜,感觉好累。

    这两天的课也落下了,话说集训队的那个帮忙请假的同志有点不负责任了,好几门要点到的课都没有请掉,所以现在我还得去一门一门的请掉。

    其它课还好,因为落下几节对成绩不会有很多影响。主要的概率论,离散数学和c#。

    白天学校上的是星期五的课(国庆的原因),我通过自己的努力成功赶上了C#的进度。晚上在机房又把离散数学和概率论的知识补了,所以剩下的只有各科的作业了。

    回去后就有点颓废了,和室友玩游戏玩到12点左右。

    10.1

    早上说好去机房的,结果又和室友玩了一早上游戏。中午点了个披萨,然后又是一下午。晚上和另一个室友三个人去外面吃了一顿。晚上继续浪。

    国庆这一天,是我这一年来最xx的一天(找不到形容词了)。

    暗自发誓明天开始正常学习。

    10.3

    这两天还是有点水的,只是补了几门课程的笔记和作业。

    可能是前两天坐火车时着凉了,这两天有点感冒,还挺严重的。有点咳嗽,最难过的是晚上睡觉喉咙痛,崩溃。白天还头痛,好吧,这不是我偷懒的借口,就是自己懒,贪玩游戏。

    10.4

    今天开始训练,想偷懒也偷不成了。

    早上9点有个浙工大的大佬来讲座,一个数学和dp问题的专题。
    几乎全是新知识,感觉好难。。以后有时间再慢慢消化吧。

    下午女队友还没来,我们两个人反正随便做了一下,后面就开始玩了。(感觉真的做不出来了)

    虽然感冒很严重,但是晚上还是去健身了。毕竟我不会连续超过两天不健身的。现在我每次除了锻炼各个部位的肌肉以外,还在开始和结束添加了两次跑步。今天开始200m用8km/h热身,之后16km/h跑1000m,算上跑步机的缓慢的加速时间,用4m3s。虽然我跑完还有余力,但是如果正常跑的话还会有空气阻力和向前的推力,所以综合一下我觉得也差不多是这个时间了。

    现在不想其他的,就想感冒早点好,毕竟很影响作息和学习。

    10.6

    感谢室友的快客,一片一夜,第二天除了喉咙还有点难受以外,感冒差不多直接好了。hh,又可以浪了,又可以洗冷水澡了。

    接下来几天就很常规了,不会一整天玩,也不会一整天学习。该健身的健身,该玩的玩,该学习的学习。

    10.16

    这个星期日就要体侧了,这个1000m感觉比ccpc还要难应付。所以这几天晚上的健身时间就变成了操场上的跑步时间。为什么强调操场?因为我在健身房的跑步机上可以1000跑的速度在操场上只能怕跑800。风阻太大了!

    10.17

    中午吃饭的时候无聊就看了会算法视频,因为上次我心爱的耳机的丢失,我是开小了听不见,开大了怕影响别人,机房电脑又不能蓝牙所以蓝牙耳机也不能用。。。

    我近2k-买的cks1100丢了是真的心痛,听了贴吧上的推荐,今天中午下单一个1k+的msr7(陌生人妻),加上前几个月买了一串老山檀佛珠,现在真的是压箱底的钱都没了,这个月只能吃土了。。。

    期待人妻的表现QAQ(话说我和那个店家聊天的时候提了一下se,2k+的那个,蓝色配色颜值惊艳。他说有货,我差点就发昏换了,哎,等我以后赚钱了一定买很多耳机。要是有个有钱的土豪朋友送我各种高端数码产品,我。。。)

    10.18

    早上来机房本来打算做题的,但是又因为耳机的事情(音源,煲机,其他问题)浪费了一个早上。哎,就算不玩游戏,人类还是很容易分心的。

    10.21

    下午的体测,嵌在下午的训练之间,一道模拟题卡在Case32过不去,然后就去体测了。
    50m起跑满了,7.5,引体向上16,1000m因为站了太久不想跑了,只好等11月中的补测。
    回来继续找bug,结束后让别人帮忙看了案例才知道if(a==1)写成了if(a)。
    晚上是一晚上的炉石,回去后想着最后浪一天,见了两年年多不见的老朋友泡面,打王者到12点。吃完想吐。。。哎,明天还要早起上课。

    10.22

    早上六点被自动亮起的灯照醒,后来就睡不着了,睡觉时间5个小时。。不过早上的课还是很有精神的,可能是因为早上的课比较重要吧。

    晚上先回机房写作业,本来打算一边听着我的人妻一边写作业的,但是感觉有点汗就没带了,毕竟这么好看这么漂亮的人妻应该好好保养。

    Msr7评测

    拿到后,第一感觉就是高颜值。棕色皮革,红色光圈,不愧人妻!
    耳套的外层也是人造皮革,填充物非常舒服,弹性很好。
    夹头其实真的不是很明显,因为长度可调,只要认真调长度和佩戴方式,一定可以找到一个舒适的姿势。

    刚开始的时候人声唱高了锯齿感很强,但是这个问题在煲好机后有所好转。
    女声的表现说实话并没有到底女毒的程度,不过确实比这个价位的其他耳机要近耳一些。
    低音部分虽然下潜不够,但是弹性很好,听起来很脆,悦耳。这个耳机本来就不是用来听那种动次打次的,我觉得这种设定很好。可以很好的配合女声的近耳,而不会抢风头。

    因为是皮质耳套的原因,有点热,容易出汗。

    颜值是真的高,很喜欢一个配色,可以当老婆一样供着了。

    10.29

    今晚玩了3个半小时的游戏,打破了上个星期开始不玩游戏的誓言,我对自己的行为感到深深的自责。

    有一部分原因是因为身边的人一直在玩游戏,但是我并不好以此为理由对自己从轻发落,这个稍后再说。如果你的同伴上课的时候一直玩游戏看视频,下课了也继续玩游戏,这个影响不必多说。现在上课的时候,我已经不像以前那样随便打发时间了。老师说的几乎每一个知识点,无论难易,我都会听。我怕,如果我旁边一直有人这样影响我,我会变回去。

    接下来是惩罚,明天下午我虽然要去一趟医院,但是,我还是要惩罚自己,明天必须做一场codeforce的比赛。

    10.30

    早上起床后发现落枕了,不过按摩加热敷感觉好多了,不影响敲代码就是好事。不过吃饭的时候需要低头有点痛。

    上课的时候发现CSDN有app,就一直在看FFT。以前虽然想上课看算法,但是浏览器上直接看太麻烦了。现在终于可以用手机学习了。

    中午到下午两点,终于解决了hdu上的一道FFT题目,我算是成功学会了FFT。这个毒瘤从暑假训练的时候就一直想解决,但是每次一看那么多东西就头痛。今天我从代码入手,抛弃了一大堆对做题没有帮助的知识点,终于是成功KO了一个毒瘤。

    下午去医院,挂的号下午四点半。太早了,我去百货弄了下佛珠。去银泰城逛了逛,最后去星巴克买了杯饮料求去医院了。医生也说鼻炎很难治,哎。回来后晚上做了场多校,结束了一天。

    11.1

    上心理课的时候,看到了以前的自己。一个听话的孩子,五个不听话的孩子。有一列火车,你现在撞死哪堆?好多人都在说孩子又不是绑在轨道上为什么不能跑。以前的我好像也是这样,老是答非所问。这个问题的核心在于救哪边而不是让你判断前提条件是否合法。

    还提到让座的问题,现在好像有不让座会被喷的情况。本人不是经常让座,因为做公交少,而且坐的时候也不一定旁边就有一个年迈的老人。让座本来是很开心的事,帮助了别人自己手里也有余香。如果现在变成了被迫,总会觉得心中有点不爽。

    但是无论如何,帮助别人总是善事。例如昨天去医院的路上,有个阿姨过路障要抬一下自行车,我就随手帮了一下,也没有说什么。这种事不会付出什么,只需要脸皮厚一点而已,却可以让两个人心中都感到舒服,我觉得很赚。

    下午看了部电影,大蜘蛛还是很帅的,虽然漂亮温柔身材好的女主Die了。晚上继续比赛,明天就要去青岛了。

    青岛之耻

    因为状态的问题输了有点可惜,下次赢回来!

    11.2

    昨晚躺在床上睡不着,又是那种感觉。在褪黑素的作用下双眼疲惫,欲睁却不开,然而思绪却很强烈,没想什么却很乱。犹如在观看视觉电影。
    最后也不知道挣扎了多久,终于睡去。
    早上起来后却不觉困意,可能深度睡眠的时间较长。
    约与11点半至机房,时间可以说很充分。吃早饭,洗衣服,准备行李。在准备衣物时有点两难,冲锋衣更保暖,但使双手笨拙,不便敲代码。秋季外套很适身,但怕不够保暖。最后考虑比赛现场较暖,还是选择了短袖加秋季外套。
    这个时候,想起来久置的shuffle。因为用手机放歌容易没电,也不方便。
    充了电,下载了歌单里的歌,更新了iTunes。
    吃了个外卖就去机房,和韩老师寒暄后便启程了。
    上火车后用shuffle听歌的时候震惊了,人妻的女声更近耳了,低音也深了很多,表现可谓脱胎换骨。所以对于稍微高级的耳机前端还是很重要的,平时用机房的电脑听真的是埋没了人妻。
    最后到了那个五星级酒店,很豪华。入住后我来讲讲房间的细节。墙上有很多镜子和壁画,灯设置为渐亮渐暗。沙发一般,但厕所设计不错,可移动壁镜,最让人感觉舒适的是浴室,糙大理石板,带横条凹槽,脚感十足,旁边有条集水槽,就不会有积水了。
    晚上12点准备睡觉,但是发现雾感器会闪,照亮一片犹如*片。最后决定开两个床灯睡觉。晚上容易醒,但醒来看着亮着的房间感觉心里很踏实。

    11.3

    早上9点起床,听说楼下有自助餐,就洗漱一下下去了。自助餐百元级别。接下来是去学校报到,红色带绒外套,有点担心明天会不会太热了,毕竟现在我身上只有一件短袖。
    中午正常吃饭,下午热身。晚上一起去海边逛了一群。和温岭一样没有沙滩的海,但没有高坝。海边有个类购物中心,随便逛了一下就回来了。回来玩手机,哎。

    11.4

    昨晚一点睡,早上7点起。9点比赛,下午两点打铁,over。假二分那题明明不是单调的,比赛的时候没有分析就上了。那个时候队友说的比较急,搞得我都没有分析正确性。回来发现问题改了一种方法,但是那个想出来的队友敲了个bug,毕竟那个队友昨晚失眠了4点多睡的。我也没注意到。比赛快结束了才过,三题打铁。原因有两个:睡眠问题,我没有很好地发挥带队作用。第四题我已经分析了做法的正确性,但是已经没时间敲了。
    没什么好说的,继续刷题。大三的时候赢回来。
    飞机晚点,到了杭州火车已经开走了。然后又是像上次那样,大半夜在街上走了好久。到了宾馆也没洗澡就睡了。

    11/5

    虽然没享受到得牌的喜悦,却还是要承受奔波的劳碌。7点地铁,8点高铁,到金华后出租,到学校后单车,到机房后拿了书直接赶到教室,还好没有迟到。
    为了不落下课程真的是拼。
    接下来是一大堆首尾工作,作业,补课…
    因为地铁,飞机和公交乘太多的原因,到现在脑袋还是晕晕的,感觉在左右慌。

    败转-寒假

    生病、考砸、生活荒废~

    12.5

    今天外婆做了个小手术,我早上本来想打电话问候一下的,后来忙别的去了就忘了,感觉怪怪的。
    每天立了一个小目标,不知道可以坚持多久。
    课题是做一个微信小程序,需要html,CSS,JavaScript。看来课题唯一的好处就是自学了吧。

    12.10

    日子过的很不充实,虽然没有浪费很多时间,但是作业等各种学校里的杂事占用了很多时间。(没时间好好敲代码总是可以找到好借口hahah)
    寒冬已至,万物归寂。寒风点缀细雨,刺痛一阵。不过有句话叫春捂秋冻,我这几个星期都是短袖卫衣加薄外套,秋季套装,从15度穿到2度。我感觉我可以穿秋天的衣服过冬了,比较南方冬天也就这样了。
    晚上学了一些指令方面的知识,虽然实用,但现阶段看来不会有什么回报,纯属兴趣罢了。

    12.13

    今天中午愉快的吃完饭,回来上了个厕所发现内出血了。虽然是老毛病,但是却是个姓名攸关的老毛病。我还记得上次输完液后,想去对面小卖铺买点吃的,因为失血过多险些发生车祸。整个人有种从天上掉下来,垮在地上的感觉。前一秒想的是如何活的更精彩,后一秒就成了怎么活着。
    早上还在想晚上去不去健身,哈哈,可能接下来很长时间不能。
    到医院后,和医生说了好久,说明了不能住院和检查的原因,最后就剩下输液了。
    但是就一个输液,因为有生长抑素(止血)和奥美拉挫(抑制胃酸)的原因,需要用注射泵,输液部不能直接输液。所以得去楼下那边开一个留院观察(床位),付完钱回去又和我说没有床位了。又回到楼下,由另外一个医生帮我先注射。然后
    有一个药不知道是已经注射到氯化钠溶液还是少了,有回去拿了瓶新的。最后重要是挂上了,回到二楼找了张床输液。
    开始的时候机器速度调成8ml/h,护士说没有必要,不过不知怎么的又或是我说了什么,后来还是8ml/h,据说那种吐血的就是这种速度。没过多久就吐了,好久没有吐了,挺难受的。于是乎调回了4ml/h。
    后来我妈来了,情况算好了一点。
    也是辛苦老妈了,做生意还要照顾我。

    展开全文
  • Activity ... 1. Activity 的创建 在程序包处单击右键,选择New → Activity → Empty Activity 后会弹出Customize the Activity界面 Activity Name :用于输入Activity名称。 Layout Name:用输入布...

    Activity

    用户与程序的交互是通过Activity完成的,Activity负责管理Android应用程序的登陆界面。

    1. Activity 的创建

    在程序包处单击右键,选择New → Activity → Empty Activity

    后会弹出Customize the Activity界面

    • Activity Name :用于输入Activity名称。
    • Layout Name:用输入布局名称。
    • Launcher Activity :复选框用于设置当前Activity是否为默认启动界面,通常情况下不勾选。
    • Package name :表示包名,默认即可。
    • Target Source set 表示目标源集,默认即可。
    • 点击Finish

    创建的文件代码如下

    package top.codebooks.meizu;
    import androidx.appcompat.app.AppCompatActivity;
    import android.os.Bundle;
    public class Activity extends AppCompatActivity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_);
        }
    }
    

    创建的Activuty 继承自 AppCompatActivity 并重写了该类的 onCreate() 方法,该方法时在Activity创建时调用,因此通常存放一些控件的初始化代码。

    在Android中,创建四大组件都需要在manifests/AndroidManifest.xml文件(清单文件)中注册,当Activity在清单文件中注册时,会添加一行<activity android:name="ActivityName"></activity> 代码。Android studio 高级IDE在创建Activity时会自动注册,当然如果在创建时进选择的是java类继承自AppCompatActivity,并需要在manifests/AndroidManifest.xml添加<activity android:name="ActivityName"></activity>

    2. Activaty 的生命周期

    Activity 的生命周期中包含了5中状态,涉及了7种方法.生命周期指的是一个Activity从创建到销毁的全过程。

    (1)生命周期的五个状态:
    • 启动状态:启动状态很短,启动后便会进入运行状态。
    • 运行状态:Activity在运行状态时处于屏幕的最前端,它是可见的有焦点的,可以与用户进行交互。当Activity处于运行状态时,Android会尽可能地保持它的运行,即使出现内存不足时,Android也会先销毁栈底的Activity来确保当前的Activity正常运行。
    • 暂停状态:某些情况下,Activity对用户来说仍然可见,但它无法获得焦点。对用户的操作也没有响应此时它就处于暂停状态。例如当前Activity被覆盖了一个透明或者非全屏的Activity时,被覆盖的Activity就处于暂停状态。
    • 停止状态:Activaty完全不可见时,他就处于停止状态,但仍保留着当前状态的和成员的信息,如果系统内存资源不足,那么这类状态很容易被销毁。
    • 销毁状态:当Activity位于此状态时,Activity会被清理出内存。

    Android生命周期模型

    在这里插入图片描述

    一个Activity 从启动到关闭,会依次执行onCreate() → onStart() → onResume() → onPause() → onStop() → onDestroy() 方法。当Activity执行到 onPause方法时Activity失去焦点后会到前台会执行onTrsume() 方法。如果此时进程被销毁。Activity会重新执行onCreate()方法。当执行到onStop()方法Activity不可见时再次会到前台会执行onRestart() 方法如果此时进程被销毁,会执行onCreate()方法。

    (2)生命周期的七个方法
    • onCreate() :在Avtivity创建时调用,通常做一些初始化的工作。
    • onStart():在Avtivity即将可见时调用。
    • onResume():在Actuvity获得焦点开始与用户交互式调用。
    • onPause():当前Avtivity被其他Activity覆盖或锁屏时调用。
    • onStop(): 在Activity对用户不可见时调用。
    • onRestart():在Activity从停止状态再次启动时调用。
    • onDestroy():在Activity销毁时调用。
    // 调试时在logcat输出控制信息
    package top.codebooks.meizu;
    
    import androidx.appcompat.app.AppCompatActivity;
    import android.os.Bundle;
    import android.util.Log;
    
    public class MainActivity extends AppCompatActivity{
        String TAG = "myTag";
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
    
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Log.i(TAG,"onCreate");
        }
    
        @Override
        protected void onStart() {
            super.onStart();
            Log.i(TAG, "onStart: ");
        }
    
        @Override
        protected void onRestart() {
            super.onRestart();
            Log.i(TAG, "onRestart: ");
        }
    
        @Override
        protected void onResume() {
            super.onResume();
            Log.i(TAG, "onResume: ");
        }
    
        @Override
        protected void onPause() {
            super.onPause();
            Log.i(TAG, "onPause: ");
        }
    
        @Override
        protected void onStop() {
            super.onStop();
            Log.i(TAG, "onStop: ");
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            Log.i(TAG, "onDestroy: ");
        }
    }
    

    程序中只有一个Activity时无法进行返回操作,当程序中有多个Activity进行切换时就可以进行切换操作,也就可以看到onRestart() 方法执行。

    • 横竖屏切换时的细节

    默认属性下,Avtivity的生命周期会依次调用 onCreate() → onStart() → onResume()方法,而进行横竖屏切换后调用的方法依次是onPause() → onStop() → onDestroy() → onCreate() → onStart() → onResume() ,所以我们可以在此生命周期内进行横竖屏切换时会首先销毁Activity,之后再重建Activity这种模式在开发中会有一定的影响。如果不希望在横竖屏切换时Activity被销毁重建,可以通过configChanges属性进行设置。这样无论怎样切换Ativity都不会被销毁重建。在manifests/AndroidManifest(清单文件)中修改。

    <activity android:name=".MainActivity"
              android:configChanges="orientation|keyboardHidden|screenSize">
    

    如果希望某一界面一直处于竖屏或则横屏状态不随手机的晃动而改变同样可以在清单文件中通过设置Activity的参数来完成

    竖屏:android:screenOrientation="portrait"
    横屏:android:screenOrientation="landscape"
    

    3.Activity的启动模式

    Activity是层叠摆放的,每启动一个新的Activity就会覆盖到原Activity之上。如果单击“返回”按钮,最上面的Activity就会被销毁。下面的Activity重新显示,Activity之所以能这样显示,是因为Android系统是通过任务栈方式来管理Activity实例的。

    (1)Android中的任务栈

    栈是一种先进后出的数据结构,Android任务栈又被称为返回栈,栈中Activity的顺序就是按照他们打开的顺序依次存放的。

    通常一个应用程序对应一个任务栈。默认情况下每一个启动的Activity都会入栈并位于栈顶位置。用户操作的永远都是栈顶的Activity在栈中的Activity只有入栈和出栈两种操作。被当前Activity启动时压入,用户单击返回按钮时出栈,而栈中的Activity的位置和循序都不会发生变化。

    (2)Activity的四种启动模式

    在实际开发中,应根据特定的需求为每个Activity指定适当的启动模式,Activity的启动模式共有4种。分别是standard、singleTop、singleTask、和singleInstance。在清单文件AndroidManifest中通过<activity>标签的android:launchModo 属性可以设置启动模式。

    • standard (标准启动)模式

      Activity的默认启动模式,每启动一个Activity就会在栈顶创建一个新的实例,在实际开发中,闹钟程序通常使用这种模式。

    在这里插入图片描述
    启动时创建的Aty1_1位于栈底,后创建的实例会堆放到栈顶, 当点返回键的时候,便会从栈顶按顺序将实例移除栈。直至栈为空则退回到桌面。

    • singleTop

      在standard模式下每次启动都会创建一个新的实例,即使当前要创建的Activity已经位于栈顶,不能直接复用,这样并不合理。所以提出了singleTop模式,

      该模式要判断启动的Aativity实例是否位于栈顶,如果位于栈顶则直接复用。否则创建新的实例。

      • 如果此时Aty1的实例在栈顶,此时再创建一个新的Aty1并不会成功。

    在这里插入图片描述

    • 如果Aty_1不在栈顶,此时再创建一个Aty1的实例,会产生一个新的Aty1实例到栈顶。

    在这里插入图片描述
    当点返回键的时候,便会从栈顶按顺序将实例移除栈。直至栈为空则退回到桌面。

    • singleTask

      singleTask很好的解决的重复创建栈顶的问题,但是如果Activity没有处于栈顶的位置,则很有可能会创建多个实例,为了让某个Activity在整个应用程序中只有一个实例,则需要借助singleTask来完成。

      Activity指定singleTask模式后,每次启动该Activity时,系统会先查找栈中是否存在当前的Activity如果存在则直接使用并将该Activity之上的所有实例全部出栈。否则才会创建一个新的实例。实力开发时浏览器主界面通常使用这种模式。

      如果此时Aty1的实例在栈顶,此时再创建一个新的Aty1并不会成功。
      在这里插入图片描述
      如果Aty_1不在栈顶,此时再创建一个Aty1的实例,页面会返回到任务栈中存在的Aty1的实例,并将Aty1上面所有的实例移出栈。
      在这里插入图片描述
      当点返回键的时候,便会从栈顶按顺序将实例移除栈。直至栈为空则退回到桌面

    • singleInstance

      singleInstance模式时四种启动模式中最特殊的一个,指定为singleInstance模式的Activity会启动一个新任务栈来管理Activity实例,无论从哪个任务栈启动该Activity,带实力在整个Android系统中只有一个,这种模式存在的意义,是为了在不同的程序之间共享一个Activity实例。

      Activity采用singleInstance模式启动通常有两种情况

      • 要启动的Activity不存在

        系统会新创建一个任务栈,然后在创建该Activity实例。

        此时Aty2的实例还未被创建,创建的Aty2的实例时被存放到一个新的任务栈中。

    在这里插入图片描述

    • 启动的Activity已存在

      无论当前Activity在那个程序那个任务栈,系统都会把Activity所在的任务栈移动到前台,从而使Activity显示。来电界面通常采用这种模式。

      此时Aty1的实例在栈顶,此时再创建一个新的Aty1并不会成功。

    在这里插入图片描述
    此时Aty1的实例已存在且我们在Aty2的页面中创建新的Aty1实例时,则不会创建新的Aty1实例,而是页面跳转到原来任务栈中存在的Aty1的实例,但存放Aty2实例的任务栈仍然存在
    在这里插入图片描述
    当点返回键的时候,便会依次销毁相应的任务栈,直至任务栈数为空则退回到系统桌面。

    4. Activity之间的跳转

    在Android系统里,应用程序通常都是由多个界面组成的,每一个界面就是一个Activity,在这些界面之间进行跳转时,实际上也就是Activity之间的跳转。Activity之间的跳转需要用到Intent(意图)组件,通过Intent可以开启新的Activity实现界面跳转功能。

    (1)Intent简介

    Intent被称为意图,是程序中各组件进行交互的一种重要方式,它不仅可以指定当前组件要执行的动作,还能再不同的组件之间进行数据的传递。一般用于启动Activity、Service以及发送个广播等。根据开启目标组件的不同,Intent被分为隐式意图和显式意图两种。

    • 显式意图

      ​显示意图可以直接通过名称来开启指定的目标组件,通过其构造方法Intent(ContextpackgeContext,Class<?>cls)来实现,其中第一个参数表示但分分钱的Activity对象,这里使用this即可。第二个参数Class表示要启动的目标Activity。通过这个方法创建一个Intent对象,然后将该对象传递给Activity的startActivity
      (Intent intent)方法即可启动目标组件。

      Intent intent = new Intent(this,Activity.class);		// 创建Intent对象
      startActivity(intent);								// 开启Activity
      
    • 隐式意图

      隐式意图比显式意图来说更为抽象。他并没有明确指明要开启那个目标组件,而是通过指定action和category等属性信息,系统根据这些信息进行分析,然后寻找目标Activity。

      Intent intent = new Intent();
      // 设置action动作,该动作要和清单文件中设置的一样。
      intent.setAction("cn.itcast.START_ACTIVITY");
      startActivity(intent);
      

      上面只是指定了action,并没有指定category,这是因为在目标Activity的清单文件中配置的category是一个默认值,在调用startActivity() 方法时自动将这个categroy添加到Intent中。

      但是,上述代码还不能开启指定的Activity还需在目标Activity的清单文件中配置<intent-filter>指定当前Activity能够响应action和category。

              <activity android:name="cn.itcast.Activity">
                  <intent-filter>
                  <!--设置action属性,需要在代码中所设置的name打开指定的组件-->
                      <action android:name="cn.itcast.START_ACTIVITY"/>
                      <category android:name="android.intent.category.DEFAULT"/>
                  </intent-filter>
              </activity>
      

      在清单文件中,目标组件Activity指定了可以响应的<action><categoty> 信息,只有当action 和 categoty属性与目标组件设置的内容相同时,目标组件才会被开启,注意:每个intent只能指定一个action,却能指定多个categoty。

    5.Activity 中的数据传递

    Intent 不仅可以来气Activity、Service、BroadcastReceiver 组件。还可以在这些组件之间传递数据。

    (1)数据传递

    Intent提供了一系列重载的 putExtra(String name,String values)方法。通过该方法可以将要传递的数据暂存在Intent中。当启动另一个Activity之后,只需要将这些数据从Intent取出即可。

    在Activity01中的字符串数据传递到Activity02中

    • 在Activity01 将数据传递到Intent中
    Intent intent = new Intentthis,Activity02.class;
    intent.putExtra("ectra_data","Hello Activity02");
    startActivity(intent);
    

    putExtra();方法中有两个参数,第一个参数是键,用于后面从Intent中取值;第二个参数是传递的数据内容。

    • 在Activity02中取出传递过来的值。
    Intent intent = getIntent();
    String data = intent.getStringExtra("extra_data");
    

    通过getIntent方法获取Intent对象,然后调用getStringExtra(String name)根据传入的名称获取值,若要获取其他类型的值,如数字可以用getIntExtra(String name) 方法。

    (2)数据回传

    Intent除了可以将数据传递给下一个Activity,还可以将数据传递给上一个Activity。例如:在微信发朋友圈时,进入图库选择好照片后,会返回到发表状态的页面并带回所选的图片信息。

    Activity中有一个方法startActivityForResult(Intent intent,int requestCode)方法。该方法可以用于启动Activity,并且可以在当前Avtivity销毁时返回一个结果给上一个Activity。

    • startActivityForResult(Intent intent,int requestCode)

      参数(2个):第一个参数接收一个 Intent 对象,第二个参数时请求吗,用于判断数据的来源,输入一个唯一值即可。

    在Activity01中开启Activity02

    Intent intent = new Intent(this,Activity02.class);
    startActivityForResult(intent,1);
    

    在Activity02中添加返回数据的实例代码

    Intent intent = new Intent();
    intent.putExtra("extra_data","Hello Acyivity01");
    setResult(1,intent);
    

    在上面的代码里仍然构建了一个对象,然后调用setResult(int resultCode,Intent data)方法向上一个Activity回传数据,其中第一个参数用于处理回传的结果,一般使用0或者1,第二个参数是把带有数据的Intent传递回去。

    由于在Activity01中使用了startActivityForResult(Intent intent,int requestCode)方法启动了Activity02,在Activity02被销毁后会调用Activity01的onActivityResult()方法,因此要在Activity01中重写该方法来得到返回的数据。

        protected void onActivityResule(int requestCode, int resultCode, Intent data){
            super.onActivityResult(requestCode,resultCode,data);
            if(requestCode == 1){
                if(resultCode == 1){
                    String string = data.getStringExtra("extra_data");
                }
            }
        }
    

    在Activity01中,通过onActivityResule(int requestCode, int resultCode, Intent data)方法来获取数据,该方法有三个参数。

    • int requestCode:当前Activity01启动Activity02时的请求码。
    • int resultCode:在ACtivity返回数据时的传入结果码。
    • Intent data:携带返回数据的 Intent。

    注意:在一个Activity中可能会调用startActivityForResult(Intent intent,int requestCode)方法启动多个不同的Activity,每一个Activity返回的数据都会回调到onActivityResule(int requestCode, int resultCode, Intent data)这个方法中,因此首先要做的事通过检查requstCode来判断数据的来源,确定是从那个Activity返回的,然后通过resultCode的值来判断数据处理结果是否成功。最后才能从data中取出数据。

    展开全文
  • 从理论到实践,快速入门Spring Boot 2.x 从Spring Boot扩展到Spring相关知识的学习和实战 Spring Boot与其他技术框架的集成 Spring Boot部分功能源码解析
  • JAVA 并发知识库 JAVA 线程实现/创建方式 继承 Thread 类 Thread 类本质上是实现了 Runnable 接口的一个实例,代表一个线程的实例。启动线程的唯一方 法就是通过 Thread 类的 start()实例方法。...

    在这里插入图片描述

    JAVA 并发知识库

    在这里插入图片描述
    JAVA 线程实现/创建方式
    继承 Thread 类
    Thread 类本质上是实现了 Runnable 接口的一个实例,代表一个线程的实例。启动线程的唯一方
    法就是通过 Thread 类的 start()实例方法。start()方法是一个 native 方法,它将启动一个新线
    程,并执行 run()方法。

    public class MyThread extends Thread { 
     public void run() { 
     System.out.println("MyThread.run()"); 
     } 
    } 
    MyThread myThread1 = new MyThread(); 
    myThread1.start();
    

    实现 Runnable 接口。
    如果自己的类已经 extends 另一个类,就无法直接 extends Thread,此时,可以实现一个
    Runnable 接口。

    public class MyThread extends OtherClass implements Runnable { 
     public void run() { 
     System.out.println("MyThread.run()"); 
     } 
    }
    //启动 MyThread,需要首先实例化一个 Thread,并传入自己的 MyThread 实例:
    MyThread myThread = new MyThread(); 
    Thread thread = new Thread(myThread); 
    thread.start(); 
    //事实上,当传入一个 Runnable target 参数给 Thread 后,Thread 的 run()方法就会调用
    target.run()
    public void run() { 
     if (target != null) { 
     target.run(); 
     } 
    }
    

    ExecutorService、Callable、Future 有返回值线程
    有返回值的任务必须实现 Callable 接口,类似的,无返回值的任务必须 Runnable 接口。执行
    Callable 任务后,可以获取一个 Future 的对象,在该对象上调用 get 就可以获取到 Callable 任务
    返回的 Object 了,再结合线程池接口 ExecutorService 就可以实现传说中有返回结果的多线程
    了。

    //创建一个线程池
    ExecutorService pool = Executors.newFixedThreadPool(taskSize);
    // 创建多个有返回值的任务
    List<Future> list = new ArrayList<Future>(); 
    for (int i = 0; i < taskSize; i++) { 
    Callable c = new MyCallable(i + " "); 
    // 执行任务并获取 Future 对象
    Future f = pool.submit(c); 
    list.add(f); 
    } 
    // 关闭线程池
    pool.shutdown(); 
    // 获取所有并发任务的运行结果
    for (Future f : list) { 
    // 从 Future 对象上获取任务的返回值,并输出到控制台
    System.out.println("res:" + f.get().toString()); 
    }
    

    基于线程池的方式
    线程和数据库连接这些资源都是非常宝贵的资源。那么每次需要的时候创建,不需要的时候销
    毁,是非常浪费资源的。那么我们就可以使用缓存的策略,也就是使用线程池。

     // 创建线程池
     ExecutorService threadPool = Executors.newFixedThreadPool(10);
     while(true) {
     threadPool.execute(new Runnable() { // 提交多个线程任务,并执行
     @Override
     public void run() {
     System.out.println(Thread.currentThread().getName() + " is running ..");
     try {
     Thread.sleep(3000);
     } catch (InterruptedException e) {
     e.printStackTrace();
     }
     }
     });
     } }
    

    种线程池
    Java 里面线程池的顶级接口是 Executor,但是严格意义上讲 Executor 并不是一个线程池,而
    只是一个执行线程的工具。真正的线程池接口是 ExecutorService。
    newCachedThreadPool
    创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。对于执行
    很多短期异步任务的程序而言,这些线程池通常可提高程序性能。调用 execute 将重用以前构造
    的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并
    从缓存中移除那些已有 60 秒钟未被使用的线程。因此,长时间保持空闲的线程池不会使用任何资
    源。
    newFixedThreadPool
    创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。在任意点,在大
    多数 nThreads 线程会处于处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务,
    则在有可用线程之前,附加任务将在队列中等待。如果在关闭前的执行期间由于失败而导致任何
    线程终止,那么一个新线程将代替它执行后续的任务(如果需要)。在某个线程被显式地关闭之
    前,池中的线程将一直存在。
    newScheduledThreadPool
    创建一个线程池,它可安排在给定延迟后运行命令或者定期。

     ScheduledExecutorService scheduledThreadPool= Executors.newScheduledThreadPool(3); 
     scheduledThreadPool.schedule(newRunnable(){ 
     @Override 
     public void run() {
     System.out.println("延迟三秒");
     }
     }, 3, TimeUnit.SECONDS);
    scheduledThreadPool.scheduleAtFixedRate(newRunnable(){ 
     @Override 
     public void run() {
     System.out.println("延迟 1 秒后每三秒执行一次");
     }
     },1,3,TimeUnit.SECONDS);
    

    newSingleThreadExecutor
    Executors.newSingleThreadExecutor()返回一个线程池(这个线程池只有一个线程),这个线程
    池可以在线程死后(或发生异常时)重新启动一个线程来替代原来的线程继续执行下去!
    线程生命周期(状态)
    当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。
    在线程的生命周期中,它要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞
    (Blocked)和死亡(Dead)5 种状态。尤其是当线程启动以后,它不可能一直"霸占"着 CPU 独自
    运行,所以 CPU 需要在多条线程之间切换,于是线程状态也会多次在运行、阻塞之间切换
    新建状态(NEW)
    当程序使用 new 关键字创建了一个线程之后,该线程就处于新建状态,此时仅由 JVM 为其分配
    内存,并初始化其成员变量的值。
    就绪状态(RUNNABLE):
    当线程对象调用了 start()方法之后,该线程处于就绪状态。Java 虚拟机会为其创建方法调用栈和
    程序计数器,等待调度运行。
    运行状态(RUNNING):
    如果处于就绪状态的线程获得了 CPU,开始执行 run()方法的线程执行体,则该线程处于运行状
    态。
    阻塞状态(BLOCKED):
    阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice,暂时停止运行。
    直到线程进入可运行(runnable)状态,才有机会再次获得 cpu timeslice 转到运行(running)状
    态。阻塞的情况分三种:
    等待阻塞(o.wait->等待对列):
    运行(running)的线程执行 o.wait()方法,JVM 会把该线程放入等待队列(waitting queue)
    中。
    同步阻塞(lock->锁池)
    运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线
    程放入锁池(lock pool)中。
    其他阻塞(sleep/join)
    运行(running)的线程执行 Thread.sleep(long ms)或 t.join()方法,或者发出了 I/O 请求时,
    JVM 会把该线程置为阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O
    处理完毕时,线程重新转入可运行(runnable)状态。
    线程死亡(DEAD)
    线程会以下面三种方式结束,结束后就是死亡状态。
    正常结束

    1. run()或 call()方法执行完成,线程正常结束。
      异常结束
    2. 线程抛出一个未捕获的 Exception 或 Error。
      调用 stop
    3. 直接调用该线程的 stop()方法来结束该线程—该方法通常容易导致死锁,不推荐使用。
    4. 在这里插入图片描述
    5. 终止线程 4 种方式
      正常运行结束
      程序运行结束,线程自动结束。
      使用退出标志退出线程
      一般 run()方法执行完,线程就会正常结束,然而,常常有些线程是伺服线程。它们需要长时间的
      运行,只有在外部某些条件满足的情况下,才能关闭这些线程。使用一个变量来控制循环,例如:
      最直接的方法就是设一个 boolean 类型的标志,并通过设置这个标志为 true 或 false 来控制 while
      循环是否退出,代码示例:
    public class ThreadSafe extends Thread {
     public volatile boolean exit = false; 
     public void run() { 
     while (!exit){
     //do something
     }
     } 
    }
    

    定义了一个退出标志 exit,当 exit 为 true 时,while 循环退出,exit 的默认值为 false.在定义 exit
    时,使用了一个 Java 关键字 volatile,这个关键字的目的是使 exit 同步,也就是说在同一时刻只
    能由一个线程来修。
    Interrupt 方法结束线程
    使用 interrupt()方法来中断线程有两种情况:
    6. 线程处于阻塞状态:如使用了 sleep,同步锁的 wait,socket 中的 receiver,accept 等方法时,
    会使线程处于阻塞状态。当调用线程的 interrupt()方法时,会抛出 InterruptException 异常。
    阻塞中的那个方法抛出这个异常,通过代码捕获该异常,然后 break 跳出循环状态,从而让
    我们有机会结束这个线程的执行。通常很多人认为只要调用 interrupt 方法线程就会结束,实
    际上是错的, 一定要先捕获 InterruptedException 异常之后通过 break 来跳出循环,才能正
    常结束 run 方法。
    7. 线程未处于阻塞状态:使用 isInterrupted()判断线程的中断标志来退出循环。当使用
    interrupt()方法时,中断标志就会置 true,和使用自定义的标志来控制循环是一样的道理。

     public class ThreadSafe extends Thread {
     public void run() { 
     while (!isInterrupted()){ //非阻塞过程中通过判断中断标志来退出
     try{
     Thread.sleep(5*1000);//阻塞过程捕获中断异常来退出
     }catch(InterruptedException e){
     e.printStackTrace();
     break;//捕获到异常之后,执行 break 跳出循环
     }
     }
     } 
    }
    

    stop 方法终止线程(线程不安全)
    程序中可以直接使用 thread.stop()来强行终止线程,但是 stop 方法是很危险的,就象突然关
    闭计算机电源,而不是按正常程序关机一样,可能会产生不可预料的结果,不安全主要是:
    thread.stop()调用之后,创建子线程的线程就会抛出 ThreadDeatherror 的错误,并且会释放子
    线程所持有的所有锁。一般任何进行加锁的代码块,都是为了保护数据的一致性,如果在调用
    thread.stop()后导致了该线程所持有的所有锁的突然释放(不可控制),那么被保护数据就有可能呈
    现不一致性,其他线程在使用这些被破坏的数据时,有可能导致一些很奇怪的应用程序错误。因
    此,并不推荐使用 stop 方法来终止线程。
    sleep 与 wait 区别
    8. 对于 sleep()方法,我们首先要知道该方法是属于 Thread 类中的。而 wait()方法,则是属于
    Object 类中的。
    9. sleep()方法导致了程序暂停执行指定的时间,让出 cpu 该其他线程,但是他的监控状态依然
    保持者,当指定的时间到了又会自动恢复运行状态。
    10. 在调用 sleep()方法的过程中,线程不会释放对象锁。
    11. 而当调用 wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此
    对象调用 notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。
    start 与 run 区别
    12. start()方法来启动线程,真正实现了多线程运行。这时无需等待 run 方法体代码执行完毕,
    可以直接继续执行下面的代码。
    13. 通过调用 Thread 类的 start()方法来启动一个线程, 这时此线程是处于就绪状态, 并没有运
    行。
    14. 方法 run()称为线程体,它包含了要执行的这个线程的内容,线程就进入了运行状态,开始运
    行 run 函数当中的代码。 Run 方法运行结束, 此线程终止。然后 CPU 再调度其它线程。
    JAVA 后台线程
    15. 定义:守护线程–也称“服务线程”,他是后台线程,它有一个特性,即为用户线程 提供 公
    共服务,在没有用户线程可服务时会自动离开。
    16. 优先级:守护线程的优先级比较低,用于为系统中的其它对象和线程提供服务。
    17. 设置:通过 setDaemon(true)来设置线程为“守护线程”;将一个用户线程设置为守护线程
    的方式是在 线程对象创建 之前 用线程对象的 setDaemon 方法。
    18. 在 Daemon 线程中产生的新线程也是 Daemon 的。
    19. 线程则是 JVM 级别的,以 Tomcat 为例,如果你在 Web 应用中启动一个线程,这个线程的
    生命周期并不会和 Web 应用程序保持同步。也就是说,即使你停止了 Web 应用,这个线程
    依旧是活跃的。
    20. example: 垃圾回收线程就是一个经典的守护线程,当我们的程序中不再有任何运行的Thread,
    程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是 JVM 上仅剩的线
    程时,垃圾回收线程会自动离开。它始终在低级别的状态中运行,用于实时监控和管理系统
    中的可回收资源。
    21. 生命周期:守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周
    期性地执行某种任务或等待处理某些发生的事件。也就是说守护线程不依赖于终端,但是依
    赖于系统,与系统“同生共死”。当 JVM 中所有的线程都是守护线程的时候,JVM 就可以退
    出了;如果还有一个或以上的非守护线程则 JVM 不会退出。
    JAVA 锁
    乐观锁
    乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为
    别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数
    据,采取在写时先读出当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样则更新),
    如果失败则要重复读-比较-写的操作。
    java 中的乐观锁基本都是通过 CAS 操作实现的,CAS 是一种更新的原子操作,比较当前值跟传入
    值是否一样,一样则更新,否则失败。
    悲观锁
    悲观锁是就是悲观思想,即认为写多,遇到并发写的可能性高,每次去拿数据的时候都认为别人
    会修改,所以每次在读写数据的时候都会上锁,这样别人想读写这个数据就会 block 直到拿到锁。
    java中的悲观锁就是Synchronized,AQS框架下的锁则是先尝试cas乐观锁去获取锁,获取不到,
    才会转换为悲观锁,如 RetreenLock。
    自旋锁
    自旋锁原理非常简单,如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁
    的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),
    等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。
    线程自旋是需要消耗 cup 的,说白了就是让 cup 在做无用功,如果一直获取不到锁,那线程
    也不能一直占用 cup 自旋做无用功,所以需要设定一个自旋等待的最大时间。
    如果持有锁的线程执行的时间超过自旋等待的最大时间扔没有释放锁,就会导致其它争用锁
    的线程在最大等待时间内还是获取不到锁,这时争用线程会停止自旋进入阻塞状态。
    自旋锁的优缺点
    自旋锁尽可能的减少线程的阻塞,这对于锁的竞争不激烈,且占用锁时间非常短的代码块来
    说性能能大幅度的提升,因为自旋的消耗会小于线程阻塞挂起再唤醒的操作的消耗,这些操作会
    导致线程发生两次上下文切换!
    但是如果锁的竞争激烈,或者持有锁的线程需要长时间占用锁执行同步块,这时候就不适合
    使用自旋锁了,因为自旋锁在获取锁前一直都是占用 cpu 做无用功,占着 XX 不 XX,同时有大量
    线程在竞争一个锁,会导致获取锁的时间很长,线程自旋的消耗大于线程阻塞挂起操作的消耗,
    其它需要 cup 的线程又不能获取到 cpu,造成 cpu 的浪费。所以这种情况下我们要关闭自旋锁;
    自旋锁时间阈值(1.6 引入了适应性自旋锁)
    自旋锁的目的是为了占着 CPU 的资源不释放,等到获取到锁立即进行处理。但是如何去选择
    自旋的执行时间呢?如果自旋执行时间太长,会有大量的线程处于自旋状态占用 CPU 资源,进而
    会影响整体系统的性能。因此自旋的周期选的额外重要!
    JVM 对于自旋周期的选择,jdk1.5 这个限度是一定的写死的,在 1.6 引入了适应性自旋锁,适应
    性自旋锁意味着自旋的时间不在是固定的了,而是由前一次在同一个锁上的自旋时间以及锁的拥
    有者的状态来决定,基本认为一个线程上下文切换的时间是最佳的一个时间,同时 JVM 还针对当
    前 CPU 的负荷情况做了较多的优化,如果平均负载小于 CPUs 则一直自旋,如果有超过(CPUs/2)
    个线程正在自旋,则后来线程直接阻塞,如果正在自旋的线程发现 Owner 发生了变化则延迟自旋
    时间(自旋计数)或进入阻塞,如果 CPU 处于节电模式则停止自旋,自旋时间的最坏情况是 CPU
    的存储延迟(CPU A 存储了一个数据,到 CPU B 得知这个数据直接的时间差),自旋时会适当放
    弃线程优先级之间的差异。
    自旋锁的开启
    JDK1.6 中-XX:+UseSpinning 开启;
    -XX:PreBlockSpin=10 为自旋次数;
    JDK1.7 后,去掉此参数,由 jvm 控制;
    Synchronized 同步锁
    synchronized 它可以把任意一个非 NULL 的对象当作锁。他属于独占式的悲观锁,同时属于可重
    入锁。
    Synchronized 作用范围
    22. 作用于方法时,锁住的是对象的实例(this);
    23. 当作用于静态方法时,锁住的是Class实例,又因为Class的相关数据存储在永久带PermGen
    (jdk1.8 则是 metaspace),永久带是全局共享的,因此静态方法锁相当于类的一个全局锁,
    会锁所有调用该方法的线程;
    24. synchronized 作用于一个对象实例时,锁住的是所有以该对象为锁的代码块。它有多个队列,
    当多个线程一起访问某个对象监视器的时候,对象监视器会将这些线程存储在不同的容器中。
    Synchronized 核心组件

    1. Wait Set:哪些调用 wait 方法被阻塞的线程被放置在这里;
    2. Contention List:竞争队列,所有请求锁的线程首先被放在这个竞争队列中;
    3. Entry List:Contention List 中那些有资格成为候选资源的线程被移动到 Entry List 中;
    4. OnDeck:任意时刻,最多只有一个线程正在竞争锁资源,该线程被成为 OnDeck;
    5. Owner:当前已经获取到所资源的线程被称为 Owner;
    6. !Owner:当前释放锁的线程。
      Synchronized 实现
      在这里插入图片描述
    1. JVM 每次从队列的尾部取出一个数据用于锁竞争候选者(OnDeck),但是并发情况下,
      ContentionList 会被大量的并发线程进行 CAS 访问,为了降低对尾部元素的竞争,JVM 会将
      一部分线程移动到 EntryList 中作为候选竞争线程。
    2. Owner 线程会在 unlock 时,将 ContentionList 中的部分线程迁移到 EntryList 中,并指定
      EntryList 中的某个线程为 OnDeck 线程(一般是最先进去的那个线程)。
    3. Owner 线程并不直接把锁传递给 OnDeck 线程,而是把锁竞争的权利交给 OnDeck,
      OnDeck 需要重新竞争锁。这样虽然牺牲了一些公平性,但是能极大的提升系统的吞吐量,在
      JVM 中,也把这种选择行为称之为“竞争切换”。
    4. OnDeck 线程获取到锁资源后会变为 Owner 线程,而没有得到锁资源的仍然停留在 EntryList
      中。如果 Owner 线程被 wait 方法阻塞,则转移到 WaitSet 队列中,直到某个时刻通过 notify
      或者 notifyAll 唤醒,会重新进去 EntryList 中。
    5. 处于 ContentionList、EntryList、WaitSet 中的线程都处于阻塞状态,该阻塞是由操作系统
      来完成的(Linux 内核下采用 pthread_mutex_lock 内核函数实现的)。
    6. Synchronized 是非公平锁。 Synchronized 在线程进入 ContentionList 时,等待的线程会先
      尝试自旋获取锁,如果获取不到就进入 ContentionList,这明显对于已经进入队列的线程是
      不公平的,还有一个不公平的事情就是自旋获取锁的线程还可能直接抢占 OnDeck 线程的锁
      资源。
    7. 每个对象都有个 monitor 对象,加锁就是在竞争 monitor 对象,代码块加锁是在前后分别加
      上 monitorenter 和 monitorexit 指令来实现的,方法加锁是通过一个标记位来判断的
    8. synchronized 是一个重量级操作,需要调用操作系统相关接口,性能是低效的,有可能给线
      程加锁消耗的时间比有用操作消耗的时间更多。
    9. Java1.6,synchronized 进行了很多的优化,有适应自旋、锁消除、锁粗化、轻量级锁及偏向
      锁等,效率有了本质上的提高。在之后推出的 Java1.7 与 1.8 中,均对该关键字的实现机理做
      了优化。引入了偏向锁和轻量级锁。都是在对象头中有标记位,不需要经过操作系统加锁。
    10. 锁可以从偏向锁升级到轻量级锁,再升级到重量级锁。这种升级过程叫做锁膨胀;
    11. JDK 1.6 中默认是开启偏向锁和轻量级锁,可以通过-XX:-UseBiasedLocking 来禁用偏向锁。

    ReentrantLock
    ReentantLock 继承接口 Lock 并实现了接口中定义的方法,他是一种可重入锁,除了能完
    成 synchronized 所能完成的所有工作外,还提供了诸如可响应中断锁、可轮询锁请求、定时锁等
    避免多线程死锁的方法。
    Lock 接口的主要方法

    1. void lock(): 执行此方法时, 如果锁处于空闲状态, 当前线程将获取到锁. 相反, 如果锁已经
      被其他线程持有, 将禁用当前线程, 直到当前线程获取到锁.
    2. boolean tryLock():如果锁可用, 则获取锁, 并立即返回 true, 否则返回 false. 该方法和
      lock()的区别在于, tryLock()只是"试图"获取锁, 如果锁不可用, 不会导致当前线程被禁用,
      当前线程仍然继续往下执行代码. 而 lock()方法则是一定要获取到锁, 如果锁不可用, 就一
      直等待, 在未获得锁之前,当前线程并不继续向下执行.
    3. void unlock():执行此方法时, 当前线程将释放持有的锁. 锁只能由持有者释放, 如果线程
      并不持有锁, 却执行该方法, 可能导致异常的发生.
    4. Condition newCondition():条件对象,获取等待通知组件。该组件和当前的锁绑定,
      当前线程只有获取了锁,才能调用该组件的 await()方法,而调用后,当前线程将缩放锁。
    5. getHoldCount() :查询当前线程保持此锁的次数,也就是执行此线程执行 lock 方法的次
      数。
    6. getQueueLength():返回正等待获取此锁的线程估计数,比如启动 10 个线程,1 个
      线程获得锁,此时返回的是 9
    7. getWaitQueueLength:(Condition condition)返回等待与此锁相关的给定条件的线
      程估计数。比如 10 个线程,用同一个 condition 对象,并且此时这 10 个线程都执行了
      condition 对象的 await 方法,那么此时执行此方法返回 10
    8. hasWaiters(Condition condition):查询是否有线程等待与此锁有关的给定条件
      (condition),对于指定 contidion 对象,有多少线程执行了 condition.await 方法
    9. hasQueuedThread(Thread thread):查询给定线程是否等待获取此锁
    10. hasQueuedThreads():是否有线程等待此锁
    11. isFair():该锁是否公平锁
    12. isHeldByCurrentThread(): 当前线程是否保持锁锁定,线程的执行 lock 方法的前后分
      别是 false 和 true
    13. isLock():此锁是否有任意线程占用
    14. lockInterruptibly():如果当前线程未被中断,获取锁
    15. tryLock():尝试获得锁,仅在调用时锁未被线程占用,获得锁
    16. tryLock(long timeout TimeUnit unit):如果锁在给定等待时间内没有被另一个线程保持,
      则获取该锁。
      非公平锁
      JVM 按随机、就近原则分配锁的机制则称为不公平锁,ReentrantLock 在构造函数中提供了
      是否公平锁的初始化方式,默认为非公平锁。非公平锁实际执行的效率要远远超出公平锁,除非
      程序有特殊需要,否则最常用非公平锁的分配机制。

    公平锁
    公平锁指的是锁的分配机制是公平的,通常先对锁提出获取请求的线程会先被分配到锁,
    ReentrantLock 在构造函数中提供了是否公平锁的初始化方式来定义公平锁。
    ReentrantLock 与 synchronized

    1. ReentrantLock 通过方法 lock()与 unlock()来进行加锁与解锁操作,与 synchronized 会 被 JVM 自动解锁机制不同,ReentrantLock 加锁后需要手动进行解锁。为了避免程序出
      现异常而无法正常解锁的情况,使用 ReentrantLock 必须在 finally 控制块中进行解锁操
      作。
    2. ReentrantLock 相比 synchronized 的优势是可中断、公平锁、多个锁。这种情况下需要
      使用 ReentrantLock

    ReentrantLock 实现

    public class MyService {
     private Lock lock = new ReentrantLock();
    //Lock lock=new ReentrantLock(true);//公平锁
     //Lock lock=new ReentrantLock(false);//非公平锁
     private Condition condition=lock.newCondition();//创建 Condition
     public void testMethod() {
     try {
     lock.lock();//lock 加锁
    //1:wait 方法等待:
     //System.out.println("开始 wait");
     condition.await();
    //通过创建 Condition 对象来使线程 wait,必须先执行 lock.lock 方法获得锁
    //:2:signal 方法唤醒
    condition.signal();//condition 对象的 signal 方法可以唤醒 wait 线程
     for (int i = 0; i < 5; i++) {
    System.out.println("ThreadName=" + Thread.currentThread().getName()+ (" " + (i + 1)));
     }
     } catch (InterruptedException e) {
     e.printStackTrace();
     }
     finally
     {
     lock.unlock();
     }
     } }
    

    Condition 类和 Object 类锁方法区别区别

    1. Condition 类的 awiat 方法和 Object 类的 wait 方法等效
    2. Condition 类的 signal 方法和 Object 类的 notify 方法等效
    3. Condition 类的 signalAll 方法和 Object 类的 notifyAll 方法等效
    4. ReentrantLock 类可以唤醒指定条件的线程,而 object 的唤醒是随机的
      tryLock 和 lock 和 lockInterruptibly 的区别
    5. tryLock 能获得锁就返回 true,不能就立即返回 false,tryLock(long timeout,TimeUnit
      unit),可以增加时间限制,如果超过该时间段还没获得锁,返回 false
    6. lock 能获得锁就返回 true,不能的话一直等待获得锁
    7. lock 和 lockInterruptibly,如果两个线程分别执行这两个方法,但此时中断这两个线程,
      lock 不会抛出异常,而 lockInterruptibly 会抛出异常。
      Semaphore 信号量
      Semaphore 是一种基于计数的信号量。它可以设定一个阈值,基于此,多个线程竞争获取许可信
      号,做完自己的申请后归还,超过阈值后,线程申请许可信号将会被阻塞。Semaphore 可以用来
      构建一些对象池,资源池之类的,比如数据库连接池
      实现互斥锁(计数器为 1)
      我们也可以创建计数为 1 的 Semaphore,将其作为一种类似互斥锁的机制,这也叫二元信号量,
      表示两种互斥状态。
      代码实现
      它的用法如下:
    // 创建一个计数阈值为 5 的信号量对象
    // 只能 5 个线程同时访问
    Semaphore semp = new Semaphore(5);
    try { // 申请许可
    semp.acquire();
    try {
    // 业务逻辑
    } catch (Exception e) {
    } finally {
    // 释放许可
    semp.release();
    }
    } catch (InterruptedException e) {
    }
    

    Semaphore 与 ReentrantLock
    Semaphore 基本能完成 ReentrantLock 的所有工作,使用方法也与之类似,通过 acquire()与
    release()方法来获得和释放临界资源。经实测,Semaphone.acquire()方法默认为可响应中断锁,
    与 ReentrantLock.lockInterruptibly()作用效果一致,也就是说在等待临界资源的过程中可以被
    Thread.interrupt()方法中断。
    此外,Semaphore 也实现了可轮询的锁请求与定时锁的功能,除了方法名 tryAcquire 与 tryLock
    不同,其使用方法与 ReentrantLock 几乎一致。Semaphore 也提供了公平与非公平锁的机制,也
    可在构造函数中进行设定。
    Semaphore 的锁释放操作也由手动进行,因此与 ReentrantLock 一样,为避免线程因抛出异常而
    无法正常释放锁的情况发生,释放锁的操作也必须在 finally 代码块中完成。
    AtomicInteger
    首先说明,此处 AtomicInteger ,一个提供原子操作的 Integer 的类,常见的还有
    AtomicBoolean、AtomicInteger、AtomicLong、AtomicReference 等,他们的实现原理相同,
    区别在与运算对象类型的不同。令人兴奋地,还可以通过 AtomicReference将一个对象的所
    有操作转化成原子操作。
    我们知道,在多线程程序中,诸如++i 或 i++等运算不具有原子性,是不安全的线程操作之一。
    通常我们会使用 synchronized 将该操作变成一个原子操作,但 JVM 为此类操作特意提供了一些
    同步类,使得使用更方便,且使程序运行效率变得更高。通过相关资料显示,通常AtomicInteger
    的性能是 ReentantLock 的好几倍。
    可重入锁(递归锁)
    本文里面讲的是广义上的可重入锁,而不是单指 JAVA 下的 ReentrantLock。可重入锁,也叫
    做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受
    影响。在 JAVA 环境下 ReentrantLock 和 synchronized 都是 可重入锁。
    公平锁与非公平锁
    公平锁(Fair)
    加锁前检查是否有排队等待的线程,优先排队等待的线程,先来先得
    非公平锁(Nonfair)
    加锁时不考虑排队等待问题,直接尝试获取锁,获取不到自动到队尾等待
    8. 非公平锁性能比公平锁高 5~10 倍,因为公平锁需要在多核的情况下维护一个队列
    9. Java 中的 synchronized 是非公平锁,ReentrantLock 默认的 lock()方法采用的是非公平锁。

    ReadWriteLock 读写锁
    为了提高性能,Java 提供了读写锁,在读的地方使用读锁,在写的地方使用写锁,灵活控制,如
    果没有写锁的情况下,读是无阻塞的,在一定程度上提高了程序的执行效率。读写锁分为读锁和写
    锁,多个读锁不互斥,读锁与写锁互斥,这是由 jvm 自己控制的,你只要上好相应的锁即可。
    读锁
    如果你的代码只读数据,可以很多人同时读,但不能同时写,那就上读锁
    写锁
    如果你的代码修改数据,只能有一个人在写,且不能同时读取,那就上写锁。总之,读的时候上
    读锁,写的时候上写锁!
    Java 中读写锁有个接口 java.util.concurrent.locks.ReadWriteLock ,也有具体的实现
    ReentrantReadWriteLock。
    共享锁和独占锁
    java 并发包提供的加锁模式分为独占锁和共享锁。
    独占锁
    独占锁模式下,每次只能有一个线程能持有锁,ReentrantLock 就是以独占方式实现的互斥锁。
    独占锁是一种悲观保守的加锁策略,它避免了读/读冲突,如果某个只读线程获取锁,则其他读线
    程都只能等待,这种情况下就限制了不必要的并发性,因为读操作并不会影响数据的一致性。
    共享锁
    共享锁则允许多个线程同时获取锁,并发访问 共享资源,如:ReadWriteLock。共享锁则是一种
    乐观锁,它放宽了加锁策略,允许多个执行读操作的线程同时访问共享资源。

    1. AQS 的内部类 Node 定义了两个常量 SHARED 和 EXCLUSIVE,他们分别标识 AQS 队列中等
      待线程的锁获取模式。
    2. java 的并发包中提供了 ReadWriteLock,读-写锁。它允许一个资源可以被多个读操作访问,
      或者被一个 写操作访问,但两者不能同时进行。
      重量级锁(Mutex Lock)
      Synchronized 是通过对象内部的一个叫做监视器锁(monitor)来实现的。但是监视器锁本质又
      是依赖于底层的操作系统的 Mutex Lock 来实现的。而操作系统实现线程之间的切换这就需要从用
      户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么
      Synchronized 效率低的原因。因此,这种依赖于操作系统 Mutex Lock 所实现的锁我们称之为
      “重量级锁”。JDK 中对 Synchronized 做的种种优化,其核心都是为了减少这种重量级锁的使用。
      JDK1.6 以后,为了减少获得锁和释放锁所带来的性能消耗,提高性能,引入了“轻量级锁”和
      “偏向锁”。
      轻量级锁
      锁的状态总共有四种:无锁状态、偏向锁、轻量级锁和重量级锁。
      锁升级
      随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁(但是锁的升级是单向的,
      也就是说只能从低到高升级,不会出现锁的降级)。
      “轻量级”是相对于使用操作系统互斥量来实现的传统锁而言的。但是,首先需要强调一点的是,
      轻量级锁并不是用来代替重量级锁的,它的本意是在没有多线程竞争的前提下,减少传统的重量
      级锁使用产生的性能消耗。在解释轻量级锁的执行过程之前,先明白一点,轻量级锁所适应的场
      景是线程交替执行同步块的情况,如果存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀
      为重量级锁。
      偏向锁
      Hotspot 的作者经过以往的研究发现大多数情况下锁不仅不存在多线程竞争,而且总是由同一线
      程多次获得。偏向锁的目的是在某个线程获得锁之后,消除这个线程锁重入(CAS)的开销,看起
      来让这个线程得到了偏护。引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级
      锁执行路径,因为轻量级锁的获取及释放依赖多次 CAS 原子指令,而偏向锁只需要在置换
      ThreadID 的时候依赖一次 CAS 原子指令(由于一旦出现多线程竞争的情况就必须撤销偏向锁,所
      以偏向锁的撤销操作的性能损耗必须小于节省下来的 CAS 原子指令的性能消耗)。上面说过,轻
      量级锁是为了在线程交替执行同步块时提高性能,而偏向锁则是在只有一个线程执行同步块时进
      一步提高性能。
      分段锁
      分段锁也并非一种实际的锁,而是一种思想 ConcurrentHashMap 是学习分段锁的最好实践。
      锁优化
      减少锁持有时间
      只用在有线程安全要求的程序上加锁
      减小锁粒度
      将大对象(这个对象可能会被很多线程访问),拆成小对象,大大增加并行度,降低锁竞争。
      降低了锁的竞争,偏向锁,轻量级锁成功率才会提高。最最典型的减小锁粒度的案例就是
      ConcurrentHashMap。
      锁分离
      最常见的锁分离就是读写锁 ReadWriteLock,根据功能进行分离成读锁和写锁,这样读读不互
      斥,读写互斥,写写互斥,即保证了线程安全,又提高了性能,具体也请查看[高并发 Java 五]
      JDK 并发包 1。读写分离思想可以延伸,只要操作互不影响,锁就可以分离。比如
      LinkedBlockingQueue 从头部取出,从尾部放数据
      锁粗化
      通常情况下,为了保证多线程间的有效并发,会要求每个线程持有锁的时间尽量短,即在使用完
      公共资源后,应该立即释放锁。但是,凡事都有一个度,如果对同一个锁不停的进行请求、同步
      和释放,其本身也会消耗系统宝贵的资源,反而不利于性能的优化 。
      锁消除
      锁消除是在编译器级别的事情。在即时编译器时,如果发现不可能被共享的对象,则可以消除这
      些对象的锁操作,多数是因为程序员编码不规范引起。
      线程基本方法
      线程相关的基本方法有 wait,notify,notifyAll,sleep,join,yield 等。
      在这里插入图片描述
      线程等待(wait)
      调用该方法的线程进入 WAITING 状态,只有等待另外线程的通知或被中断才会返回,需要注意的
      是调用 wait()方法后,会释放对象的锁。因此,wait 方法一般用在同步方法或同步代码块中。
      线程睡眠(sleep)
      sleep 导致当前线程休眠,与 wait 方法不同的是 sleep 不会释放当前占有的锁,sleep(long)会导致
      线程进入 TIMED-WATING 状态,而 wait()方法会导致当前线程进入 WATING 状态
      线程让步(yield)
      yield 会使当前线程让出 CPU 执行时间片,与其他线程一起重新竞争 CPU 时间片。一般情况下,
      优先级高的线程有更大的可能性成功竞争得到 CPU 时间片,但这又不是绝对的,有的操作系统对
      线程优先级并不敏感。
      线程中断(interrupt)
      中断一个线程,其本意是给这个线程一个通知信号,会影响这个线程内部的一个中断标识位。这
      个线程本身并不会因此而改变状态(如阻塞,终止等)。
    3. 调用 interrupt()方法并不会中断一个正在运行的线程。也就是说处于 Running 状态的线
      程并不会因为被中断而被终止,仅仅改变了内部维护的中断标识位而已。
    4. 若调用 sleep()而使线程处于 TIMED-WATING 状态,这时调用 interrupt()方法,会抛出
      InterruptedException,从而使线程提前结束 TIMED-WATING 状态。
    5. 许多声明抛出 InterruptedException 的方法(如 Thread.sleep(long mills 方法)),抛出异
      常前,都会清除中断标识位,所以抛出异常后,调用 isInterrupted()方法将会返回 false。
    6. 中断状态是线程固有的一个标识位,可以通过此标识位安全的终止线程。比如,你想终止
      一个线程 thread 的时候,可以调用 thread.interrupt()方法,在线程的 run 方法内部可以
      根据 thread.isInterrupted()的值来优雅的终止线程。

    Join 等待其他线程终止
    join() 方法,等待其他线程终止,在当前线程中调用一个线程的 join() 方法,则当前线程转为阻塞
    状态,回到另一个线程结束,当前线程再由阻塞状态变为就绪状态,等待 cpu 的宠幸。
    为什么要用 join()方法?
    很多情况下,主线程生成并启动了子线程,需要用到子线程返回的结果,也就是需要主线程需要
    在子线程结束后再结束,这时候就要用到 join() 方法。

    System.out.println(Thread.currentThread().getName() + "线程运行开始!");
     Thread6 thread1 = new Thread6();
     thread1.setName("线程 B");
     thread1.join();
    System.out.println("这时 thread1 执行完毕之后才能执行主线程");
    

    线程唤醒(notify)
    Object 类中的 notify() 方法,唤醒在此对象监视器上等待的单个线程,如果所有线程都在此对象
    上等待,则会选择唤醒其中一个线程,选择是任意的,并在对实现做出决定时发生,线程通过调
    用其中一个 wait() 方法,在对象的监视器上等待,直到当前的线程放弃此对象上的锁定,才能继
    续执行被唤醒的线程,被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞
    争。类似的方法还有 notifyAll() ,唤醒再次监视器上等待的所有线程。
    其他方法:

    1. sleep():强迫一个线程睡眠N毫秒。
    2. isAlive(): 判断一个线程是否存活。
    3. join(): 等待线程终止。
    4. activeCount(): 程序中活跃的线程数。
    5. enumerate(): 枚举程序中的线程。
    6. currentThread(): 得到当前线程。
    7. isDaemon(): 一个线程是否为守护线程。
    8. setDaemon(): 设置一个线程为守护线程。(用户线程和守护线程的区别在于,是否等待主线
      程依赖于主线程结束而结束)
    9. setName(): 为线程设置一个名称。
    10. wait(): 强迫一个线程等待。
    11. notify(): 通知一个线程继续运行。
    12. setPriority(): 设置一个线程的优先级。
    13. getPriority()::获得一个线程的优先级。

    线程上下文切换
    巧妙地利用了时间片轮转的方式, CPU 给每个任务都服务一定的时间,然后把当前任务的状态保存
    下来,在加载下一任务的状态后,继续服务下一任务,任务的状态保存及再加载, 这段过程就叫做
    上下文切换。时间片轮转的方式使多个任务在同一颗 CPU 上执行变成了可能。
    在这里插入图片描述
    进程
    (有时候也称做任务)是指一个程序运行的实例。在 Linux 系统中,线程就是能并行运行并且
    与他们的父进程(创建他们的进程)共享同一地址空间(一段内存区域)和其他资源的轻量
    级的进程。
    上下文
    是指某一时间点 CPU 寄存器和程序计数器的内容。
    寄存器
    是 CPU 内部的数量较少但是速度很快的内存(与之对应的是 CPU 外部相对较慢的 RAM 主内
    存)。寄存器通过对常用值(通常是运算的中间值)的快速访问来提高计算机程序运行的速
    度。
    程序计数器
    是一个专用的寄存器,用于表明指令序列中 CPU 正在执行的位置,存的值为正在执行的指令
    的位置或者下一个将要被执行的指令的位置,具体依赖于特定的系统。
    PCB-“切换桢”
    上下文切换可以认为是内核(操作系统的核心)在 CPU 上对于进程(包括线程)进行切换,上下
    文切换过程中的信息是保存在进程控制块(PCB, process control block)中的。PCB 还经常被称
    作“切换桢”(switchframe)。信息会一直保存到 CPU 的内存中,直到他们被再次使用。
    上下文切换的活动:
    14. 挂起一个进程,将这个进程在 CPU 中的状态(上下文)存储于内存中的某处。
    15. 在内存中检索下一个进程的上下文并将其在 CPU 的寄存器中恢复。
    16. 跳转到程序计数器所指向的位置(即跳转到进程被中断时的代码行),以恢复该进程在程序
    中。
    引起线程上下文切换的原因
    17. 当前执行任务的时间片用完之后,系统 CPU 正常调度下一个任务;
    18. 当前执行任务碰到 IO 阻塞,调度器将此任务挂起,继续下一任务;
    19. 多个任务抢占锁资源,当前任务没有抢到锁资源,被调度器挂起,继续下一任务;
    20. 用户代码挂起当前任务,让出 CPU 时间;
    21. 硬件中断;
    22.
    同步锁与死锁
    同步锁
    当多个线程同时访问同一个数据时,很容易出现问题。为了避免这种情况出现,我们要保证线程
    同步互斥,就是指并发执行的多个线程,在同一时间内只允许一个线程访问共享数据。 Java 中可
    以使用 synchronized 关键字来取得一个对象的同步锁。
    死锁
    何为死锁,就是多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。
    线程池原理
    线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后
    启动这些任务,如果线程数量超过了最大数量超出数量的线程排队等候,等其它线程执行完毕,
    再从队列中取出任务来执行。他的主要特点为:线程复用;控制最大并发数;管理线程。
    线程复用
    每一个 Thread 的类都有一个 start 方法。 当调用 start 启动线程时 Java 虚拟机会调用该类的 run
    方法。 那么该类的 run() 方法中就是调用了 Runnable 对象的 run() 方法。 我们可以继承重写
    Thread 类,在其 start 方法中添加不断循环调用传递过来的 Runnable 对象。 这就是线程池的实
    现原理。循环方法中不断获取 Runnable 是用 Queue 实现的,在获取下一个 Runnable 之前可以
    是阻塞的。
    线程池的组成
    一般的线程池主要分为以下 4 个组成部分:
    22. 线程池管理器:用于创建并管理线程池
    23. 工作线程:线程池中的线程
    24. 任务接口:每个任务必须实现的接口,用于工作线程调度其运行
    25. 任务队列:用于存放待处理的任务,提供一种缓冲机制
    Java 中的线程池是通过 Executor 框架实现的,该框架中用到了 Executor,Executors,
    ExecutorService,ThreadPoolExecutor ,Callable 和 Future、FutureTask 这几个类。
    在这里插入图片描述
    ThreadPoolExecutor 的构造方法如下

    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize, long keepAliveTime,
    TimeUnit unit, BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
    Executors.defaultThreadFactory(), defaultHandler);
    }
    
    1. corePoolSize:指定了线程池中的线程数量。
    2. maximumPoolSize:指定了线程池中的最大线程数量。
    3. keepAliveTime:当前线程池数量超过 corePoolSize 时,多余的空闲线程的存活时间,即多
      次时间内会被销毁。
    4. unit:keepAliveTime 的单位。
    5. workQueue:任务队列,被提交但尚未被执行的任务。
    6. threadFactory:线程工厂,用于创建线程,一般用默认的即可。
    7. handler:拒绝策略,当任务太多来不及处理,如何拒绝任务。
      拒绝策略
      线程池中的线程已经用完了,无法继续为新任务服务,同时,等待队列也已经排满了,再也
      塞不下新任务了。这时候我们就需要拒绝策略机制合理的处理这个问题。
      JDK 内置的拒绝策略如下:
    8. AbortPolicy : 直接抛出异常,阻止系统正常运行。
    9. CallerRunsPolicy : 只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的
      任务。显然这样做不会真的丢弃任务,但是,任务提交线程的性能极有可能会急剧下降。
    10. DiscardOldestPolicy : 丢弃最老的一个请求,也就是即将被执行的一个任务,并尝试再
      次提交当前任务。
    11. DiscardPolicy : 该策略默默地丢弃无法处理的任务,不予任何处理。如果允许任务丢
      失,这是最好的一种方案。
      以上内置拒绝策略均实现了 RejectedExecutionHandler 接口,若以上策略仍无法满足实际
      需要,完全可以自己扩展 RejectedExecutionHandler 接口。
      Java 线程池工作过程
    12. 线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面
      有任务,线程池也不会马上执行它们。
    13. 当调用 execute() 方法添加一个任务时,线程池会做如下判断:
      a) 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;
      b) 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列;
      c) 如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么还是要
      创建非核心线程立刻运行这个任务;
      d) 如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池
      会抛出异常 RejectExecutionException。
    14. 当一个线程完成任务时,它会从队列中取下一个任务来执行。
    15. 当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运
      行的线程数大于 corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它
      最终会收缩到 corePoolSize 的大小。
      在这里插入图片描述
      JAVA 阻塞队列原理
      阻塞队列,关键字是阻塞,先理解阻塞的含义,在阻塞队列中,线程阻塞有这样的两种情况:
    16. 当队列中没有数据的情况下,消费者端的所有线程都会被自动阻塞(挂起),直到有数据放
      入队列。
      在这里插入图片描述
    17. 当队列中填满数据的情况下,生产者端的所有线程都会被自动阻塞(挂起),直到队列中有
      空的位置,线程被自动唤醒。
      在这里插入图片描述
      阻塞队列的主要方法
      在这里插入图片描述
      „ 抛出异常:抛出一个异常;
      „ 特殊值:返回一个特殊值(null 或 false,视情况而定)
      „ 则塞:在成功操作之前,一直阻塞线程
      „ 超时:放弃前只在最大的时间内阻塞
      插入操作:
      1:public abstract boolean add(E paramE):将指定元素插入此队列中(如果立即可行
      且不会违反容量限制),成功时返回 true,如果当前没有可用的空间,则抛
      出 IllegalStateException。如果该元素是 NULL,则会抛出 NullPointerException 异常。
      2:public abstract boolean offer(E paramE):将指定元素插入此队列中(如果立即可行
      且不会违反容量限制),成功时返回 true,如果当前没有可用的空间,则返回 false。 3:public abstract void put(E paramE) throws InterruptedException: 将指定元素插
      入此队列中,将等待可用的空间(如果有必要)
    public void put(E paramE) throws InterruptedException {
     checkNotNull(paramE);
     ReentrantLock localReentrantLock = this.lock;
     localReentrantLock.lockInterruptibly();
     try {
     while (this.count == this.items.length)
     this.notFull.await();//如果队列满了,则线程阻塞等待
     enqueue(paramE);
     localReentrantLock.unlock();
     } finally {
     localReentrantLock.unlock();
     }
     }
    

    4:offer(E o, long timeout, TimeUnit unit):可以设定等待的时间,如果在指定的时间
    内,还不能往队列中加入 BlockingQueue,则返回失败。
    获取数据操作: 1:poll(time):取走 BlockingQueue 里排在首位的对象,若不能立即取出,则可以等 time 参数
    规定的时间,取不到时返回 null;
    2:poll(long timeout, TimeUnit unit):从 BlockingQueue 取出一个队首的对象,如果在
    指定时间内,队列一旦有数据可取,则立即返回队列中的数据。否则直到时间超时还没有数
    据可取,返回失败。
    3:take():取走 BlockingQueue 里排在首位的对象,若 BlockingQueue 为空,阻断进入等待状
    态直到 BlockingQueue 有新的数据被加入。
    4.drainTo():一次性从 BlockingQueue 获取所有可用的数据对象(还可以指定获取数据的个
    数),通过该方法,可以提升获取数据效率;不需要多次分批加锁或释放锁。
    Java 中的阻塞队列
    43. ArrayBlockingQueue :由数组结构组成的有界阻塞队列。
    44. LinkedBlockingQueue :由链表结构组成的有界阻塞队列。
    45. PriorityBlockingQueue :支持优先级排序的无界阻塞队列。
    46. DelayQueue:使用优先级队列实现的无界阻塞队列。
    47. SynchronousQueue:不存储元素的阻塞队列。
    48. LinkedTransferQueue:由链表结构组成的无界阻塞队列。
    49. LinkedBlockingDeque:由链表结构组成的双向阻塞队列。

    在这里插入图片描述
    ArrayBlockingQueue(公平、非公平)
    用数组实现的有界阻塞队列。此队列按照先进先出(FIFO)的原则对元素进行排序。默认情况下
    不保证访问者公平的访问队列,所谓公平访问队列是指阻塞的所有生产者线程或消费者线程,当
    队列可用时,可以按照阻塞的先后顺序访问队列,即先阻塞的生产者线程,可以先往队列里插入
    元素,先阻塞的消费者线程,可以先从队列里获取元素。通常情况下为了保证公平性会降低吞吐
    量。我们可以使用以下代码创建一个公平的阻塞队列:
    ArrayBlockingQueue fairQueue = new ArrayBlockingQueue(1000,true);
    LinkedBlockingQueue(两个独立锁提高并发)
    基于链表的阻塞队列,同 ArrayListBlockingQueue 类似,此队列按照先进先出(FIFO)的原则对
    元素进行排序。而 LinkedBlockingQueue 之所以能够高效的处理并发数据,还因为其对于生产者
    端和消费者端分别采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生产者和消费
    者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。
    LinkedBlockingQueue 会默认一个类似无限大小的容量(Integer.MAX_VALUE)
    PriorityBlockingQueue(compareTo 排序实现优先)
    是一个支持优先级的无界队列。默认情况下元素采取自然顺序升序排列。可以自定义实现
    compareTo()方法来指定元素进行排序规则,或者初始化 PriorityBlockingQueue 时,指定构造
    参数 Comparator 来对元素进行排序。需要注意的是不能保证同优先级元素的顺序。
    DelayQueue(缓存失效、定时任务 )
    是一个支持延时获取元素的无界阻塞队列。队列使用 PriorityQueue 来实现。队列中的元素必须实
    现 Delayed 接口,在创建元素时可以指定多久才能从队列中获取当前元素。只有在延迟期满时才
    能从队列中提取元素。我们可以将 DelayQueue 运用在以下应用场景:
    50. 缓存系统的设计:可以用 DelayQueue 保存缓存元素的有效期,使用一个线程循环查询
    DelayQueue,一旦能从 Delay。
    51. 定时任务调度:使用 DelayQueue 保存当天将会执行的任务和执行时间,一旦从
    DelayQueue 中获取到任务就开始执行,从比如 TimerQueue 就是使用 DelayQueue 实现的。
    SynchronousQueue(不存储数据、可用于传递数据)
    是一个不存储元素的阻塞队列。每一个 put 操作必须等待一个 take 操作,否则不能继续添加元素。
    SynchronousQueue 可以看成是一个传球手,负责把生产者线程处理的数据直接传递给消费者线
    程。队列本身并不存储任何元素,非常适合于传递性场景,比如在一个线程中使用的数据,传递给
    另外一个线程使用, SynchronousQueue 的吞吐量高于 LinkedBlockingQueue 和
    ArrayBlockingQueue。
    LinkedTransferQueue
    是一个由链表结构组成的无界阻塞 TransferQueue 队列。相对于其他阻塞队列,
    LinkedTransferQueue 多了 tryTransfer 和 transfer 方法。
    52. transfer 方法:如果当前有消费者正在等待接收元素(消费者使用 take()方法或带时间限制的
    poll()方法时),transfer 方法可以把生产者传入的元素立刻 transfer(传输)给消费者。如
    果没有消费者在等待接收元素,transfer 方法会将元素存放在队列的 tail 节点,并等到该元素
    被消费者消费了才返回。
    53. tryTransfer 方法。则是用来试探下生产者传入的元素是否能
    54.者等待接收元素,则返回 false。和 transfer 方法的区别是 tryTransfer 方法无论消费者是否
    接收,方法立即返回。而 transfer 方法是必须等到消费者消费了才返回。
    对于带有时间限制的 tryTransfer(E e, long timeout, TimeUnit unit)方法,则是试图把生产者传
    入的元素直接传给消费者,但是如果没有消费者消费该元素则等待指定的时间再返回,如果超时
    还没消费元素,则返回 false,如果在超时时间内消费了元素,则返回 true。
    LinkedBlockingDeque
    是一个由链表结构组成的双向阻塞队列。所谓双向队列指的你可以从队列的两端插入和移出元素。
    双端队列因为多了一个操作队列的入口,在多线程同时入队时,也就减少了一半的竞争。相比其
    他的阻塞队列,LinkedBlockingDeque 多了 addFirst,addLast,offerFirst,offerLast,
    peekFirst,peekLast 等方法,以 First 单词结尾的方法,表示插入,获取(peek)或移除双端队
    列的第一个元素。以 Last 单词结尾的方法,表示插入,获取或移除双端队列的最后一个元素。另
    外插入方法 add 等同于 addLast,移除方法 remove 等效于 removeFirst。但是 take 方法却等同
    于 takeFirst,不知道是不是 Jdk 的 bug,使用时还是用带有 First 和 Last 后缀的方法更清楚。
    在初始化 LinkedBlockingDeque 时可以设置容量防止其过渡膨胀。另外双向阻塞队列可以运用在
    “工作窃取”模式中。
    CyclicBarrier、CountDownLatch、Semaphore 的用法
    CountDownLatch(线程计数器 )
    CountDownLatch 类位于 java.util.concurrent 包下,利用它可以实现类似计数器的功能。比如有
    一个任务 A,它要等待其他 4 个任务执行完毕之后才能执行,此时就可以利用 CountDownLatch
    来实现这种功能了。

    final CountDownLatch latch = new CountDownLatch(2);
    new Thread(){public void run() {
    System.out.println("子线程"+Thread.currentThread().getName()+"正在执行");
     Thread.sleep(3000);
     System.out.println("子线程"+Thread.currentThread().getName()+"执行完毕");
     latch.countDown();
    };}.start();
    new Thread(){ public void run() {
    System.out.println("子线程"+Thread.currentThread().getName()+"正在执行");
     Thread.sleep(3000);
     System.out.println("子线程"+Thread.currentThread().getName()+"执行完毕");
     latch.countDown();
    };}.start();
    System.out.println("等待 2 个子线程执行完毕...");
    latch.await();
    System.out.println("2 个子线程已经执行完毕");
    System.out.println("继续执行主线程");
    }
    

    CyclicBarrier(回环栅栏-等待至 barrier 状态再全部同时执行)
    字面意思回环栅栏,通过它可以实现让一组线程等待至某个状态之后再全部同时执行。叫做回环
    是因为当所有等待线程都被释放以后,CyclicBarrier 可以被重用。我们暂且把这个状态就叫做
    barrier,当调用 await()方法之后,线程就处于 barrier 了。
    CyclicBarrier 中最重要的方法就是 await 方法,它有 2 个重载版本:

    1. public int await():用来挂起当前线程,直至所有线程都到达 barrier 状态再同时执行后续任
      务;
    2. public int await(long timeout, TimeUnit unit):让这些线程等待至一定的时间,如果还有
      线程没有到达 barrier 状态就直接让到达 barrier 的线程执行后续任务。
      具体使用如下,另外 CyclicBarrier 是可以重用的。
    public static void main(String[] args) {
     int N = 4;
     CyclicBarrier barrier = new CyclicBarrier(N);
     for(int i=0;i<N;i++)
     new Writer(barrier).start();
     }
     static class Writer extends Thread{
     private CyclicBarrier cyclicBarrier;
     public Writer(CyclicBarrier cyclicBarrier) {
     this.cyclicBarrier = cyclicBarrier;
     }
     @Override
     public void run() {
     try {
     Thread.sleep(5000); //以睡眠来模拟线程需要预定写入数据操作
    System.out.println("线程"+Thread.currentThread().getName()+"写入数据完
    毕,等待其他线程写入完毕");
     cyclicBarrier.await();
     } catch (InterruptedException e) {
     e.printStackTrace();
     }catch(BrokenBarrierException e){
     e.printStackTrace();
     }
     System.out.println("所有线程写入完毕,继续处理其他任务,比如数据操作");
     }
     }
    

    Semaphore(信号量-控制同时访问的线程个数)
    Semaphore 翻译成字面意思为 信号量,Semaphore 可以控制同时访问的线程个数,通过
    acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。
    Semaphore 类中比较重要的几个方法:

    1. public void acquire(): 用来获取一个许可,若无许可能够获得,则会一直等待,直到获得许
      可。
    2. public void acquire(int permits):获取 permits 个许可
    3. public void release() { } :释放许可。注意,在释放许可之前,必须先获获得许可。
    4. public void release(int permits) { }:释放 permits 个许可
      上面 4 个方法都会被阻塞,如果想立即得到执行结果,可以使用下面几个方法
    5. public boolean tryAcquire():尝试获取一个许可,若获取成功,则立即返回 true,若获取失
      败,则立即返回 false
    6. public boolean tryAcquire(long timeout, TimeUnit unit):尝试获取一个许可,若在指定的
      时间内获取成功,则立即返回 true,否则则立即返回 false
    7. public boolean tryAcquire(int permits):尝试获取 permits 个许可,若获取成功,则立即返
      回 true,若获取失败,则立即返回 false
    8. public boolean tryAcquire(int permits, long timeout, TimeUnit unit): 尝试获取 permits
      个许可,若在指定的时间内获取成功,则立即返回 true,否则则立即返回 false
    9. 还可以通过 availablePermits()方法得到可用的许可数目。
      例子:若一个工厂有 5 台机器,但是有 8 个工人,一台机器同时只能被一个工人使用,只有使用完
      了,其他工人才能继续使用。那么我们就可以通过 Semaphore 来实现:
    int N = 8; //工人数
    Semaphore semaphore = new Semaphore(5); //机器数目
    for(int i=0;i<N;i++)
    new Worker(i,semaphore).start();
    }
    static class Worker extends Thread{
    private int num;
    private Semaphore semaphore;
    public Worker(int num,Semaphore semaphore){
    this.num = num;
    this.semaphore = semaphore;
    }
    @Override
    public void run() {
    try {
    semaphore.acquire();
    System.out.println("工人"+this.num+"占用一个机器在生产...");
    Thread.sleep(2000);
    System.out.println("工人"+this.num+"释放出机器");
    semaphore.release();
    } catch (InterruptedException e) {
    e.printStackTrace();
    } }
    

    CountDownLatch 和 CyclicBarrier 都能够实现线程之间的等待,只不过它们侧重点不
    同;CountDownLatch 一般用于某个线程 A 等待若干个其他线程执行完任务之后,它才
    执行;而 CyclicBarrier 一般用于一组线程互相等待至某个状态,然后这一组线程再同时
    执行;另外,CountDownLatch 是不能够重用的,而 CyclicBarrier 是可以重用的。
    „ Semaphore 其实和锁有点类似,它一般用于控制对某组资源的访问权限。


    -END-

    如果你喜欢我的分享,想结交更多的JAVA朋友,关注微信公众号
    java学长
    学习更多java技术干货,提升职场技术水平!

    展开全文
1
收藏数 5
精华内容 2