WIKI
+WIKI
全部项目文档索引
+diff --git a/tags/HEXO/index.html b/tags/HEXO/index.html index ac48426..694f090 100644 --- a/tags/HEXO/index.html +++ b/tags/HEXO/index.html @@ -1361,7 +1361,7 @@
๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
try { document.getElementById('last-update-show').innerHTML = timeago(new Date(lastUpDate)); } catch (error) { - document.getElementById('last-update-show').innerHTML = '2020-12-07 日'; + document.getElementById('last-update-show').innerHTML = '2020-12-08 日'; } diff --git a/archives/2018/index.html b/archives/2018/index.html index 591ccbf..b81178f 100644 --- a/archives/2018/index.html +++ b/archives/2018/index.html @@ -1092,8 +1092,8 @@๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
try { document.getElementById('last-update-show').innerHTML = timeago(new Date(lastUpDate)); } catch (error) { - document.getElementById('last-update-show').innerHTML = '2020-12-07 日'; + document.getElementById('last-update-show').innerHTML = '2020-12-08 日'; } diff --git a/archives/2019/11/index.html b/archives/2019/11/index.html index 9c3f37d..a08165f 100644 --- a/archives/2019/11/index.html +++ b/archives/2019/11/index.html @@ -1094,8 +1094,8 @@๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
try { document.getElementById('last-update-show').innerHTML = timeago(new Date(lastUpDate)); } catch (error) { - document.getElementById('last-update-show').innerHTML = '2020-12-07 日'; + document.getElementById('last-update-show').innerHTML = '2020-12-08 日'; } diff --git a/archives/2019/index.html b/archives/2019/index.html index 8042e2b..fecc531 100644 --- a/archives/2019/index.html +++ b/archives/2019/index.html @@ -1094,8 +1094,8 @@๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
try { document.getElementById('last-update-show').innerHTML = timeago(new Date(lastUpDate)); } catch (error) { - document.getElementById('last-update-show').innerHTML = '2020-12-07 日'; + document.getElementById('last-update-show').innerHTML = '2020-12-08 日'; } diff --git a/archives/2020/01/index.html b/archives/2020/01/index.html index 4d08f1b..c693da7 100644 --- a/archives/2020/01/index.html +++ b/archives/2020/01/index.html @@ -1086,8 +1086,8 @@๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
try { document.getElementById('last-update-show').innerHTML = timeago(new Date(lastUpDate)); } catch (error) { - document.getElementById('last-update-show').innerHTML = '2020-12-07 日'; + document.getElementById('last-update-show').innerHTML = '2020-12-08 日'; } diff --git a/archives/2020/03/index.html b/archives/2020/03/index.html index f01e891..f4e46b3 100644 --- a/archives/2020/03/index.html +++ b/archives/2020/03/index.html @@ -1092,8 +1092,8 @@๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
try { document.getElementById('last-update-show').innerHTML = timeago(new Date(lastUpDate)); } catch (error) { - document.getElementById('last-update-show').innerHTML = '2020-12-07 日'; + document.getElementById('last-update-show').innerHTML = '2020-12-08 日'; } diff --git a/archives/2020/05/index.html b/archives/2020/05/index.html index 4a1e443..49f0209 100644 --- a/archives/2020/05/index.html +++ b/archives/2020/05/index.html @@ -1358,8 +1358,8 @@๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
try { document.getElementById('last-update-show').innerHTML = timeago(new Date(lastUpDate)); } catch (error) { - document.getElementById('last-update-show').innerHTML = '2020-12-07 日'; + document.getElementById('last-update-show').innerHTML = '2020-12-08 日'; } diff --git a/archives/2020/06/index.html b/archives/2020/06/index.html index 8bbe089..6a684e8 100644 --- a/archives/2020/06/index.html +++ b/archives/2020/06/index.html @@ -1086,8 +1086,8 @@๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
try { document.getElementById('last-update-show').innerHTML = timeago(new Date(lastUpDate)); } catch (error) { - document.getElementById('last-update-show').innerHTML = '2020-12-07 日'; + document.getElementById('last-update-show').innerHTML = '2020-12-08 日'; } diff --git a/archives/2020/index.html b/archives/2020/index.html index 325a817..b6be458 100644 --- a/archives/2020/index.html +++ b/archives/2020/index.html @@ -1754,8 +1754,8 @@๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
try { document.getElementById('last-update-show').innerHTML = timeago(new Date(lastUpDate)); } catch (error) { - document.getElementById('last-update-show').innerHTML = '2020-12-07 日'; + document.getElementById('last-update-show').innerHTML = '2020-12-08 日'; } diff --git a/archives/index.html b/archives/index.html index 8e75a5f..a63bd92 100644 --- a/archives/index.html +++ b/archives/index.html @@ -1101,8 +1101,8 @@๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
try { document.getElementById('last-update-show').innerHTML = timeago(new Date(lastUpDate)); } catch (error) { - document.getElementById('last-update-show').innerHTML = '2020-12-07 日'; + document.getElementById('last-update-show').innerHTML = '2020-12-08 日'; } diff --git a/categories/HEXO/index.html b/categories/HEXO/index.html index 51c8653..bbb1dec 100644 --- a/categories/HEXO/index.html +++ b/categories/HEXO/index.html @@ -1362,8 +1362,8 @@๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
try { document.getElementById('last-update-show').innerHTML = timeago(new Date(lastUpDate)); } catch (error) { - document.getElementById('last-update-show').innerHTML = '2020-12-07 日'; + document.getElementById('last-update-show').innerHTML = '2020-12-08 日'; } diff --git a/categories/Java/index.html b/categories/Java/index.html index f009044..542df23 100644 --- a/categories/Java/index.html +++ b/categories/Java/index.html @@ -1370,8 +1370,8 @@๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
try { document.getElementById('last-update-show').innerHTML = timeago(new Date(lastUpDate)); } catch (error) { - document.getElementById('last-update-show').innerHTML = '2020-12-07 日'; + document.getElementById('last-update-show').innerHTML = '2020-12-08 日'; } diff --git "a/categories/\345\267\245\345\205\267/index.html" "b/categories/\345\267\245\345\205\267/index.html" index 816e55c..bc27e75 100644 --- "a/categories/\345\267\245\345\205\267/index.html" +++ "b/categories/\345\267\245\345\205\267/index.html" @@ -1090,8 +1090,8 @@๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
try { document.getElementById('last-update-show').innerHTML = timeago(new Date(lastUpDate)); } catch (error) { - document.getElementById('last-update-show').innerHTML = '2020-12-07 日'; + document.getElementById('last-update-show').innerHTML = '2020-12-08 日'; } diff --git "a/categories/\347\276\216\346\226\207/index.html" "b/categories/\347\276\216\346\226\207/index.html" index baee1d9..a5fb85c 100644 --- "a/categories/\347\276\216\346\226\207/index.html" +++ "b/categories/\347\276\216\346\226\207/index.html" @@ -1090,8 +1090,8 @@๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
try { document.getElementById('last-update-show').innerHTML = timeago(new Date(lastUpDate)); } catch (error) { - document.getElementById('last-update-show').innerHTML = '2020-12-07 日'; + document.getElementById('last-update-show').innerHTML = '2020-12-08 日'; } diff --git "a/categories/\351\224\201/index.html" "b/categories/\351\224\201/index.html" index c6cac57..57a36a2 100644 --- "a/categories/\351\224\201/index.html" +++ "b/categories/\351\224\201/index.html" @@ -1096,8 +1096,8 @@๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
try { document.getElementById('last-update-show').innerHTML = timeago(new Date(lastUpDate)); } catch (error) { - document.getElementById('last-update-show').innerHTML = '2020-12-07 日'; + document.getElementById('last-update-show').innerHTML = '2020-12-08 日'; } diff --git "a/categories/\351\235\242\350\257\225/index.html" "b/categories/\351\235\242\350\257\225/index.html" index a7f274a..f58914a 100644 --- "a/categories/\351\235\242\350\257\225/index.html" +++ "b/categories/\351\235\242\350\257\225/index.html" @@ -1370,8 +1370,8 @@๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
try { document.getElementById('last-update-show').innerHTML = timeago(new Date(lastUpDate)); } catch (error) { - document.getElementById('last-update-show').innerHTML = '2020-12-07 日'; + document.getElementById('last-update-show').innerHTML = '2020-12-08 日'; } diff --git a/content.json b/content.json index 398fdc7..bbd2f87 100644 --- a/content.json +++ b/content.json @@ -1 +1 @@ -{"meta":{"title":"Coder编程","subtitle":"","description":"","author":"Coder编程","url":"http://yoursite.com","root":"/"},"pages":[{"title":"关于","date":"2020-12-06T08:01:15.406Z","updated":"2020-12-06T08:01:15.406Z","comments":true,"path":"about/index.html","permalink":"http://yoursite.com/about/index.html","excerpt":"","text":"关于本站 https://coder-programming.cn 本站作为小编文章分类总结地,在这里你能看到最全的技术学习文章! 这里你能看到Java相关又或者分享一些奇闻怪事! 数据结构与算法、Java相关知识体系、框架知识及原理、Spring全家桶、微服务项目实战、DevOps实践之路、每日一篇互联网大厂面试或笔试题以及PMP项目管理知识等。 关于本人 Coder编程 一名九零后小伙~ 一位Java开发从业者~ 有着较强的学习心态,喜欢学习尝试一些新鲜事物,有想法的朋友可以一起学习哦~联系方式: 微信公众号:Coder编程 QQ群:315211365 QQ邮箱:573059382@qq.com 2021年计划 1.阅读书籍 《沉默的大多数》 《原则》 《态度改变与社会影响》 《最好的告别》 《社会性动物》 2.LeetCode刷题 数组题:349"},{"title":"所有分类","date":"2020-05-11T12:49:50.025Z","updated":"2020-05-11T12:49:50.025Z","comments":true,"path":"categories/index.html","permalink":"http://yoursite.com/categories/index.html","excerpt":"","text":""},{"title":"建站历史","date":"2020-12-07T15:23:18.175Z","updated":"2020-12-07T15:23:18.175Z","comments":true,"path":"history/index.html","permalink":"http://yoursite.com/history/index.html","excerpt":"","text":"建站历史 2020-07-28 集成 面试题 2020-07-24 改用 Volantis 主题优化 侧边栏、导航栏等 配置 2020-05-15 改用域名:www.coder-programming.cn使用:hexo-theme-material-x 主题 2020-04-20 使用Hexo初建网站使用Github域名:CoderMerlin.github.io"},{"title":"我的朋友们","date":"2020-05-11T13:04:34.163Z","updated":"2020-05-11T13:04:34.163Z","comments":true,"path":"friends/index.html","permalink":"http://yoursite.com/friends/index.html","excerpt":"","text":"互换友链规则:博客名、头像链接、博客链接、标签(最多3个)"},{"title":"","date":"2020-01-19T12:26:43.257Z","updated":"2020-01-14T14:01:16.353Z","comments":true,"path":"mylist/index.html","permalink":"http://yoursite.com/mylist/index.html","excerpt":"","text":""},{"title":"关于","date":"2020-12-07T15:22:27.575Z","updated":"2020-12-07T15:22:27.575Z","comments":true,"path":"project/index.html","permalink":"http://yoursite.com/project/index.html","excerpt":"","text":"WIKI 全部项目文档索引 HM云平台 OPC平台 Java面试"},{"title":"所有标签","date":"2020-05-11T12:29:45.896Z","updated":"2020-05-11T12:29:45.896Z","comments":true,"path":"tags/index.html","permalink":"http://yoursite.com/tags/index.html","excerpt":"","text":""}],"posts":[{"title":"玩转 IDEA 系列教程——强烈推荐官方中文(汉化)插件!","slug":"idea/idea01","date":"2020-06-04T12:43:00.000Z","updated":"2020-06-04T12:43:00.000Z","comments":true,"path":"2020/06/04/idea/idea01/","link":"","permalink":"http://yoursite.com/2020/06/04/idea/idea01/","excerpt":"IDEA2020.1版本的到来,官方也悄悄更新了IDEA的中文插件。至今为止也有一段时间了,本人也下载使用过,对于英语底子薄弱的同学来说,中文插件还是非常友好的!…","text":"每天进步一点,不做curd工程师与Api调用工程师欢迎访问个人博客网站:https://www.coder-programming.cn/ IDEA2020.1版本的到来,官方也悄悄更新了IDEA的中文插件。至今为止也有一段时间了,本人也下载使用过,对于英语底子薄弱的同学来说,中文插件还是非常友好的! 有兴趣的童鞋也可以下载尝尝鲜~ 接下来,我们来介绍如何安装这款插件! 一、在线安装打开设置——>插件 输入框搜索:Chinese (Simplified) Language Pack 之后重启即可使用! 二、离线安装离线下载地址:https://plugins.jetbrains.com/plugin/13710-chinese-simplified-language-pack-eap 注意事项:需要根据自身的IDEA版本号进行相应的选择下载! 下载离线包 安装 安装离线包有两张方法 方式一: File -> Settings -> Plugins -> 安装本地插件包 方式二: 直接将插件包托入到IDEA框内即可! 之后重启即可使用! 效果 文末文章收录至Github: https://github.com/CoderMerlin/coder-programmingGitee: https://gitee.com/573059382/coder-programming欢迎关注并star~","categories":[{"name":"工具","slug":"工具","permalink":"http://yoursite.com/categories/%E5%B7%A5%E5%85%B7/"}],"tags":[{"name":"工具","slug":"工具","permalink":"http://yoursite.com/tags/%E5%B7%A5%E5%85%B7/"}]},{"title":"博客搭建-博客搭建完整教程","slug":"BLOG03-HEXO-JC","date":"2020-05-14T14:39:58.972Z","updated":"2020-08-17T12:32:05.849Z","comments":true,"path":"2020/05/14/BLOG03-HEXO-JC/","link":"","permalink":"http://yoursite.com/2020/05/14/BLOG03-HEXO-JC/","excerpt":"Hexo初学者都会搭建的教程在这里!!!","text":"每天进步一点,不做curd工程师与Api调用工程师! 欢迎大家访问我的博客:Coder编程——个人博客 前言其实早在三年前,刚毕业那会就在网上学着搭建自己的个人博客。后面由于其他原因(主要是懒)就没再继续管理。如今又为何重新拾起呢? 第一:自己本身从毕业到工作也已经有了三年时间,或多或少有了一些工作经验和感悟。第二:平时有时间的时候,自己也会看看博客,写写文章(划划水)。第三:工作这么久,也要记录一下自己的学习记录,分享给大家。…. 下面的开始写教程了,这篇教程主要参考: https://www.itrhx.com/ 他的教程写的非常详细!本文也主要参考他写的内容,并修改~ 推荐文章: 《我为什么写博客》 (By 知明所以) 《为什么你应该(从现在开始就)写博客》 (By 刘未鹏 | Mind Hacks) 1. 概念1.1 Github PagesGithub Pages可以被认为是用户编写的、托管在github上的静态网页。使用Github Pages可以为你提供一个免费的服务器,免去了自己搭建服务器和写数据库的麻烦。此外还可以绑定自己的域名。 类似Github Pages的 Gitee Pages Gitlab Pages Coding 等等 后续我也会写文章专门介绍,这几个Pages的搭建过程,欢迎大家关注~ 1.2 HexoHexo 是一个快速、简洁且高效的博客框架。Hexo 使用 Markdown(或其他渲染引擎)解析文章,在几秒内,即可利用靓丽的主题生成静态网页。 2. 环境安装2.1 安装Node.js访问官网,按需下载相应版本,默认安装可以了。 更多详细教程: https://www.runoob.com/nodejs/nodejs-install-setup.html https://blog.csdn.net/antma/article/details/86104068 2.2 安装Git访问官网,按需下载相应版本,默认安装即可。 更多详细教程: Win10安装:https://blog.csdn.net/qq_32786873/article/details/80570783 Mac安装:https://www.jianshu.com/p/b91f848655af 2.3 检查软件是否安装成功同时按下 Win 键和 R 键打开运行窗口,输入 cmd ,然后输入以下命令,有相应版本信息显示则安装成功,若不正确可以卸载软件重新安装,此外若安装成功,在桌面右键鼠标,可以看到菜单里多了 Git GUI Here 和 Git Bash Here两个选项,第一个是图形界面的Git操作,另一个是命令行 123$ git --version$ node -v$ npm -v 正确输出版本号,说明安装成功了~ 3. Hexo安装正在编写。。。","categories":[{"name":"HEXO","slug":"HEXO","permalink":"http://yoursite.com/categories/HEXO/"}],"tags":[{"name":"HEXO","slug":"HEXO","permalink":"http://yoursite.com/tags/HEXO/"}]},{"title":"博客搭建-设置标签/分类/归档相关页面","slug":"BLOG02-HEXO-BQFLGD","date":"2020-05-12T08:47:59.726Z","updated":"2020-08-17T12:31:36.569Z","comments":true,"path":"2020/05/12/BLOG02-HEXO-BQFLGD/","link":"","permalink":"http://yoursite.com/2020/05/12/BLOG02-HEXO-BQFLGD/","excerpt":"如何配置标签/分类/归档???看完你就懂了!!!","text":"每天进步一点,不做curd工程师与Api调用工程师 前言最近晚上有时间就忙着倒腾自己的博客网站:https://www.coder-programming.cn/ 网站还在建设当中,我会慢慢分享自己的搭建博客的过程和遇到的问题,在这里与大家一起分享! 设置标签页面主题的 _config.yml 文件中,找到如下配置menu_desktop 或者 menu_mobile。(注意:填写的路径要对应上) 在\\source\\tags 中新建index.md。写入以下内容即可。 12345---layout: tagindex: truetitle: 所有标签--- 在需要发布的文章同样注意加入tags。举个栗子: 12345678910111213---title: Java学习路线整理updated: 2020-03-19 00:34:14date: 2020-03-19 00:34:14categories: - [Java] - [面试]tags: - [Java] - [面试] top: true--- 效果如图: 设置分类页面主题的 _config.yml 文件中,找到如下配置menu_desktop 或者 menu_mobile。(注意:填写的路径要对应上) 在\\source\\categories 中新建index.md。写入以下内容即可。 1234---layout: categorytitle: 所有分类--- 在需要发布的文章同样注意加入tags。举个栗子: 12345678910111213---title: Java学习路线整理updated: 2020-03-19 00:34:14date: 2020-03-19 00:34:14categories: - [Java] - [面试]tags: - [Java] - [面试] top: true--- 效果如图: 设置归档页面归档页面不要添加任何文件!归档页面不要添加任何文件!归档页面不要添加任何文件!重要的事说三遍! 只需要在主题的 _config.yml 文件中,找到如下配置menu_desktop 或者 menu_mobile。(注意:填写的路径要对应上), 填写archives/即可。 效果如图: 推荐[博客搭建] 增加百度统计功能 ProcessOn是一个在线作图工具的聚合平台~ 文末 欢迎关注个人微信公众号:Coder编程欢迎关注Coder编程公众号,主要分享数据结构与算法、Java相关知识体系、框架知识及原理、Spring全家桶、微服务项目实战、DevOps实践之路、每日一篇互联网大厂面试或笔试题以及PMP项目管理知识等。更多精彩内容正在路上~新建了一个qq群:315211365,欢迎大家进群交流一起学习。谢谢了!也可以介绍给身边有需要的朋友。 文章收录至Github: https://github.com/CoderMerlin/coder-programmingGitee: https://gitee.com/573059382/coder-programming欢迎关注并star~","categories":[{"name":"HEXO","slug":"HEXO","permalink":"http://yoursite.com/categories/HEXO/"}],"tags":[{"name":"HEXO","slug":"HEXO","permalink":"http://yoursite.com/tags/HEXO/"}]},{"title":"博客搭建-增加百度统计功能","slug":"BLOG01-HEXO-BDTJ","date":"2020-05-12T06:49:55.625Z","updated":"2020-08-17T12:31:54.706Z","comments":true,"path":"2020/05/12/BLOG01-HEXO-BDTJ/","link":"","permalink":"http://yoursite.com/2020/05/12/BLOG01-HEXO-BDTJ/","excerpt":"如何增加增加百度统计功能???","text":"每天进步一点,不做curd工程师与Api调用工程师 前言最近晚上有时间就忙着倒腾自己的博客网站:https://www.coder-programming.cn/ 网站还在建设当中,我会慢慢分享自己的搭建博客的过程和遇到的问题,在这里与大家一起分享! 百度统计是百度推出的一款免费的专业网站流量分析工具,能够告诉用户访客是如何找到并浏览用户的网站,在网站上做了些什么,非常有趣,接下来我们把百度统计添加到自己博客当中 访问百度统计首页,注册一个账号后登陆,添加你的博客网站 点击增加网站,填写个人网站的信息。 接着点击代码获取,复制该代码 然后到目录\\themes\\hexo-theme-material-x\\layout\\_partial\\head.ejs,里面粘贴你刚刚复制的代码。代码如下: 123456789101112<% if (config.baidu_analytics_key) { %> <!-- ba --> <script> var _hmt = _hmt || []; (function() { var hm = document.createElement("script"); hm.src = "https://hm.baidu.com/hm.js?<%= config.baidu_analytics_key %>"; var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(hm, s); })(); </script> <% } %> 修改博客根目录下的 _config.yml 文件(注意:不是主题下的_config.yml),将你的key填写进去: 所有操作完成后可以在百度统计管理页面检查代码是否安装成功,如果代码安装正确,一般20分钟后,可以查看网站分析数据 另外推荐:友盟,2010年4月在北京成立,安全、可靠、公正、第三方的网站流量统计分析系统 参考:https://www.itrhx.com/2018/08/27/A04-Hexo-blog-topic-personalization/ 推荐ProcessOn是一个在线作图工具的聚合平台~ 文末 欢迎关注个人微信公众号:Coder编程欢迎关注Coder编程公众号,主要分享数据结构与算法、Java相关知识体系、框架知识及原理、Spring全家桶、微服务项目实战、DevOps实践之路、每日一篇互联网大厂面试或笔试题以及PMP项目管理知识等。更多精彩内容正在路上~新建了一个qq群:315211365,欢迎大家进群交流一起学习。谢谢了!也可以介绍给身边有需要的朋友。 文章收录至Github: https://github.com/CoderMerlin/coder-programmingGitee: https://gitee.com/573059382/coder-programming欢迎关注并star~","categories":[{"name":"HEXO","slug":"HEXO","permalink":"http://yoursite.com/categories/HEXO/"}],"tags":[{"name":"HEXO","slug":"HEXO","permalink":"http://yoursite.com/tags/HEXO/"},{"name":"百度统计","slug":"百度统计","permalink":"http://yoursite.com/tags/%E7%99%BE%E5%BA%A6%E7%BB%9F%E8%AE%A1/"}]},{"title":"Java学习路线整理","slug":"java","date":"2020-03-18T16:34:14.000Z","updated":"2020-03-18T16:34:14.000Z","comments":true,"path":"2020/03/19/java/","link":"","permalink":"http://yoursite.com/2020/03/19/java/","excerpt":"面试 《剑指Offer》(豆瓣评分 8.3,0.7K+人评价) … 程序员代码面试指南:IT名企算法与数据结构题目最优解(第2版) (豆瓣评分 8.7,0.2K+人评价) … 编程之美(豆瓣评分 8.4,3K+人评价) … …","text":"Java 基础 并发 JVM Java8 新特性 代码优化 网络 操作系统 数据结构 算法 入门 经典 面试 数据库 系统设计 设计模式 常用框架 Spring/SpringBoot Netty 分布式 网站架构 软件底层 其他 其他 Java基础 《Head First Java》 : 可以说是我的 Java 启蒙书籍了,特别适合新手读当然也适合我们用来温故 Java 知识点。 《Java 核心技术卷 1+卷 2》: 很棒的两本书,建议有点 Java 基础之后再读,介绍的还是比较深入的,非常推荐。这两本书我一般也会用来巩固知识点或者当做工具书参考,是两本适合放在自己身边的好书。 《Java 编程思想 (第 4 版)》(推荐,豆瓣评分 9.1,3.2K+人评价):大部分人称之为Java领域的圣经,但我不推荐初学者阅读,有点劝退的味道。稍微有点基础后阅读更好。 《JAVA 网络编程 第 4 版》: 可以系统的学习一下网络的一些概念以及网络编程在 Java 中的使用。 《Java性能权威指南》:O’Reilly 家族书,性能调优的入门书,我个人觉得性能调优是每个 Java 从业者必备知识,这本书的缺点就是太老了,但是这本书可以作为一个实战书,尤其是 JVM 调优!不适合初学者。前置书籍:《深入理解 Java 虚拟机》 并发 《Java 并发编程之美》 :我觉得这本书还是非常适合我们用来学习 Java 多线程的。这本书的讲解非常通俗易懂,作者从并发编程基础到实战都是信手拈来。 另外,这本书的作者加多自身也会经常在网上发布各种技术文章。我觉得这本书也是加多大佬这么多年在多线程领域的沉淀所得的结果吧!他书中的内容基本都是结合代码讲解,非常有说服力! 《实战 Java 高并发程序设计》: 这个是我第二本要推荐的书籍,比较适合作为多线程入门/进阶书籍来看。这本书内容同样是理论结合实战,对于每个知识点的讲解也比较通俗易懂,整体结构也比较清。 《深入浅出 Java 多线程》:这本书是几位大厂(如阿里)的大佬开源的,Github 地址:https://github.com/RedSpider1/concurrent几位作者为了写好《深入浅出 Java 多线程》这本书阅读了大量的 Java 多线程方面的书籍和博客,然后再加上他们的经验总结、Demo 实例、源码解析,最终才形成了这本书。这本书的质量也是非常过硬!给作者们点个赞!这本书有统一的排版规则和语言风格、清晰的表达方式和逻辑。并且每篇文章初稿写完后,作者们就会互相审校,合并到主分支时所有成员会再次审校,最后再通篇修订了三遍。 《Java 并发编程的艺术》 :这本书不是很适合作为 Java 多线程入门书籍,需要具备一定的 JVM 基础,有些东西讲的还是挺深入的。另外,就我自己阅读这本书的感觉来说,我觉得这本书的章节规划有点杂乱,但是,具体到某个知识点又很棒!这可能也和这本书由三名作者共同编写完成有关系吧! …… JVM 《深入理解 Java 虚拟机(第 3 版)》):必读!必读!必读!神书,建议多刷几篇。里面不光有丰富地JVM理论知识,还有JVM实战案例!必读! 《实战 JAVA 虚拟机》:作为入门的了解 Java 虚拟机的知识还是不错的。 Java8 新特性 《Java 8 实战》:面向 Java 8 的技能升级,包括 Lambdas、流和函数式编程特性。实战系列的一贯风格让自己快速上手应用起来。Java 8 支持的 Lambda 是精简表达在语法上提供的支持。Java 8 提供了 Stream,学习和使用可以建立流式编程的认知。 《Java 8 编程参考官方教程》:建议当做工具书来用!哪里不会翻哪里! 代码优化 《重构_改善既有代码的设计》:豆瓣 9.1 分,重构书籍的开山鼻祖。 《Effective java 》:本书介绍了在 Java 编程中很多极具实用价值的经验规则,这些经验规则涵盖了大多数开发人员每天所面临的问题的解决方案。这篇文章能够非常实际地帮助你写出更加清晰、健壮和高效的代码。本书中的每条规则都以简短、独立的小文章形式出现,并通过例子代码加以进一步说明。 《代码整洁之道》:虽然是用 Java 语言作为例子,全篇都是在阐述 Java 面向对象的思想,但是其中大部分内容其它语言也能应用到。 阿里巴巴 Java 开发手册 :https://github.com/alibaba/p3c Google Java 编程风格指南: http://www.hawstein.com/posts/google-java-style.html 网络 《图解 HTTP》: 讲漫画一样的讲 HTTP,很有意思,不会觉得枯燥,大概也涵盖也 HTTP 常见的知识点。因为篇幅问题,内容可能不太全面。不过,如果不是专门做网络方向研究的小伙伴想研究 HTTP 相关知识的话,读这本书的话应该来说就差不多了。 《HTTP 权威指南》:如果要全面了解 HTTP 非此书不可! 操作系统 《鸟哥的 Linux 私房菜》:本书是最具知名度的 Linux 入门书《鸟哥的 Linux 私房菜基础学习篇》的最新版,全面而详细地介绍了 Linux 操作系统。 数据结构 《大话数据结构》:入门类型的书籍,读起来比较浅显易懂,适合没有数据结构基础或者说数据结构没学好的小伙伴用来入门数据结构。 算法入门 《我的第一本算法书》 (豆瓣评分 7.1,0.2K+人评价) 一本不那么“专业”的算法书籍。和下面两本推荐的算法书籍都是比较通俗易懂,“不那么深入”的算法书籍。我个人非常推荐,配图和讲解都非常不错! 《算法图解》(豆瓣评分 8.4,1.5K+人评价) :入门类型的书籍,读起来比较浅显易懂,非常适合没有算法基础或者说算法没学好的小伙伴用来入门。示例丰富,图文并茂,以让人容易理解的方式阐释了算法.读起来比较快,内容不枯燥! 《啊哈!算法》 (豆瓣评分 7.7,0.5K+人评价) :和《算法图解》类似的算法趣味入门书籍。 经典 下面这些书籍都是经典中的经典,但是阅读起来难度也比较大,不做太多阐述,神书就完事了!推荐先看 《算法》,然后再选下面的书籍进行进一步阅读。不需要都看,找一本好好看或者找某本书的某一个章节知识点好好看。 《算法 第四版》(豆瓣评分 9.3,0.4K+人评价): 我在大二的时候被我们的一个老师强烈安利过!自己也在当时购买了一本放在宿舍,到离开大学的时候自己大概看了一半多一点。因为内容实在太多了!另外,这本书还提供了详细的Java代码,非常适合学习 Java 的朋友来看,可以说是 Java 程序员的必备书籍之一了。再来介绍一下这本书籍吧!这本书籍算的上是算法领域经典的参考书,全面介绍了关于算法和数据结构的必备知识,并特别针对排序、搜索、图处理和字符串处理进行了论述。 编程珠玑(豆瓣评分 9.1,2K+人评价) :经典名著,被无数读者强烈推荐的书籍,几乎是顶级程序员必看的书籍之一了。这本书的作者也非常厉害,Java之父 James Gosling 就是他的学生。很多人都说这本书不是教你具体的算法,而是教你一种编程的思考方式。这种思考方式不仅仅在编程领域适用,在其他同样适用。 《算法设计手册》(豆瓣评分9.1 , 45人评价) :被 Teach Yourself Computer Science 强烈推荐的一本算法书籍。 《算法导论》 (豆瓣评分 9.2,0.4K+人评价) 《计算机程序设计艺术(第1卷)》(豆瓣评分 9.4,0.4K+人评价) 面试 《剑指Offer》(豆瓣评分 8.3,0.7K+人评价)这本面试宝典上面涵盖了很多经典的算法面试题,如果你要准备大厂面试的话一定不要错过这本书。《剑指Offer》 对应的算法编程题部分的开源项目解析:CodingInterviews 程序员代码面试指南:IT名企算法与数据结构题目最优解(第2版) (豆瓣评分 8.7,0.2K+人评价) :题目相比于《剑指 offer》 来说要难很多,题目涵盖面相比于《剑指 offer》也更加全面。全书一共有将近300道真实出现过的经典代码面试题。 编程之美(豆瓣评分 8.4,3K+人评价):这本书收集了约60道算法和程序设计题目,这些题目大部分在近年的笔试、面试中出现过,或者是被微软员工热烈讨论过。作者试图从书中各种有趣的问题出发,引导读者发现问题,分析问题,解决问题,寻找更优的解法。 数据库MySQL: 《高性能 MySQL》:这本书不用多说了把!MySQL 领域的经典之作,拥有广泛的影响力。不但适合数据库管理员(dba)阅读,也适合开发人员参考学习。不管是数据库新手还是专家,相信都能从本书有所收获。如果你的时间不够的话,第5章关于索引的内容和第6章关于查询的内容是必读的! 《MySQL 技术内幕-InnoDB 存储引擎》(推荐,豆瓣评分 8.7):了解 InnoDB 存储引擎底层原理必备的一本书,比较深入。 Redis: 《Redis 实战》:如果你想了解 Redis 的一些概念性知识的话,这本书真的非常不错。 《Redis 设计与实现》:也还行吧! 系统设计设计模式 《设计模式 : 可复用面向对象软件的基础》 :设计模式的经典! 《Head First 设计模式(中文版)》 :相当赞的一本设计模式入门书籍。用实际的编程案例讲解算法设计中会遇到的各种问题和需求变更(对的,连需求变更都考虑到了!),并以此逐步推导出良好的设计模式解决办法。 《大话设计模式》 :本书通篇都是以情景对话的形式,用多个小故事或编程示例来组织讲解GOF(即《设计模式 : 可复用面向对象软件的基础》这本书)),但是不像《设计模式 : 可复用面向对象软件的基础》难懂。但是设计模式只看书是不够的,还是需要在实际项目中运用,在实战中体会。 常用框架Spring/SpringBoot 《Spring 实战(第 4 版)》 :不建议当做入门书籍读,入门的话可以找点国人的书或者视频看。这本定位就相当于是关于 Spring 的新华字典,只有一些基本概念的介绍和示例,涵盖了 Spring 的各个方面,但都不够深入。就像作者在最后一页写的那样:“学习 Spring,这才刚刚开始”。 《Spring源码深度解析 第2版》 :读Spring源码必备的一本书籍。市面上关于Spring源码分析的书籍太少了。 《Spring 5高级编程(第5版)》 :推荐阅读,对于Spring5的新特性介绍的很好!不过内容比较多,可以作为工具书参考。 《精通Spring4.x企业应用开发实战》 :通过实战讲解,比较适合作为Spring入门书籍来看。 《Spring入门经典》 :适合入门,也有很多示例! 《Spring Boot实战派》 :这本书使用的Spring Boot 2.0+的版本,还算比较新。整本书采用“知识点+实例”的形式编写。本书通过“58个基于知识的实例+2个综合性的项目”,深入地讲解Spring Boot的技术原理、知识点和具体应用;把晦涩难懂的理论用实例展现出来,使得读者对知识的理解变得非常容易,同时也立即学会如何使用它。说实话,我还是比较推荐这本书的。 《Spring Boot编程思想(核心篇)》 :SpringBoot深入书,不适合初学者。书尤其的厚,这本书的缺点是书的很多知识点的讲解过于啰嗦和拖沓,优点是书中对SpringBoot内部原理讲解很清楚。 Netty 《Netty进阶之路:跟着案例学Netty》 : 这本书的优点是有不少实际的案例的讲解,通过案例来学习是很不错的! 《Netty 4.x 用户指南》 :《Netty 4.x 用户指南》中文翻译(包含了官方文档以及其他文章)。 《Netty 入门与实战:仿写微信 IM 即时通讯系统》 :基于 Netty 框架实现 IM 核心系统,带你深入学习 Netty 网络编程核心知识 《Netty 实战》 :可以作为工具书参考! 分布式 《从 Paxos 到 Zookeeper》:简要介绍几种典型的分布式一致性协议,以及解决分布式一致性问题的思路,其中重点讲解了 Paxos 和 ZAB 协议。同时,本书深入介绍了分布式一致性问题的工业解决方案——ZooKeeper,并着重向读者展示这一分布式协调框架的使用方法、内部实现及运维技巧,旨在帮助读者全面了解 ZooKeeper,并更好地使用和运维 ZooKeeper。 《RabbitMQ 实战指南》:《RabbitMQ 实战指南》从消息中间件的概念和 RabbitMQ 的历史切入,主要阐述 RabbitMQ 的安装、使用、配置、管理、运维、原理、扩展等方面的细节。如果你想浅尝 RabbitMQ 的使用,这本书是你最好的选择;如果你想深入 RabbitMQ 的原理,这本书也是你最好的选择;总之,如果你想玩转 RabbitMQ,这本书一定是最值得看的书之一 《Spring Cloud 微服务实战》:从时下流行的微服务架构概念出发,详细介绍了 Spring Cloud 针对微服务架构中几大核心要素的解决方案和基础组件。对于各个组件的介绍,《Spring Cloud 微服务实战》主要以示例与源码结合的方式来帮助读者更好地理解这些组件的使用方法以及运行原理。同时,在介绍的过程中,还包含了作者在实践中所遇到的一些问题和解决思路,可供读者在实践中作为参考。 网站架构 《大型网站技术架构:核心原理与案例分析+李智慧》:这本书我读过,基本不需要你有什么基础啊~读起来特别轻松,但是却可以学到很多东西,非常推荐了。另外我写过这本书的思维导图,关注我的微信公众号:“Java 面试通关手册”回复“大型网站技术架构”即可领取思维导图。 《亿级流量网站架构核心技术》:一书总结并梳理了亿级流量网站高可用和高并发原则,通过实例详细介绍了如何落地这些原则。本书分为四部分:概述、高可用原则、高并发原则、案例实战。从负载均衡、限流、降级、隔离、超时与重试、回滚机制、压测与预案、缓存、池化、异步化、扩容、队列等多方面详细介绍了亿级流量网站的架构核心技术,让读者看后能快速运用到实践项目中。 软件底层 《深入剖析 Tomcat》:本书深入剖析 Tomcat 4 和 Tomcat 5 中的每个组件,并揭示其内部工作原理。通过学习本书,你将可以自行开发 Tomcat 组件,或者扩展已有的组件。 读完这本书,基本可以摆脱背诵面试题的尴尬。 《深入理解 Nginx(第 2 版)》:作者讲的非常细致,注释都写的都很工整,对于 Nginx 的开发人员非常有帮助。优点是细致,缺点是过于细致,到处都是代码片段,缺少一些抽象。 其他 《深入分析 Java Web 技术内幕》: 感觉还行,涉及的东西也蛮多。 其他 《黑客与画家》:这本书是硅谷创业之父,Y Combinator 创始人 Paul Graham 的文集。之所以叫这个名字,是因为作者认为黑客(并非负面的那个意思)与画家有着极大的相似性,他们都是在创造,而不是完成某个任务。 《图解密码技术》:本书以图配文的形式,第一部分讲述了密码技术的历史沿革、对称密码、分组密码模式(包括ECB、CBC、CFB、OFB、CTR)、公钥、混合密码系统。第二部分重点介绍了认证方面的内容,涉及单向散列函数、消息认证码、数字签名、证书等。第三部分讲述了密钥、随机数、PGP、SSL/TLS 以及密码技术在现实生活中的应用。关键字:JWT 前置知识、区块链密码技术前置知识。属于密码知识入门书籍。 《人月神话》 、《程序开发心理学》 、《程序员修炼之道,从小工道专家》、 《高效程序员的45个习惯,敏捷开发修炼之道》 、《高效能程序员的修炼》 、《软技能,代码之外的生存之道》 、《程序员的职业素养》 、《程序员的思维修炼》","categories":[{"name":"Java","slug":"Java","permalink":"http://yoursite.com/categories/Java/"},{"name":"面试","slug":"面试","permalink":"http://yoursite.com/categories/%E9%9D%A2%E8%AF%95/"}],"tags":[{"name":"Java","slug":"Java","permalink":"http://yoursite.com/tags/Java/"},{"name":"面试","slug":"面试","permalink":"http://yoursite.com/tags/%E9%9D%A2%E8%AF%95/"}]},{"title":"分享一些摘抄的优美句子~","slug":"good","date":"2020-01-14T14:14:44.789Z","updated":"2020-05-12T07:32:52.452Z","comments":true,"path":"2020/01/14/good/","link":"","permalink":"http://yoursite.com/2020/01/14/good/","excerpt":"我是你路上最后一个过客,最后一个春天,最后一场雪,最后一次求生的战争——保尔 艾吕雅","text":"1.有一个夜晚我烧毁了所有的记忆,从此我的梦就透明了。有一个早晨我扔掉了所有的昨天,从此我的脚步就轻盈了。——泰戈尔 2.我是你路上最后一个过客,最后一个春天,最后一场雪,最后一次求生的战争——保尔 艾吕雅 3.虽然我已经十年没见过他,但我知道我会永远想念他。后来我再也没有交过像十二岁时那帮人一样的好朋友。也许,每个人都是这样——罗伯莱纳 4.在喧闹、混杂的生活中你应该与你的内心和平相处。尽管这世上有很多假冒和欺骗,有很多单调乏味的工作和众多破灭的梦幻,他仍然是一个美好的世界。记住:你应该努力的追求幸福。(此文于1692年镌于巴尔的摩圣保罗教堂) 5.我开始真正爱自己,我不再继续沉溺于过去,也不再为明天而忧虑,现在的我只活在一切在发生的当下,今天我活在此时此地,如此日复一日,这就叫”完美”。——《当我开始爱自己》 6.如果有一天你不再寻找爱情,只是去爱;你不再渴望成功,只是去做;你不再追逐成长,只是去修;一切才真正开始——纪伯伦 7.我原谅了从前的自己,就像谅解了一个野心勃勃的傻逼,体恤了一个笨手笨脚的勇士,释怀了一个难以启齿的秘密。 8.原来可以这样爱你,什么也可以说,什么也可以不说。让我采一束月光吧!插在今夜我寂寥的窗前。过了今夜,我不会再有力气为你写诗和流泪。——海烟 9.我不知该如何珍藏明里这份温暖,也不知该将她的灵魂带往何处。我清楚地明白,我们无法保证将来能够永远在一起。横亘在我们面前的是那沉重的人生于漫长时间,让人不由得产生一种无力感——新海诚 10.此刻有谁在世上某处哭,无缘无故在世上哭,在哭我。此刻有谁在夜间某处笑,无缘无故的在夜间笑,在笑我。此刻有谁在世上某处走,无缘无故的在世上走,走向我。此刻有谁在世上某处死,无缘无故在世上死,望着我。——里尔克 11.不惋惜,不呼唤,我也不啼哭。一切将逝去。如苹果花丛的薄雾。金黄的落叶堆满心间,我已不再是青春少年——叶赛宁 12.多希望我知道如何放弃你,你什么都没留给我却活在我心里。 13.最要紧的是,我们首先应该知道善良,其次要诚实,再其次是以后永远不要互相遗忘。 14.我向旧日的恋人道歉,因为我对新人如同初恋。——辛波斯卡 15.我永恒的灵魂,注视着你的心,纵然黑夜孤寂白昼如焚。——兰波 16.我常想,如果我拍够了足够的照片,我就不会再失去任何人。事实上,我的照片让我看到了我失去了多少——南戈尔丁 17.知识让我们愤世嫉俗,聪明让我们铁石心肠。我们想的太多,同情太少,除了机器我们更需要善良,没有这些品质,生命就没有意义。——卓别林 18.你别赤脚走在这片草地上散步,我怕我的花园到处都是星星的碎片。——伊迪斯 索德格朗 19.身体里的碳可以制成九千支铅笔,赠给诗人。身体里的铁,只够打成一枚铁钉,就钉在爱人的心上。 20.岁月有加,并非垂老。理想丢弃,方坠暮年。岁月悠悠,衰微只及肌肤。热忱抛却,颓废必致灵魂。——塞缪尔 厄尔曼 21.情不知所起,一往而深——汤显祖 22.渐渐觉得,友谊这个东西已经被世人捧的太高,它跟永恒其实没有太大关系。换了空间时间,总会有人离去。也总会有与当下的你心有相同的同伴不断出现,来陪你走接下来或短或长的人生。所以不要太念念不忘。也不要期待有什么回响。你要从同路者中寻找同伴,而非硬拽着旧人一起上路。 23.世界上最大的勇气,是压力下的优雅。——海明威 24.为你,千千万万遍——卡勒德 胡塞尼 25.你,一会看我一会看云;你看我时很远,你看云时很近——顾城 26.草在结它的种子 树在摇它的叶子 我们站着 不说话 就十分美好 ——顾城《门前》 27.曾国藩的交友原则,八交九不交: 八交:胜己者;盛德者;趣味者;肯吃亏者;直言者;志趣广大者;惠在当厄者;体人者 九不交:志不同者;谀人者;恩怨颠倒者;全无性情者;不孝不悌者;迂人者;落井下石者;德薄者;好占便宜者。 28.那一天我二十一岁,在我一生的黄金时代。我有好多奢望。我想爱,我想吃,我还想一瞬间变成天上半明半暗的云。——王小波《黄金时代》 29.从童年起,我便独自一人照顾着历代星辰——《孤独》白鹤林 30.这样看你,用所有的眼睛和所有距离,就像风住了,风又起——《沉溺》冯唐 31.人时已尽,人世很长。我在中间应当休息,走过的人说树枝低了,走过的人说树枝在长。——《墓床》顾城 32.万物皆有裂痕,那是光进来的地方。——莱昂纳德 科恩 33.成功只有一种,那就是用自己喜欢的方式度过一生——《明朝那些事》原出于美国记者 34.生活永远不可能像你想象的那么好,但也不会像你想象的那么糟,无论是好的还是糟糕的时候都需要坚强——《人生》莫泊桑 35.谁这时没有房屋,就不必建筑;谁这时孤独,就永远孤独,就醒着,读着,写着长信。在林荫道上来回,不安的游荡,当落叶纷飞。——《秋日》里尔克 36.也许,我太会隐藏自己的悲伤;也许我太会安慰自己的伤;从阴雨走到艳阳,我路过泥泞,路过风——《你若懂我 该多好》 37.我想无论是在塞纳河还是滹沱河边,我们两个人都已经放下彼此,所以我并没有等,她也不会在那个路灯下了,那个灯光下笑盈盈的她和慌张无力却又自大的我,永远属于了二十出头的我们,仅止于此我很满足,毕竟我们都要朝着让自己幸福努力。 38.一个人有两个我,一个在黑暗中醒着,一个在光明中睡着——纪伯伦 39.若我会见到你,事隔经年,我如何向你招呼,以眼泪,以沉默——拜伦 40.枕上诗书闲处好,门前风景雨来佳——李清照 文末 欢迎关注个人微信公众号:Coder编程欢迎关注Coder编程公众号,主要分享数据结构与算法、Java相关知识体系、框架知识及原理、Spring全家桶、微服务项目实战、DevOps实践之路、每日一篇互联网大厂面试或笔试题以及PMP项目管理知识等。更多精彩内容正在路上~也分享一些杂文~ 文章收录至Github: https://github.com/CoderMerlin/coder-programmingGitee: https://gitee.com/573059382/coder-programming欢迎关注并star~","categories":[{"name":"美文","slug":"美文","permalink":"http://yoursite.com/categories/%E7%BE%8E%E6%96%87/"}],"tags":[{"name":"美文","slug":"美文","permalink":"http://yoursite.com/tags/%E7%BE%8E%E6%96%87/"}]},{"title":"Java面试集锦:25道线程类相关面试题与答案(一)","slug":"interview/java/A-thread01","date":"2019-11-14T13:45:00.000Z","updated":"2019-11-14T13:45:00.000Z","comments":true,"path":"2019/11/14/interview/java/A-thread01/","link":"","permalink":"http://yoursite.com/2019/11/14/interview/java/A-thread01/","excerpt":"线程是什么?进程是什么?二者有什么区别和联系? 线程和进程各自有什么区别和优劣呢? 创建线程有几种不同的方式?你喜欢哪一种?为什么? 概括的解释下线程的几种可用状态? 点击查看更多","text":"每天进步一点,不做curd工程师与Api调用工程师欢迎访问个人网站:https://www.coder-programming.cn/ 1. 线程是什么?进程是什么?二者有什么区别和联系?(1)线程是CPU独立运行和独立调度的基本单位;(2)进程是资源分配的基本单位;是执行着的应用程序两者的联系:进程和线程都是操作系统所运行的程序运行的基本单元。 区别:(1)进程具有独立的空间地址,一个进程崩溃后,在保护模式下不会对其它进程产生影响。(2)线程只是一个进程的不同执行路径,线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉。 进程是执行着的应用程序,而线程是进程内部的一个执行序列。一个进程可以有多个线程。线程又叫做轻量级进程。 2. 线程和进程各自有什么区别和优劣呢?进程是资源分配的最小单位,线程是程序执行的最小单位。 进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。而线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。 线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点。 但是多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。 可参考文章 3. 创建线程有几种不同的方式?你喜欢哪一种?为什么?有三种方式可以用来创建线程: 继承Thread类 实现Runnable接口 应用程序可以使用Executor框架来创建线程池 实现Runnable接口这种方式更受欢迎,因为这不需要继承Thread类。在应用设计中已经继承了别的对象的情况下,这需要多继承(而Java不支持多继承),只能实现接口。同时,线程池也是非常高效的,很容易实现和使用。 4. 概括的解释下线程的几种可用状态?线程在执行过程中,可以处于下面几种状态: 就绪(Runnable):线程准备运行,不一定立马就能开始执行。 运行中(Running):进程正在执行线程的代码。 等待中(Waiting):线程处于阻塞的状态,等待外部的处理结束。 睡眠中(Sleeping):线程被强制睡眠。 I/O阻塞(Blocked on I/O):等待I/O操作完成。 同步阻塞(Blocked on Synchronization):等待获取锁。 死亡(Dead):线程完成了执行。 5. 同步方法和同步代码块的区别是什么?在Java语言中,每一个对象有一把锁。线程可以使用synchronized关键字来获取对象上的锁。synchronized关键字可应用在方法级别(粗粒度锁)或者是代码块级别(细粒度锁)。 Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。 (1)、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。 (2)、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。 (3)、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。 (4)、第三个例子同样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。 (5)、以上规则对其它对象锁同样适用。 6. 在监视器(Monitor)内部,是如何做线程同步的?程序应该做哪种级别的同步?监视器和锁在Java虚拟机中是一块使用的。监视器监视一块同步代码块,确保一次只有一个线程执行同步代码块。每一个监视器都和一个对象引用相关联。线程在获取锁之前不允许执行同步代码。 7. 什么是死锁(deadlock)?两个进程都在等待对方执行完毕才能继续往下执行的时候就发生了死锁。结果就是两个进程都陷入了无限的等待中。 8. 如何确保N个线程可以访问N个资源同时又不导致死锁?使用多线程的时候,一种非常简单的避免死锁的方式就是:指定获取锁的顺序,并强制线程按照指定的顺序获取锁。因此,如果所有的线程都是以同样的顺序加锁和释放锁,就不会出现死锁了。 9. 如何避免死锁?多线程产生死锁的四个必要条件:互斥条件: 一个资源每次只能被一个进程使用。保持和请求条件: 一个进程因请求资源而阻塞时,对已获得资源保持不放。不可剥夺调教: 进程已获得资源,在未使用完成前,不能被剥夺。循环等待条件: 若干进程之间形成一种头尾相接的循环等待资源关系。 只要破坏其中任意一个条件,就可以避免死锁,其中最简单的就是破环循环等待条件。按同一顺序访问对象,加载锁,释放锁。 10. Thread 类中的start() 和 run() 方法有什么区别?start()方法被用来启动新创建的线程,使该被创建的线程状态变为可运行状态。当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启动,start()方法才会启动新线程。如果我们调用了Thread的run()方法,它的行为就会和普通的方法一样,直接运行run()方法。为了在新的线程中执行我们的代码,必须使用Thread.start()方法。 11. Java中Runnable和Callable有什么不同?Runnable和Callable都代表那些要在不同的线程中执行的任务。Runnable从JDK1.0开始就有了,Callable是在JDK1.5增加的。它们的主要区别是Callable的 call() 方法可以返回值和抛出异常,而Runnable的run()方法没有这些功能。Callable可以返回装载有计算结果的Future对象。 12. Java中什么是竞态条件?在大多数实际的多线程应用中,两个或两个以上的线程需要共享对同一数据的存取。如果i线程存取相同的对象,并且每一个线程都调用了一个修改该对象状态的方法,将会发生什么呢?可以想象,线程彼此踩了对方的脚。根据线程访问数据的次序,可能会产生讹误的对象。这样的情况通常称为竞争条件。 13. Java中如何停止一个线程?Java提供了很丰富的API但没有为停止线程提供API。JDK 1.0本来有一些像stop(), suspend() 和 resume()的控制方法,但是由于潜在的死锁威胁。因此在后续的JDK版本中他们被弃用了,之后Java API的设计者就没有提供一个兼容且线程安全的方法来停止一个线程。当run()或者 call() 方法执行完的时候线程会自动结束,如果要手动结束一个线程,可以用volatile 布尔变量来退出run()方法的循环或者是取消任务来中断线程。 14. Java中notify 和 notifyAll有什么区别?一个很明显的原因是JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。如果线程需要等待某些锁那么调用对象中的wait()方法就有意义了。如果wait()方法定义在Thread类中,线程正在等待的是哪个锁就不明显了。简单的说,由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中因为锁属于对象。 15. Java中的同步集合与并发集合有什么区别?同步集合与并发集合都为多线程和并发提供了合适的线程安全的集合,不过并发集合的可扩展性更高。在Java1.5之前程序员们只有同步集合来用且在多线程并发的时候会导致争用,阻碍了系统的扩展性。Java5介绍了并发集合像ConcurrentHashMap,不仅提供线程安全还用锁分离和内部分区等现代技术提高了可扩展性。 16. 什么是线程池?线程池是一种多线程处理形式,处理过程中将任务提交到线程池,任务的执行交由线程池来管理。如果每个请求都创建一个线程去处理,那么服务器的资源很快就会被耗尽,使用线程池可以减少创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。 17. 为什么要使用线程池?创建线程和销毁线程的花销是比较大的,这些时间有可能比处理业务的时间还要长。这样频繁的创建线程和销毁线程,再加上业务工作线程,消耗系统资源的时间,可能导致系统资源不足。(我们可以把创建和销毁的线程的过程去掉) 18. 线程池有什么作用?线程池作用就是限制系统中执行线程的数量。 1、提高效率 创建好一定数量的线程放在池中,等需要使用的时候就从池中拿一个,这要比需要的时候创建一个线程对象要快的多。 2、方便管理 可以编写线程池管理代码对池中的线程同一进行管理,比如说启动时有该程序创建100个线程,每当有请求的时候,就分配一个线程去工作,如果刚好并发有101个请求,那多出的这一个请求可以排队等候,避免因无休止的创建线程导致系统崩溃。 19. 说说几种常见的线程池及使用场景?1、newSingleThreadExecutor创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。 2、newFixedThreadPool创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。 3、newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。 4、newScheduledThreadPool创建一个定长线程池,支持定时及周期性任务执行。 20. 线程池中的几种重要的参数?corePoolSize就是线程池中的核心线程数量,这几个核心线程,只是在没有用的时候,也不会被回收 maximumPoolSize就是线程池中可以容纳的最大线程的数量 keepAliveTime,就是线程池中除了核心线程之外的其他的最长可以保留的时间,因为在线程池中,除了核心线程即使在无任务的情况下也不能被清除,其余的都是有存活时间的,意思就是非核心线程可以保留的最长的空闲时间。util,就是计算这个时间的一个单位。 workQueue,就是等待队列,任务可以储存在任务队列中等待被执行,执行的是FIFIO原则(先进先出)。 threadFactory,就是创建线程的线程工厂。 handler,是一种拒绝策略,我们可以在任务满了之后,拒绝执行某些任务。 21. 说说线程池的拒绝策略?当请求任务不断的过来,而系统此时又处理不过来的时候,我们需要采取的策略是拒绝服务。RejectedExecutionHandler接口提供了拒绝任务处理的自定义方法的机会。在ThreadPoolExecutor中已经包含四种处理策略。 AbortPolicy策略:该策略会直接抛出异常,阻止系统正常工作。 CallerRunsPolicy 策略:只要线程池未关闭,该策略直接在调用者线程中,运行当前的被丢弃的任务。 DiscardOleddestPolicy策略: 该策略将丢弃最老的一个请求,也就是即将被执行的任务,并尝试再次提交当前任务。 DiscardPolicy策略:该策略默默的丢弃无法处理的任务,不予任何处理。 除了JDK默认提供的四种拒绝策略,我们可以根据自己的业务需求去自定义拒绝策略,自定义的方式很简单,直接实现RejectedExecutionHandler接口即可。 22. execute和submit的区别?我们执行任务是用的execute方法,除了execute方法,还有一个submit方法也可以执行我们提交的任务。 这两个方法有什么区别呢?分别适用于在什么场景下呢?我们来做一个简单的分析。 execute适用于不需要关注返回值的场景,只需要将线程丢到线程池中去执行就可以了。 submit方法适用于需要关注返回值的场景 23. 五种线程池的使用场景? newSingleThreadExecutor:一个单线程的线程池,可以用于需要保证顺序执行的场景,并且只有一个线程在执行。 newFixedThreadPool:一个固定大小的线程池,可以用于已知并发压力的情况下,对线程数做限制。 newCachedThreadPool:一个可以无限扩大的线程池,比较适合处理执行时间比较小的任务。 newScheduledThreadPool:可以延时启动,定时启动的线程池,适用于需要多个后台线程执行周期任务的场景。 newWorkStealingPool:一个拥有多个任务队列的线程池,可以减少连接数,创建当前可用cpu数量的线程来并行执行。 24. 线程池如何关闭? 初始化线程池时线程数的选择?关闭线程池可以调用shutdownNow和shutdown两个方法来实现 shutdownNow:对正在执行的任务全部发出interrupt(),停止执行,对还未开始执行的任务全部取消,并且返回还没开始的任务列表。 shutdown:当我们调用shutdown后,线程池将不再接受新的任务,但也不会去强制终止已经提交或者正在执行中的任务。 如果任务是IO密集型,一般线程数需要设置2倍CPU数以上,以此来尽量利用CPU资源。 如果任务是CPU密集型,一般线程数量只需要设置CPU数加1即可,更多的线程数也只能增加上下文切换,不能增加CPU利用率。 上述只是一个基本思想,如果真的需要精确的控制,还是需要上线以后观察线程池中线程数量跟队列的情况来定。 25. 线程池都有哪几种工作队列?1、ArrayBlockingQueue 是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。 2、LinkedBlockingQueue一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列 3、SynchronousQueue 一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。 4、PriorityBlockingQueue 一个具有优先级的无限阻塞队列。 推荐大厂笔试内容集合(内有详细解析) 持续更新中…. ProcessOn是一个在线作图工具的聚合平台~ 文末 欢迎关注个人微信公众号:Coder编程欢迎关注Coder编程公众号,主要分享数据结构与算法、Java相关知识体系、框架知识及原理、Spring全家桶、微服务项目实战、DevOps实践之路、每日一篇互联网大厂面试或笔试题以及PMP项目管理知识等。更多精彩内容正在路上~新建了一个qq群:315211365,欢迎大家进群交流一起学习。谢谢了!也可以介绍给身边有需要的朋友。 文章收录至Github: https://github.com/CoderMerlin/coder-programmingGitee: https://gitee.com/573059382/coder-programming欢迎关注并star~","categories":[{"name":"Java","slug":"Java","permalink":"http://yoursite.com/categories/Java/"},{"name":"面试","slug":"面试","permalink":"http://yoursite.com/categories/%E9%9D%A2%E8%AF%95/"}],"tags":[{"name":"Java","slug":"Java","permalink":"http://yoursite.com/tags/Java/"},{"name":"面试","slug":"面试","permalink":"http://yoursite.com/tags/%E9%9D%A2%E8%AF%95/"},{"name":"多线程","slug":"多线程","permalink":"http://yoursite.com/tags/%E5%A4%9A%E7%BA%BF%E7%A8%8B/"}]},{"title":"不可不说的Java“锁”事","slug":"java/Lock","date":"2018-11-15T12:06:09.000Z","updated":"2018-11-15T12:06:09.000Z","comments":true,"path":"2018/11/15/java/Lock/","link":"","permalink":"http://yoursite.com/2018/11/15/java/Lock/","excerpt":"面试 乐观锁 VS 悲观锁 … 自旋锁 VS 适应性自旋锁 … 公平锁 VS 非公平锁 …","text":"来源:httpstech.meituan.com20181115java-lock.html 前言Java提供了种类丰富的锁,每种锁因其特性的不同,在适当的场景下能够展现出非常高的效率。本文旨在对锁相关源码(本文中的源码来自JDK 8和Netty 3.10.6)、使用场景进行举例,为读者介绍主流锁的知识点,以及不同的锁的适用场景。 Java中往往是按照是否含有某一特性来定义锁,我们通过特性将锁进行分组归类,再使用对比的方式进行介绍,帮助大家更快捷的理解相关知识。下面给出本文内容的总体分类目录: 1. 乐观锁 VS 悲观锁乐观锁与悲观锁是一种广义上的概念,体现了看待线程同步的不同角度。在Java和数据库中都有此概念对应的实际应用。 先说概念。对于同一个数据的并发操作,悲观锁认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。Java中,synchronized关键字和Lock的实现类都是悲观锁。 而乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作(例如报错或者自动重试)。 乐观锁在Java中是通过使用无锁编程来实现,最常采用的是CAS算法,Java原子类中的递增操作就通过CAS自旋实现的。 根据从上面的概念描述我们可以发现: 悲观锁适合写操作多的场景,先加锁可以保证写操作时数据正确。 乐观锁适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升。 光说概念有些抽象,我们来看下乐观锁和悲观锁的调用方式示例: 12345678910111213141516 ------------------------- 悲观锁的调用方式 ------------------------- synchronizedpublic synchronized void testMethod() { 操作同步资源} ReentrantLockprivate ReentrantLock lock = new ReentrantLock(); 需要保证多个线程使用的是同一个锁public void modifyPublicResources() { lock.lock(); 操作同步资源 lock.unlock();} ------------------------- 乐观锁的调用方式 -------------------------private AtomicInteger atomicInteger = new AtomicInteger(); 需要保证多个线程使用的是同一个AtomicIntegeratomicInteger.incrementAndGet(); 执行自增1 通过调用方式示例,我们可以发现悲观锁基本都是在显式的锁定之后再操作同步资源,而乐观锁则直接去操作同步资源。那么,为何乐观锁能够做到不锁定同步资源也可以正确的实现线程同步呢?我们通过介绍乐观锁的主要实现方式 “CAS” 的技术原理来为大家解惑。 CAS全称 Compare And Swap(比较与交换),是一种无锁算法。在不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步。java.util.concurrent包中的原子类就是通过CAS来实现了乐观锁。 CAS算法涉及到三个操作数: 需要读写的内存值 V。 进行比较的值 A。 要写入的新值 B。 当且仅当 V 的值等于 A 时,CAS通过原子方式用新值B来更新V的值(“比较+更新”整体是一个原子操作),否则不会执行任何操作。一般情况下,“更新”是一个不断重试的操作。 之前提到java.util.concurrent包中的原子类,就是通过CAS来实现了乐观锁,那么我们进入原子类AtomicInteger的源码,看一下AtomicInteger的定义: 根据定义我们可以看出各属性的作用: unsafe: 获取并操作内存的数据。 valueOffset: 存储value在AtomicInteger中的偏移量。 value: 存储AtomicInteger的int值,该属性需要借助volatile关键字保证其在线程间是可见的。 接下来,我们查看AtomicInteger的自增函数incrementAndGet()的源码时,发现自增函数底层调用的是unsafe.getAndAddInt()。但是由于JDK本身只有Unsafe.class,只通过class文件中的参数名,并不能很好的了解方法的作用,所以我们通过OpenJDK 8 来查看Unsafe的源码: 123456789101112131415161718192021222324 ------------------------- JDK 8 ------------------------- AtomicInteger 自增方法public final int incrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1;} Unsafe.classpublic final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5;} ------------------------- OpenJDK 8 ------------------------- Unsafe.javapublic final int getAndAddInt(Object o, long offset, int delta) { int v; do { v = getIntVolatile(o, offset); } while (!compareAndSwapInt(o, offset, v, v + delta)); return v;} 根据OpenJDK 8的源码我们可以看出,getAndAddInt()循环获取给定对象o中的偏移量处的值v,然后判断内存值是否等于v。如果相等则将内存值设置为 v + delta,否则返回false,继续循环进行重试,直到设置成功才能退出循环,并且将旧值返回。整个“比较+更新”操作封装在compareAndSwapInt()中,在JNI里是借助于一个CPU指令完成的,属于原子操作,可以保证多个线程都能够看到同一个变量的修改值。 后续JDK通过CPU的cmpxchg指令,去比较寄存器中的 A 和 内存中的值 V。如果相等,就把要写入的新值 B 存入内存中。如果不相等,就将内存值 V 赋值给寄存器中的值 A。然后通过Java代码中的while循环再次调用cmpxchg指令进行重试,直到设置成功为止。 CAS虽然很高效,但是它也存在三大问题,这里也简单说一下: ABA问题。CAS需要在操作值的时候检查内存值是否发生变化,没有发生变化才会更新内存值。但是如果内存值原来是A,后来变成了B,然后又变成了A,那么CAS进行检查时会发现值没有发生变化,但是实际上是有变化的。ABA问题的解决思路就是在变量前面添加版本号,每次变量更新的时候都把版本号加一,这样变化过程就从“A-B-A”变成了“1A-2B-3A”。 JDK从1.5开始提供了AtomicStampedReference类来解决ABA问题,具体操作封装在compareAndSet()中。compareAndSet()首先检查当前引用和当前标志与预期引用和预期标志是否相等,如果都相等,则以原子方式将引用值和标志的值设置为给定的更新值。 循环时间长开销大。CAS操作如果长时间不成功,会导致其一直自旋,给CPU带来非常大的开销。 只能保证一个共享变量的原子操作。对一个共享变量执行操作时,CAS能够保证原子操作,但是对多个共享变量操作时,CAS是无法保证操作的原子性的。 Java从1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,可以把多个变量放在一个对象里来进行CAS操作。 2. 自旋锁 VS 适应性自旋锁在介绍自旋锁前,我们需要介绍一些前提知识来帮助大家明白自旋锁的概念。 阻塞或唤醒一个Java线程需要操作系统切换CPU状态来完成,这种状态转换需要耗费处理器时间。如果同步代码块中的内容过于简单,状态转换消耗的时间有可能比用户代码执行的时间还要长。 在许多场景中,同步资源的锁定时间很短,为了这一小段时间去切换线程,线程挂起和恢复现场的花费可能会让系统得不偿失。如果物理机器有多个处理器,能够让两个或以上的线程同时并行执行,我们就可以让后面那个请求锁的线程不放弃CPU的执行时间,看看持有锁的线程是否很快就会释放锁。 而为了让当前线程“稍等一下”,我们需让当前线程进行自旋,如果在自旋完成后前面锁定同步资源的线程已经释放了锁,那么当前线程就可以不必阻塞而是直接获取同步资源,从而避免切换线程的开销。这就是自旋锁。 自旋锁本身是有缺点的,它不能代替阻塞。自旋等待虽然避免了线程切换的开销,但它要占用处理器时间。如果锁被占用的时间很短,自旋等待的效果就会非常好。反之,如果锁被占用的时间很长,那么自旋的线程只会白浪费处理器资源。所以,自旋等待的时间必须要有一定的限度,如果自旋超过了限定次数(默认是10次,可以使用-XXPreBlockSpin来更改)没有成功获得锁,就应当挂起线程。 自旋锁的实现原理同样也是CAS,AtomicInteger中调用unsafe进行自增操作的源码中的do-while循环就是一个自旋操作,如果修改数值失败则通过循环来执行自旋,直至修改成功。 自旋锁在JDK1.4.2中引入,使用-XX+UseSpinning来开启。JDK 6中变为默认开启,并且引入了自适应的自旋锁(适应性自旋锁)。 自适应意味着自旋的时间(次数)不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也是很有可能再次成功,进而它将允许自旋等待持续相对更长的时间。如果对于某个锁,自旋很少成功获得过,那在以后尝试获取这个锁时将可能省略掉自旋过程,直接阻塞线程,避免浪费处理器资源。 在自旋锁中 另有三种常见的锁形式TicketLock、CLHlock和MCSlock,本文中仅做名词介绍,不做深入讲解,感兴趣的同学可以自行查阅相关资料。 3. 无锁 VS 偏向锁 VS 轻量级锁 VS 重量级锁这四种锁是指锁的状态,专门针对synchronized的。在介绍这四种锁状态之前还需要介绍一些额外的知识。 首先为什么Synchronized能实现线程同步? 在回答这个问题之前我们需要了解两个重要的概念:“Java对象头”、“Monitor”。 Java对象头synchronized是悲观锁,在操作同步资源之前需要给同步资源先加锁,这把锁就是存在Java对象头里的,而Java对象头又是什么呢? 我们以Hotspot虚拟机为例,Hotspot的对象头主要包括两部分数据:Mark Word(标记字段)、Klass Pointer(类型指针)。 Mark Word:默认存储对象的HashCode,分代年龄和锁标志位信息。这些信息都是与对象自身定义无关的数据,所以Mark Word被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据。它会根据对象的状态复用自己的存储空间,也就是说在运行期间Mark Word里存储的数据会随着锁标志位的变化而变化。 Klass Point:对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。 MonitorMonitor可以理解为一个同步工具或一种同步机制,通常被描述为一个对象。每一个Java对象就有一把看不见的锁,称为内部锁或者Monitor锁。 Monitor是线程私有的数据结构,每一个线程都有一个可用monitor record列表,同时还有一个全局的可用列表。每一个被锁住的对象都会和一个monitor关联,同时monitor中有一个Owner字段存放拥有该锁的线程的唯一标识,表示该锁被这个线程占用。 现在话题回到synchronized,synchronized通过Monitor来实现线程同步,Monitor是依赖于底层的操作系统的Mutex Lock(互斥锁)来实现的线程同步。 如同我们在自旋锁中提到的“阻塞或唤醒一个Java线程需要操作系统切换CPU状态来完成,这种状态转换需要耗费处理器时间。如果同步代码块中的内容过于简单,状态转换消耗的时间有可能比用户代码执行的时间还要长”。这种方式就是synchronized最初实现同步的方式,这就是JDK 6之前synchronized效率低的原因。这种依赖于操作系统Mutex Lock所实现的锁我们称之为“重量级锁”,JDK 6中为了减少获得锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁”。 所以目前锁一共有4种状态,级别从低到高依次是:无锁、偏向锁、轻量级锁和重量级锁。锁状态只能升级不能降级。 通过上面的介绍,我们对synchronized的加锁机制以及相关知识有了一个了解,那么下面我们给出四种锁状态对应的的Mark Word内容,然后再分别讲解四种锁状态的思路以及特点: 锁状态 存储内容 存储内容 无锁 对象的hashCode、对象分代年龄、是否是偏向锁(0) 01 偏向锁 偏向线程ID、偏向时间戳、对象分代年龄、是否是偏向锁(1) 01 轻量级锁 指向栈中锁记录的指针 00 重量级锁 指向互斥量(重量级锁)的指针 10 无锁 无锁没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功。 无锁的特点就是修改操作在循环内进行,线程会不断的尝试修改共享资源。如果没有冲突就修改成功并退出,否则就会继续循环尝试。如果有多个线程修改同一个值,必定会有一个线程能修改成功,而其他修改失败的线程会不断重试直到修改成功。上面我们介绍的CAS原理及应用即是无锁的实现。无锁无法全面代替有锁,但无锁在某些场合下的性能是非常高的。 偏向锁 偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价。 在大多数情况下,锁总是由同一线程多次获得,不存在多线程竞争,所以出现了偏向锁。其目标就是在只有一个线程执行同步代码块时能够提高性能。 当一个线程访问同步代码块并获取锁时,会在Mark Word里存储锁偏向的线程ID。在线程进入和退出同步块时不再通过CAS操作来加锁和解锁,而是检测Mark Word里是否存储着指向当前线程的偏向锁。引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取及释放依赖多次CAS原子指令,而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令即可。 偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动释放偏向锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有字节码正在执行),它会首先暂停拥有偏向锁的线程,判断锁对象是否处于被锁定状态。撤销偏向锁后恢复到无锁(标志位为“01”)或轻量级锁(标志位为“00”)的状态。 偏向锁在JDK 6及以后的JVM里是默认启用的。可以通过JVM参数关闭偏向锁:-XX-UseBiasedLocking=false,关闭之后程序默认会进入轻量级锁状态。 轻量级锁 是指当锁是偏向锁的时候,被另外的线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能。 在代码进入同步块的时候,如果同步对象锁状态为无锁状态(锁标志位为“01”状态,是否为偏向锁为“0”),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝,然后拷贝对象头中的Mark Word复制到锁记录中。 拷贝成功后,虚拟机将使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针,并将Lock Record里的owner指针指向对象的Mark Word。 如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象Mark Word的锁标志位设置为“00”,表示此对象处于轻量级锁定状态。 如果轻量级锁的更新操作失败了,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行,否则说明多个线程竞争锁。 若当前只有一个等待线程,则该线程通过自旋进行等待。但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁升级为重量级锁。 重量级锁 升级为重量级锁时,锁标志的状态值变为“10”,此时Mark Word中存储的是指向重量级锁的指针,此时等待锁的线程都会进入阻塞状态。 整体的锁状态升级流程如下: 综上,偏向锁通过对比Mark Word解决加锁问题,避免执行CAS操作。而轻量级锁是通过用CAS操作和自旋来解决加锁问题,避免线程阻塞和唤醒而影响性能。重量级锁是将除了拥有锁的线程以外的线程都阻塞。 4. 公平锁 VS 非公平锁公平锁是指多个线程按照申请锁的顺序来获取锁,线程直接进入队列中排队,队列中的第一个线程才能获得锁。公平锁的优点是等待锁的线程不会饿死。缺点是整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞,CPU唤醒阻塞线程的开销比非公平锁大。 非公平锁是多个线程加锁时直接尝试获取锁,获取不到才会到等待队列的队尾等待。但如果此时锁刚好可用,那么这个线程可以无需阻塞直接获取到锁,所以非公平锁有可能出现后申请锁的线程先获取锁的场景。非公平锁的优点是可以减少唤起线程的开销,整体的吞吐效率高,因为线程有几率不阻塞直接获得锁,CPU不必唤醒所有线程。缺点是处于等待队列中的线程可能会饿死,或者等很久才会获得锁。 直接用语言描述可能有点抽象,这里作者用从别处看到的一个例子来讲述一下公平锁和非公平锁。 如上图所示,假设有一口水井,有管理员看守,管理员有一把锁,只有拿到锁的人才能够打水,打完水要把锁还给管理员。每个过来打水的人都要管理员的允许并拿到锁之后才能去打水,如果前面有人正在打水,那么这个想要打水的人就必须排队。管理员会查看下一个要去打水的人是不是队伍里排最前面的人,如果是的话,才会给你锁让你去打水;如果你不是排第一的人,就必须去队尾排队,这就是公平锁。 但是对于非公平锁,管理员对打水的人没有要求。即使等待队伍里有排队等待的人,但如果在上一个人刚打完水把锁还给管理员而且管理员还没有允许等待队伍里下一个人去打水时,刚好来了一个插队的人,这个插队的人是可以直接从管理员那里拿到锁去打水,不需要排队,原本排队等待的人只能继续等待。如下图所示: 接下来我们通过ReentrantLock的源码来讲解公平锁和非公平锁。 根据代码可知,ReentrantLock里面有一个内部类Sync,Sync继承AQS(AbstractQueuedSynchronizer),添加锁和释放锁的大部分操作实际上都是在Sync中实现的。它有公平锁FairSync和非公平锁NonfairSync两个子类。ReentrantLock默认使用非公平锁,也可以通过构造器来显示的指定使用公平锁。 下面我们来看一下公平锁与非公平锁的加锁方法的源码 通过上图中的源代码对比,我们可以明显的看出公平锁与非公平锁的lock()方法唯一的区别就在于公平锁在获取同步状态时多了一个限制条件:hasQueuedPredecessors()。 再进入hasQueuedPredecessors(),可以看到该方法主要做一件事情:主要是判断当前线程是否位于同步队列中的第一个。如果是则返回true,否则返回false。 综上,公平锁就是通过同步队列来实现多个线程按照申请锁的顺序来获取锁,从而实现公平的特性。非公平锁加锁时不考虑排队等待问题,直接尝试获取锁,所以存在后申请却先获得锁的情况。 5. 可重入锁 VS 非可重入锁可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象或者class),不会因为之前已经获取过还没释放而阻塞。Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。下面用示例代码来进行分析: 12345678910public class Widget { public synchronized void doSomething() { System.out.println(方法1执行...); doOthers(); } public synchronized void doOthers() { System.out.println(方法2执行...); }} 在上面的代码中,类中的两个方法都是被内置锁synchronized修饰的,doSomething()方法中调用doOthers()方法。因为内置锁是可重入的,所以同一个线程在调用doOthers()时可以直接获得当前对象的锁,进入doOthers()进行操作。 如果是一个不可重入锁,那么当前线程在调用doOthers()之前需要将执行doSomething()时获取当前对象的锁释放掉,实际上该对象锁已被当前线程所持有,且无法释放。所以此时会出现死锁。 而为什么可重入锁就可以在嵌套调用时可以自动获得锁呢?我们通过图示和源码来分别解析一下。 还是打水的例子,有多个人在排队打水,此时管理员允许锁和同一个人的多个水桶绑定。这个人用多个水桶打水时,第一个水桶和锁绑定并打完水之后,第二个水桶也可以直接和锁绑定并开始打水,所有的水桶都打完水之后打水人才会将锁还给管理员。这个人的所有打水流程都能够成功执行,后续等待的人也能够打到水。这就是可重入锁。 但如果是非可重入锁的话,此时管理员只允许锁和同一个人的一个水桶绑定。第一个水桶和锁绑定打完水之后并不会释放锁,导致第二个水桶不能和锁绑定也无法打水。当前线程出现死锁,整个等待队列中的所有线程都无法被唤醒。 之前我们说过ReentrantLock和synchronized都是重入锁,那么我们通过重入锁ReentrantLock以及非可重入锁NonReentrantLock的源码来对比分析一下为什么非可重入锁在重复调用同步资源时会出现死锁。 首先ReentrantLock和NonReentrantLock都继承父类AQS,其父类AQS中维护了一个同步状态status来计数重入次数,status初始值为0。 当线程尝试获取锁时,可重入锁先尝试获取并更新status值,如果status == 0表示没有其他线程在执行同步代码,则把status置为1,当前线程开始执行。如果status != 0,则判断当前线程是否是获取到这个锁的线程,如果是的话执行status+1,且当前线程可以再次获取锁。而非可重入锁是直接去获取并尝试更新当前status的值,如果status != 0的话会导致其获取锁失败,当前线程阻塞。 释放锁时,可重入锁同样先获取当前status的值,在当前线程是持有锁的线程的前提下。如果status-1 == 0,则表示当前线程所有重复获取锁的操作都已经执行完毕,然后该线程才会真正释放锁。而非可重入锁则是在确定当前线程是持有锁的线程之后,直接将status置为0,将锁释放。 6. 独享锁 VS 共享锁独享锁和共享锁同样是一种概念。我们先介绍一下具体的概念,然后通过ReentrantLock和ReentrantReadWriteLock的源码来介绍独享锁和共享锁。 独享锁也叫排他锁,是指该锁一次只能被一个线程所持有。如果线程T对数据A加上排它锁后,则其他线程不能再对A加任何类型的锁。获得排它锁的线程即能读数据又能修改数据。JDK中的synchronized和JUC中Lock的实现类就是互斥锁。 共享锁是指该锁可被多个线程所持有。如果线程T对数据A加上共享锁后,则其他线程只能对A再加共享锁,不能加排它锁。获得共享锁的线程只能读数据,不能修改数据。 独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。 下图为ReentrantReadWriteLock的部分源码: 我们看到ReentrantReadWriteLock有两把锁:ReadLock和WriteLock,由词知意,一个读锁一个写锁,合称“读写锁”。再进一步观察可以发现ReadLock和WriteLock是靠内部类Sync实现的锁。Sync是AQS的一个子类,这种结构在CountDownLatch、ReentrantLock、Semaphore里面也都存在。 在ReentrantReadWriteLock里面,读锁和写锁的锁主体都是Sync,但读锁和写锁的加锁方式不一样。读锁是共享锁,写锁是独享锁。读锁的共享锁可保证并发读非常高效,而读写、写读、写写的过程互斥,因为读锁和写锁是分离的。所以ReentrantReadWriteLock的并发性相比一般的互斥锁有了很大提升。 那读锁和写锁的具体加锁方式有什么区别呢?在了解源码之前我们需要回顾一下其他知识。 在最开始提及AQS的时候我们也提到了state字段(int类型,32位),该字段用来描述有多少线程获持有锁。 在独享锁中这个值通常是0或者1(如果是重入锁的话state值就是重入的次数),在共享锁中state就是持有锁的数量。但是在ReentrantReadWriteLock中有读、写两把锁,所以需要在一个整型变量state上分别描述读锁和写锁的数量(或者也可以叫状态)。于是将state变量“按位切割”切分成了两个部分,高16位表示读锁状态(读锁个数),低16位表示写锁状态(写锁个数)。如下图所示: 了解了概念之后我们再来看代码,先看写锁的加锁源码: 12345678910111213141516171819protected final boolean tryAcquire(int acquires) { Thread current = Thread.currentThread(); int c = getState(); 取到当前锁的个数 int w = exclusiveCount(c); 取写锁的个数w if (c != 0) { 如果已经有线程持有了锁(c!=0) (Note if c != 0 and w == 0 then shared count != 0) if (w == 0 current != getExclusiveOwnerThread()) 如果写线程数(w)为0(换言之存在读锁) 或者持有锁的线程不是当前线程就返回失败 return false; if (w + exclusiveCount(acquires) MAX_COUNT) 如果写入锁的数量大于最大数(65535,2的16次方-1)就抛出一个Error。 throw new Error(Maximum lock count exceeded); Reentrant acquire setState(c + acquires); return true; } if (writerShouldBlock() !compareAndSetState(c, c + acquires)) 如果当且写线程数为0,并且当前线程需要阻塞那么就返回失败;或者如果通过CAS增加写线程数失败也返回失败。 return false; setExclusiveOwnerThread(current); 如果c=0,w=0或者c0,w0(重入),则设置当前线程或锁的拥有者 return true;} 这段代码首先取到当前锁的个数c,然后再通过c来获取写锁的个数w。因为写锁是低16位,所以取低16位的最大值与当前的c做与运算( int w = exclusiveCount©; ),高16位和0与运算后是0,剩下的就是低位运算的值,同时也是持有写锁的线程数目。 在取到写锁线程的数目后,首先判断是否已经有线程持有了锁。如果已经有线程持有了锁(c!=0),则查看当前写锁线程的数目,如果写线程数为0(即此时存在读锁)或者持有锁的线程不是当前线程就返回失败(涉及到公平锁和非公平锁的实现)。 如果写入锁的数量大于最大数(65535,2的16次方-1)就抛出一个Error。 如果当且写线程数为0(那么读线程也应该为0,因为上面已经处理c!=0的情况),并且当前线程需要阻塞那么就返回失败;如果通过CAS增加写线程数失败也返回失败。 如果c=0,w=0或者c0,w0(重入),则设置当前线程或锁的拥有者,返回成功! tryAcquire()除了重入条件(当前线程为获取了写锁的线程)之外,增加了一个读锁是否存在的判断。如果存在读锁,则写锁不能被获取,原因在于:必须确保写锁的操作对读锁可见,如果允许读锁在已被获取的情况下对写锁的获取,那么正在运行的其他读线程就无法感知到当前写线程的操作。 因此,只有等待其他读线程都释放了读锁,写锁才能被当前线程获取,而写锁一旦被获取,则其他读写线程的后续访问均被阻塞。写锁的释放与ReentrantLock的释放过程基本类似,每次释放均减少写状态,当写状态为0时表示写锁已被释放,然后等待的读写线程才能够继续访问读写锁,同时前次写线程的修改对后续的读写线程可见。 接着是读锁的代码: 123456789101112131415161718192021222324252627protected final int tryAcquireShared(int unused) { Thread current = Thread.currentThread(); int c = getState(); if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) return -1; 如果其他线程已经获取了写锁,则当前线程获取读锁失败,进入等待状态 int r = sharedCount(c); if (!readerShouldBlock() && r MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) { if (r == 0) { firstReader = current; firstReaderHoldCount = 1; } else if (firstReader == current) { firstReaderHoldCount++; } else { HoldCounter rh = cachedHoldCounter; if (rh == null rh.tid != getThreadId(current)) cachedHoldCounter = rh = readHolds.get(); else if (rh.count == 0) readHolds.set(rh); rh.count++; } return 1; } return fullTryAcquireShared(current);} 可以看到在tryAcquireShared(int unused)方法中,如果其他线程已经获取了写锁,则当前线程获取读锁失败,进入等待状态。如果当前线程获取了写锁或者写锁未被获取,则当前线程(线程安全,依靠CAS保证)增加读状态,成功获取读锁。读锁的每次释放(线程安全的,可能有多个读线程同时释放读锁)均减少读状态,减少的值是“116”。所以读写锁才能实现读读的过程共享,而读写、写读、写写的过程互斥。 此时,我们再回头看一下互斥锁ReentrantLock中公平锁和非公平锁的加锁源码: 我们发现在ReentrantLock虽然有公平锁和非公平锁两种,但是它们添加的都是独享锁。根据源码所示,当某一个线程调用lock方法获取锁时,如果同步资源没有被其他线程锁住,那么当前线程在使用CAS更新state成功后就会成功抢占该资源。而如果公共资源被占用且不是被当前线程占用,那么就会加锁失败。所以可以确定ReentrantLock无论读操作还是写操作,添加的锁都是都是独享锁。 结语本文Java中常用的锁以及常见的锁的概念进行了基本介绍,并从源码以及实际应用的角度进行了对比分析。限于篇幅以及个人水平,没有在本篇文章中对所有内容进行深层次的讲解。 其实Java本身已经对锁本身进行了良好的封装,降低了研发同学在平时工作中的使用难度。但是研发同学也需要熟悉锁的底层原理,不同场景下选择最适合的锁。而且源码中的思路都是非常好的思路,也是值得大家去学习和借鉴的。 参考资料 《Java并发编程艺术》 Java中的锁 Java CAS 原理剖析 Java并发——关键字synchronized解析 Java synchronized原理总结 聊聊并发(二)——Java SE1.6中的Synchronized 深入理解读写锁—ReadWriteLock源码分析 【JUC】JDK1.8源码分析之ReentrantReadWriteLock Java多线程(十)之ReentrantReadWriteLock深入分析 Java–读写锁的实现原理 作者简介 家琪,美团点评后端工程师。2017 年加入美团点评,负责美团点评境内度假的业务开发。","categories":[{"name":"Java","slug":"Java","permalink":"http://yoursite.com/categories/Java/"},{"name":"面试","slug":"面试","permalink":"http://yoursite.com/categories/%E9%9D%A2%E8%AF%95/"},{"name":"锁","slug":"锁","permalink":"http://yoursite.com/categories/%E9%94%81/"}],"tags":[{"name":"Java","slug":"Java","permalink":"http://yoursite.com/tags/Java/"},{"name":"面试","slug":"面试","permalink":"http://yoursite.com/tags/%E9%9D%A2%E8%AF%95/"},{"name":"锁","slug":"锁","permalink":"http://yoursite.com/tags/%E9%94%81/"}]}]} \ No newline at end of file +{"meta":{"title":"Coder编程","subtitle":"","description":"","author":"Coder编程","url":"http://yoursite.com","root":"/"},"pages":[{"title":"关于","date":"2020-12-06T08:01:15.406Z","updated":"2020-12-06T08:01:15.406Z","comments":true,"path":"about/index.html","permalink":"http://yoursite.com/about/index.html","excerpt":"","text":"关于本站 https://coder-programming.cn 本站作为小编文章分类总结地,在这里你能看到最全的技术学习文章! 这里你能看到Java相关又或者分享一些奇闻怪事! 数据结构与算法、Java相关知识体系、框架知识及原理、Spring全家桶、微服务项目实战、DevOps实践之路、每日一篇互联网大厂面试或笔试题以及PMP项目管理知识等。 关于本人 Coder编程 一名九零后小伙~ 一位Java开发从业者~ 有着较强的学习心态,喜欢学习尝试一些新鲜事物,有想法的朋友可以一起学习哦~联系方式: 微信公众号:Coder编程 QQ群:315211365 QQ邮箱:573059382@qq.com 2021年计划 1.阅读书籍 《沉默的大多数》 《原则》 《态度改变与社会影响》 《最好的告别》 《社会性动物》 2.LeetCode刷题 数组题:349"},{"title":"所有分类","date":"2020-05-11T12:49:50.025Z","updated":"2020-05-11T12:49:50.025Z","comments":true,"path":"categories/index.html","permalink":"http://yoursite.com/categories/index.html","excerpt":"","text":""},{"title":"建站历史","date":"2020-12-07T15:23:18.175Z","updated":"2020-12-07T15:23:18.175Z","comments":true,"path":"history/index.html","permalink":"http://yoursite.com/history/index.html","excerpt":"","text":"建站历史 2020-07-28 集成 面试题 2020-07-24 改用 Volantis 主题优化 侧边栏、导航栏等 配置 2020-05-15 改用域名:www.coder-programming.cn使用:hexo-theme-material-x 主题 2020-04-20 使用Hexo初建网站使用Github域名:CoderMerlin.github.io"},{"title":"我的朋友们","date":"2020-05-11T13:04:34.163Z","updated":"2020-05-11T13:04:34.163Z","comments":true,"path":"friends/index.html","permalink":"http://yoursite.com/friends/index.html","excerpt":"","text":"互换友链规则:博客名、头像链接、博客链接、标签(最多3个)"},{"title":"","date":"2020-01-19T12:26:43.257Z","updated":"2020-01-14T14:01:16.353Z","comments":true,"path":"mylist/index.html","permalink":"http://yoursite.com/mylist/index.html","excerpt":"","text":""},{"title":"关于","date":"2020-12-08T15:32:30.528Z","updated":"2020-12-08T15:32:30.528Z","comments":true,"path":"project/index.html","permalink":"http://yoursite.com/project/index.html","excerpt":"","text":"WIKI 全部项目文档索引 HM云平台项目 OPC平台项目 Java面试 LeetCode解题记录"},{"title":"所有标签","date":"2020-05-11T12:29:45.896Z","updated":"2020-05-11T12:29:45.896Z","comments":true,"path":"tags/index.html","permalink":"http://yoursite.com/tags/index.html","excerpt":"","text":""}],"posts":[{"title":"玩转 IDEA 系列教程——强烈推荐官方中文(汉化)插件!","slug":"idea/idea01","date":"2020-06-04T12:43:00.000Z","updated":"2020-06-04T12:43:00.000Z","comments":true,"path":"2020/06/04/idea/idea01/","link":"","permalink":"http://yoursite.com/2020/06/04/idea/idea01/","excerpt":"IDEA2020.1版本的到来,官方也悄悄更新了IDEA的中文插件。至今为止也有一段时间了,本人也下载使用过,对于英语底子薄弱的同学来说,中文插件还是非常友好的!…","text":"每天进步一点,不做curd工程师与Api调用工程师欢迎访问个人博客网站:https://www.coder-programming.cn/ IDEA2020.1版本的到来,官方也悄悄更新了IDEA的中文插件。至今为止也有一段时间了,本人也下载使用过,对于英语底子薄弱的同学来说,中文插件还是非常友好的! 有兴趣的童鞋也可以下载尝尝鲜~ 接下来,我们来介绍如何安装这款插件! 一、在线安装打开设置——>插件 输入框搜索:Chinese (Simplified) Language Pack 之后重启即可使用! 二、离线安装离线下载地址:https://plugins.jetbrains.com/plugin/13710-chinese-simplified-language-pack-eap 注意事项:需要根据自身的IDEA版本号进行相应的选择下载! 下载离线包 安装 安装离线包有两张方法 方式一: File -> Settings -> Plugins -> 安装本地插件包 方式二: 直接将插件包托入到IDEA框内即可! 之后重启即可使用! 效果 文末文章收录至Github: https://github.com/CoderMerlin/coder-programmingGitee: https://gitee.com/573059382/coder-programming欢迎关注并star~","categories":[{"name":"工具","slug":"工具","permalink":"http://yoursite.com/categories/%E5%B7%A5%E5%85%B7/"}],"tags":[{"name":"工具","slug":"工具","permalink":"http://yoursite.com/tags/%E5%B7%A5%E5%85%B7/"}]},{"title":"博客搭建-博客搭建完整教程","slug":"BLOG03-HEXO-JC","date":"2020-05-14T14:39:58.972Z","updated":"2020-08-17T12:32:05.849Z","comments":true,"path":"2020/05/14/BLOG03-HEXO-JC/","link":"","permalink":"http://yoursite.com/2020/05/14/BLOG03-HEXO-JC/","excerpt":"Hexo初学者都会搭建的教程在这里!!!","text":"每天进步一点,不做curd工程师与Api调用工程师! 欢迎大家访问我的博客:Coder编程——个人博客 前言其实早在三年前,刚毕业那会就在网上学着搭建自己的个人博客。后面由于其他原因(主要是懒)就没再继续管理。如今又为何重新拾起呢? 第一:自己本身从毕业到工作也已经有了三年时间,或多或少有了一些工作经验和感悟。第二:平时有时间的时候,自己也会看看博客,写写文章(划划水)。第三:工作这么久,也要记录一下自己的学习记录,分享给大家。…. 下面的开始写教程了,这篇教程主要参考: https://www.itrhx.com/ 他的教程写的非常详细!本文也主要参考他写的内容,并修改~ 推荐文章: 《我为什么写博客》 (By 知明所以) 《为什么你应该(从现在开始就)写博客》 (By 刘未鹏 | Mind Hacks) 1. 概念1.1 Github PagesGithub Pages可以被认为是用户编写的、托管在github上的静态网页。使用Github Pages可以为你提供一个免费的服务器,免去了自己搭建服务器和写数据库的麻烦。此外还可以绑定自己的域名。 类似Github Pages的 Gitee Pages Gitlab Pages Coding 等等 后续我也会写文章专门介绍,这几个Pages的搭建过程,欢迎大家关注~ 1.2 HexoHexo 是一个快速、简洁且高效的博客框架。Hexo 使用 Markdown(或其他渲染引擎)解析文章,在几秒内,即可利用靓丽的主题生成静态网页。 2. 环境安装2.1 安装Node.js访问官网,按需下载相应版本,默认安装可以了。 更多详细教程: https://www.runoob.com/nodejs/nodejs-install-setup.html https://blog.csdn.net/antma/article/details/86104068 2.2 安装Git访问官网,按需下载相应版本,默认安装即可。 更多详细教程: Win10安装:https://blog.csdn.net/qq_32786873/article/details/80570783 Mac安装:https://www.jianshu.com/p/b91f848655af 2.3 检查软件是否安装成功同时按下 Win 键和 R 键打开运行窗口,输入 cmd ,然后输入以下命令,有相应版本信息显示则安装成功,若不正确可以卸载软件重新安装,此外若安装成功,在桌面右键鼠标,可以看到菜单里多了 Git GUI Here 和 Git Bash Here两个选项,第一个是图形界面的Git操作,另一个是命令行 123$ git --version$ node -v$ npm -v 正确输出版本号,说明安装成功了~ 3. Hexo安装正在编写。。。","categories":[{"name":"HEXO","slug":"HEXO","permalink":"http://yoursite.com/categories/HEXO/"}],"tags":[{"name":"HEXO","slug":"HEXO","permalink":"http://yoursite.com/tags/HEXO/"}]},{"title":"博客搭建-设置标签/分类/归档相关页面","slug":"BLOG02-HEXO-BQFLGD","date":"2020-05-12T08:47:59.726Z","updated":"2020-08-17T12:31:36.569Z","comments":true,"path":"2020/05/12/BLOG02-HEXO-BQFLGD/","link":"","permalink":"http://yoursite.com/2020/05/12/BLOG02-HEXO-BQFLGD/","excerpt":"如何配置标签/分类/归档???看完你就懂了!!!","text":"每天进步一点,不做curd工程师与Api调用工程师 前言最近晚上有时间就忙着倒腾自己的博客网站:https://www.coder-programming.cn/ 网站还在建设当中,我会慢慢分享自己的搭建博客的过程和遇到的问题,在这里与大家一起分享! 设置标签页面主题的 _config.yml 文件中,找到如下配置menu_desktop 或者 menu_mobile。(注意:填写的路径要对应上) 在\\source\\tags 中新建index.md。写入以下内容即可。 12345---layout: tagindex: truetitle: 所有标签--- 在需要发布的文章同样注意加入tags。举个栗子: 12345678910111213---title: Java学习路线整理updated: 2020-03-19 00:34:14date: 2020-03-19 00:34:14categories: - [Java] - [面试]tags: - [Java] - [面试] top: true--- 效果如图: 设置分类页面主题的 _config.yml 文件中,找到如下配置menu_desktop 或者 menu_mobile。(注意:填写的路径要对应上) 在\\source\\categories 中新建index.md。写入以下内容即可。 1234---layout: categorytitle: 所有分类--- 在需要发布的文章同样注意加入tags。举个栗子: 12345678910111213---title: Java学习路线整理updated: 2020-03-19 00:34:14date: 2020-03-19 00:34:14categories: - [Java] - [面试]tags: - [Java] - [面试] top: true--- 效果如图: 设置归档页面归档页面不要添加任何文件!归档页面不要添加任何文件!归档页面不要添加任何文件!重要的事说三遍! 只需要在主题的 _config.yml 文件中,找到如下配置menu_desktop 或者 menu_mobile。(注意:填写的路径要对应上), 填写archives/即可。 效果如图: 推荐[博客搭建] 增加百度统计功能 ProcessOn是一个在线作图工具的聚合平台~ 文末 欢迎关注个人微信公众号:Coder编程欢迎关注Coder编程公众号,主要分享数据结构与算法、Java相关知识体系、框架知识及原理、Spring全家桶、微服务项目实战、DevOps实践之路、每日一篇互联网大厂面试或笔试题以及PMP项目管理知识等。更多精彩内容正在路上~新建了一个qq群:315211365,欢迎大家进群交流一起学习。谢谢了!也可以介绍给身边有需要的朋友。 文章收录至Github: https://github.com/CoderMerlin/coder-programmingGitee: https://gitee.com/573059382/coder-programming欢迎关注并star~","categories":[{"name":"HEXO","slug":"HEXO","permalink":"http://yoursite.com/categories/HEXO/"}],"tags":[{"name":"HEXO","slug":"HEXO","permalink":"http://yoursite.com/tags/HEXO/"}]},{"title":"博客搭建-增加百度统计功能","slug":"BLOG01-HEXO-BDTJ","date":"2020-05-12T06:49:55.625Z","updated":"2020-08-17T12:31:54.706Z","comments":true,"path":"2020/05/12/BLOG01-HEXO-BDTJ/","link":"","permalink":"http://yoursite.com/2020/05/12/BLOG01-HEXO-BDTJ/","excerpt":"如何增加增加百度统计功能???","text":"每天进步一点,不做curd工程师与Api调用工程师 前言最近晚上有时间就忙着倒腾自己的博客网站:https://www.coder-programming.cn/ 网站还在建设当中,我会慢慢分享自己的搭建博客的过程和遇到的问题,在这里与大家一起分享! 百度统计是百度推出的一款免费的专业网站流量分析工具,能够告诉用户访客是如何找到并浏览用户的网站,在网站上做了些什么,非常有趣,接下来我们把百度统计添加到自己博客当中 访问百度统计首页,注册一个账号后登陆,添加你的博客网站 点击增加网站,填写个人网站的信息。 接着点击代码获取,复制该代码 然后到目录\\themes\\hexo-theme-material-x\\layout\\_partial\\head.ejs,里面粘贴你刚刚复制的代码。代码如下: 123456789101112<% if (config.baidu_analytics_key) { %> <!-- ba --> <script> var _hmt = _hmt || []; (function() { var hm = document.createElement("script"); hm.src = "https://hm.baidu.com/hm.js?<%= config.baidu_analytics_key %>"; var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(hm, s); })(); </script> <% } %> 修改博客根目录下的 _config.yml 文件(注意:不是主题下的_config.yml),将你的key填写进去: 所有操作完成后可以在百度统计管理页面检查代码是否安装成功,如果代码安装正确,一般20分钟后,可以查看网站分析数据 另外推荐:友盟,2010年4月在北京成立,安全、可靠、公正、第三方的网站流量统计分析系统 参考:https://www.itrhx.com/2018/08/27/A04-Hexo-blog-topic-personalization/ 推荐ProcessOn是一个在线作图工具的聚合平台~ 文末 欢迎关注个人微信公众号:Coder编程欢迎关注Coder编程公众号,主要分享数据结构与算法、Java相关知识体系、框架知识及原理、Spring全家桶、微服务项目实战、DevOps实践之路、每日一篇互联网大厂面试或笔试题以及PMP项目管理知识等。更多精彩内容正在路上~新建了一个qq群:315211365,欢迎大家进群交流一起学习。谢谢了!也可以介绍给身边有需要的朋友。 文章收录至Github: https://github.com/CoderMerlin/coder-programmingGitee: https://gitee.com/573059382/coder-programming欢迎关注并star~","categories":[{"name":"HEXO","slug":"HEXO","permalink":"http://yoursite.com/categories/HEXO/"}],"tags":[{"name":"HEXO","slug":"HEXO","permalink":"http://yoursite.com/tags/HEXO/"},{"name":"百度统计","slug":"百度统计","permalink":"http://yoursite.com/tags/%E7%99%BE%E5%BA%A6%E7%BB%9F%E8%AE%A1/"}]},{"title":"Java学习路线整理","slug":"java","date":"2020-03-18T16:34:14.000Z","updated":"2020-03-18T16:34:14.000Z","comments":true,"path":"2020/03/19/java/","link":"","permalink":"http://yoursite.com/2020/03/19/java/","excerpt":"面试 《剑指Offer》(豆瓣评分 8.3,0.7K+人评价) … 程序员代码面试指南:IT名企算法与数据结构题目最优解(第2版) (豆瓣评分 8.7,0.2K+人评价) … 编程之美(豆瓣评分 8.4,3K+人评价) … …","text":"Java 基础 并发 JVM Java8 新特性 代码优化 网络 操作系统 数据结构 算法 入门 经典 面试 数据库 系统设计 设计模式 常用框架 Spring/SpringBoot Netty 分布式 网站架构 软件底层 其他 其他 Java基础 《Head First Java》 : 可以说是我的 Java 启蒙书籍了,特别适合新手读当然也适合我们用来温故 Java 知识点。 《Java 核心技术卷 1+卷 2》: 很棒的两本书,建议有点 Java 基础之后再读,介绍的还是比较深入的,非常推荐。这两本书我一般也会用来巩固知识点或者当做工具书参考,是两本适合放在自己身边的好书。 《Java 编程思想 (第 4 版)》(推荐,豆瓣评分 9.1,3.2K+人评价):大部分人称之为Java领域的圣经,但我不推荐初学者阅读,有点劝退的味道。稍微有点基础后阅读更好。 《JAVA 网络编程 第 4 版》: 可以系统的学习一下网络的一些概念以及网络编程在 Java 中的使用。 《Java性能权威指南》:O’Reilly 家族书,性能调优的入门书,我个人觉得性能调优是每个 Java 从业者必备知识,这本书的缺点就是太老了,但是这本书可以作为一个实战书,尤其是 JVM 调优!不适合初学者。前置书籍:《深入理解 Java 虚拟机》 并发 《Java 并发编程之美》 :我觉得这本书还是非常适合我们用来学习 Java 多线程的。这本书的讲解非常通俗易懂,作者从并发编程基础到实战都是信手拈来。 另外,这本书的作者加多自身也会经常在网上发布各种技术文章。我觉得这本书也是加多大佬这么多年在多线程领域的沉淀所得的结果吧!他书中的内容基本都是结合代码讲解,非常有说服力! 《实战 Java 高并发程序设计》: 这个是我第二本要推荐的书籍,比较适合作为多线程入门/进阶书籍来看。这本书内容同样是理论结合实战,对于每个知识点的讲解也比较通俗易懂,整体结构也比较清。 《深入浅出 Java 多线程》:这本书是几位大厂(如阿里)的大佬开源的,Github 地址:https://github.com/RedSpider1/concurrent几位作者为了写好《深入浅出 Java 多线程》这本书阅读了大量的 Java 多线程方面的书籍和博客,然后再加上他们的经验总结、Demo 实例、源码解析,最终才形成了这本书。这本书的质量也是非常过硬!给作者们点个赞!这本书有统一的排版规则和语言风格、清晰的表达方式和逻辑。并且每篇文章初稿写完后,作者们就会互相审校,合并到主分支时所有成员会再次审校,最后再通篇修订了三遍。 《Java 并发编程的艺术》 :这本书不是很适合作为 Java 多线程入门书籍,需要具备一定的 JVM 基础,有些东西讲的还是挺深入的。另外,就我自己阅读这本书的感觉来说,我觉得这本书的章节规划有点杂乱,但是,具体到某个知识点又很棒!这可能也和这本书由三名作者共同编写完成有关系吧! …… JVM 《深入理解 Java 虚拟机(第 3 版)》):必读!必读!必读!神书,建议多刷几篇。里面不光有丰富地JVM理论知识,还有JVM实战案例!必读! 《实战 JAVA 虚拟机》:作为入门的了解 Java 虚拟机的知识还是不错的。 Java8 新特性 《Java 8 实战》:面向 Java 8 的技能升级,包括 Lambdas、流和函数式编程特性。实战系列的一贯风格让自己快速上手应用起来。Java 8 支持的 Lambda 是精简表达在语法上提供的支持。Java 8 提供了 Stream,学习和使用可以建立流式编程的认知。 《Java 8 编程参考官方教程》:建议当做工具书来用!哪里不会翻哪里! 代码优化 《重构_改善既有代码的设计》:豆瓣 9.1 分,重构书籍的开山鼻祖。 《Effective java 》:本书介绍了在 Java 编程中很多极具实用价值的经验规则,这些经验规则涵盖了大多数开发人员每天所面临的问题的解决方案。这篇文章能够非常实际地帮助你写出更加清晰、健壮和高效的代码。本书中的每条规则都以简短、独立的小文章形式出现,并通过例子代码加以进一步说明。 《代码整洁之道》:虽然是用 Java 语言作为例子,全篇都是在阐述 Java 面向对象的思想,但是其中大部分内容其它语言也能应用到。 阿里巴巴 Java 开发手册 :https://github.com/alibaba/p3c Google Java 编程风格指南: http://www.hawstein.com/posts/google-java-style.html 网络 《图解 HTTP》: 讲漫画一样的讲 HTTP,很有意思,不会觉得枯燥,大概也涵盖也 HTTP 常见的知识点。因为篇幅问题,内容可能不太全面。不过,如果不是专门做网络方向研究的小伙伴想研究 HTTP 相关知识的话,读这本书的话应该来说就差不多了。 《HTTP 权威指南》:如果要全面了解 HTTP 非此书不可! 操作系统 《鸟哥的 Linux 私房菜》:本书是最具知名度的 Linux 入门书《鸟哥的 Linux 私房菜基础学习篇》的最新版,全面而详细地介绍了 Linux 操作系统。 数据结构 《大话数据结构》:入门类型的书籍,读起来比较浅显易懂,适合没有数据结构基础或者说数据结构没学好的小伙伴用来入门数据结构。 算法入门 《我的第一本算法书》 (豆瓣评分 7.1,0.2K+人评价) 一本不那么“专业”的算法书籍。和下面两本推荐的算法书籍都是比较通俗易懂,“不那么深入”的算法书籍。我个人非常推荐,配图和讲解都非常不错! 《算法图解》(豆瓣评分 8.4,1.5K+人评价) :入门类型的书籍,读起来比较浅显易懂,非常适合没有算法基础或者说算法没学好的小伙伴用来入门。示例丰富,图文并茂,以让人容易理解的方式阐释了算法.读起来比较快,内容不枯燥! 《啊哈!算法》 (豆瓣评分 7.7,0.5K+人评价) :和《算法图解》类似的算法趣味入门书籍。 经典 下面这些书籍都是经典中的经典,但是阅读起来难度也比较大,不做太多阐述,神书就完事了!推荐先看 《算法》,然后再选下面的书籍进行进一步阅读。不需要都看,找一本好好看或者找某本书的某一个章节知识点好好看。 《算法 第四版》(豆瓣评分 9.3,0.4K+人评价): 我在大二的时候被我们的一个老师强烈安利过!自己也在当时购买了一本放在宿舍,到离开大学的时候自己大概看了一半多一点。因为内容实在太多了!另外,这本书还提供了详细的Java代码,非常适合学习 Java 的朋友来看,可以说是 Java 程序员的必备书籍之一了。再来介绍一下这本书籍吧!这本书籍算的上是算法领域经典的参考书,全面介绍了关于算法和数据结构的必备知识,并特别针对排序、搜索、图处理和字符串处理进行了论述。 编程珠玑(豆瓣评分 9.1,2K+人评价) :经典名著,被无数读者强烈推荐的书籍,几乎是顶级程序员必看的书籍之一了。这本书的作者也非常厉害,Java之父 James Gosling 就是他的学生。很多人都说这本书不是教你具体的算法,而是教你一种编程的思考方式。这种思考方式不仅仅在编程领域适用,在其他同样适用。 《算法设计手册》(豆瓣评分9.1 , 45人评价) :被 Teach Yourself Computer Science 强烈推荐的一本算法书籍。 《算法导论》 (豆瓣评分 9.2,0.4K+人评价) 《计算机程序设计艺术(第1卷)》(豆瓣评分 9.4,0.4K+人评价) 面试 《剑指Offer》(豆瓣评分 8.3,0.7K+人评价)这本面试宝典上面涵盖了很多经典的算法面试题,如果你要准备大厂面试的话一定不要错过这本书。《剑指Offer》 对应的算法编程题部分的开源项目解析:CodingInterviews 程序员代码面试指南:IT名企算法与数据结构题目最优解(第2版) (豆瓣评分 8.7,0.2K+人评价) :题目相比于《剑指 offer》 来说要难很多,题目涵盖面相比于《剑指 offer》也更加全面。全书一共有将近300道真实出现过的经典代码面试题。 编程之美(豆瓣评分 8.4,3K+人评价):这本书收集了约60道算法和程序设计题目,这些题目大部分在近年的笔试、面试中出现过,或者是被微软员工热烈讨论过。作者试图从书中各种有趣的问题出发,引导读者发现问题,分析问题,解决问题,寻找更优的解法。 数据库MySQL: 《高性能 MySQL》:这本书不用多说了把!MySQL 领域的经典之作,拥有广泛的影响力。不但适合数据库管理员(dba)阅读,也适合开发人员参考学习。不管是数据库新手还是专家,相信都能从本书有所收获。如果你的时间不够的话,第5章关于索引的内容和第6章关于查询的内容是必读的! 《MySQL 技术内幕-InnoDB 存储引擎》(推荐,豆瓣评分 8.7):了解 InnoDB 存储引擎底层原理必备的一本书,比较深入。 Redis: 《Redis 实战》:如果你想了解 Redis 的一些概念性知识的话,这本书真的非常不错。 《Redis 设计与实现》:也还行吧! 系统设计设计模式 《设计模式 : 可复用面向对象软件的基础》 :设计模式的经典! 《Head First 设计模式(中文版)》 :相当赞的一本设计模式入门书籍。用实际的编程案例讲解算法设计中会遇到的各种问题和需求变更(对的,连需求变更都考虑到了!),并以此逐步推导出良好的设计模式解决办法。 《大话设计模式》 :本书通篇都是以情景对话的形式,用多个小故事或编程示例来组织讲解GOF(即《设计模式 : 可复用面向对象软件的基础》这本书)),但是不像《设计模式 : 可复用面向对象软件的基础》难懂。但是设计模式只看书是不够的,还是需要在实际项目中运用,在实战中体会。 常用框架Spring/SpringBoot 《Spring 实战(第 4 版)》 :不建议当做入门书籍读,入门的话可以找点国人的书或者视频看。这本定位就相当于是关于 Spring 的新华字典,只有一些基本概念的介绍和示例,涵盖了 Spring 的各个方面,但都不够深入。就像作者在最后一页写的那样:“学习 Spring,这才刚刚开始”。 《Spring源码深度解析 第2版》 :读Spring源码必备的一本书籍。市面上关于Spring源码分析的书籍太少了。 《Spring 5高级编程(第5版)》 :推荐阅读,对于Spring5的新特性介绍的很好!不过内容比较多,可以作为工具书参考。 《精通Spring4.x企业应用开发实战》 :通过实战讲解,比较适合作为Spring入门书籍来看。 《Spring入门经典》 :适合入门,也有很多示例! 《Spring Boot实战派》 :这本书使用的Spring Boot 2.0+的版本,还算比较新。整本书采用“知识点+实例”的形式编写。本书通过“58个基于知识的实例+2个综合性的项目”,深入地讲解Spring Boot的技术原理、知识点和具体应用;把晦涩难懂的理论用实例展现出来,使得读者对知识的理解变得非常容易,同时也立即学会如何使用它。说实话,我还是比较推荐这本书的。 《Spring Boot编程思想(核心篇)》 :SpringBoot深入书,不适合初学者。书尤其的厚,这本书的缺点是书的很多知识点的讲解过于啰嗦和拖沓,优点是书中对SpringBoot内部原理讲解很清楚。 Netty 《Netty进阶之路:跟着案例学Netty》 : 这本书的优点是有不少实际的案例的讲解,通过案例来学习是很不错的! 《Netty 4.x 用户指南》 :《Netty 4.x 用户指南》中文翻译(包含了官方文档以及其他文章)。 《Netty 入门与实战:仿写微信 IM 即时通讯系统》 :基于 Netty 框架实现 IM 核心系统,带你深入学习 Netty 网络编程核心知识 《Netty 实战》 :可以作为工具书参考! 分布式 《从 Paxos 到 Zookeeper》:简要介绍几种典型的分布式一致性协议,以及解决分布式一致性问题的思路,其中重点讲解了 Paxos 和 ZAB 协议。同时,本书深入介绍了分布式一致性问题的工业解决方案——ZooKeeper,并着重向读者展示这一分布式协调框架的使用方法、内部实现及运维技巧,旨在帮助读者全面了解 ZooKeeper,并更好地使用和运维 ZooKeeper。 《RabbitMQ 实战指南》:《RabbitMQ 实战指南》从消息中间件的概念和 RabbitMQ 的历史切入,主要阐述 RabbitMQ 的安装、使用、配置、管理、运维、原理、扩展等方面的细节。如果你想浅尝 RabbitMQ 的使用,这本书是你最好的选择;如果你想深入 RabbitMQ 的原理,这本书也是你最好的选择;总之,如果你想玩转 RabbitMQ,这本书一定是最值得看的书之一 《Spring Cloud 微服务实战》:从时下流行的微服务架构概念出发,详细介绍了 Spring Cloud 针对微服务架构中几大核心要素的解决方案和基础组件。对于各个组件的介绍,《Spring Cloud 微服务实战》主要以示例与源码结合的方式来帮助读者更好地理解这些组件的使用方法以及运行原理。同时,在介绍的过程中,还包含了作者在实践中所遇到的一些问题和解决思路,可供读者在实践中作为参考。 网站架构 《大型网站技术架构:核心原理与案例分析+李智慧》:这本书我读过,基本不需要你有什么基础啊~读起来特别轻松,但是却可以学到很多东西,非常推荐了。另外我写过这本书的思维导图,关注我的微信公众号:“Java 面试通关手册”回复“大型网站技术架构”即可领取思维导图。 《亿级流量网站架构核心技术》:一书总结并梳理了亿级流量网站高可用和高并发原则,通过实例详细介绍了如何落地这些原则。本书分为四部分:概述、高可用原则、高并发原则、案例实战。从负载均衡、限流、降级、隔离、超时与重试、回滚机制、压测与预案、缓存、池化、异步化、扩容、队列等多方面详细介绍了亿级流量网站的架构核心技术,让读者看后能快速运用到实践项目中。 软件底层 《深入剖析 Tomcat》:本书深入剖析 Tomcat 4 和 Tomcat 5 中的每个组件,并揭示其内部工作原理。通过学习本书,你将可以自行开发 Tomcat 组件,或者扩展已有的组件。 读完这本书,基本可以摆脱背诵面试题的尴尬。 《深入理解 Nginx(第 2 版)》:作者讲的非常细致,注释都写的都很工整,对于 Nginx 的开发人员非常有帮助。优点是细致,缺点是过于细致,到处都是代码片段,缺少一些抽象。 其他 《深入分析 Java Web 技术内幕》: 感觉还行,涉及的东西也蛮多。 其他 《黑客与画家》:这本书是硅谷创业之父,Y Combinator 创始人 Paul Graham 的文集。之所以叫这个名字,是因为作者认为黑客(并非负面的那个意思)与画家有着极大的相似性,他们都是在创造,而不是完成某个任务。 《图解密码技术》:本书以图配文的形式,第一部分讲述了密码技术的历史沿革、对称密码、分组密码模式(包括ECB、CBC、CFB、OFB、CTR)、公钥、混合密码系统。第二部分重点介绍了认证方面的内容,涉及单向散列函数、消息认证码、数字签名、证书等。第三部分讲述了密钥、随机数、PGP、SSL/TLS 以及密码技术在现实生活中的应用。关键字:JWT 前置知识、区块链密码技术前置知识。属于密码知识入门书籍。 《人月神话》 、《程序开发心理学》 、《程序员修炼之道,从小工道专家》、 《高效程序员的45个习惯,敏捷开发修炼之道》 、《高效能程序员的修炼》 、《软技能,代码之外的生存之道》 、《程序员的职业素养》 、《程序员的思维修炼》","categories":[{"name":"Java","slug":"Java","permalink":"http://yoursite.com/categories/Java/"},{"name":"面试","slug":"面试","permalink":"http://yoursite.com/categories/%E9%9D%A2%E8%AF%95/"}],"tags":[{"name":"Java","slug":"Java","permalink":"http://yoursite.com/tags/Java/"},{"name":"面试","slug":"面试","permalink":"http://yoursite.com/tags/%E9%9D%A2%E8%AF%95/"}]},{"title":"分享一些摘抄的优美句子~","slug":"good","date":"2020-01-14T14:14:44.789Z","updated":"2020-05-12T07:32:52.452Z","comments":true,"path":"2020/01/14/good/","link":"","permalink":"http://yoursite.com/2020/01/14/good/","excerpt":"我是你路上最后一个过客,最后一个春天,最后一场雪,最后一次求生的战争——保尔 艾吕雅","text":"1.有一个夜晚我烧毁了所有的记忆,从此我的梦就透明了。有一个早晨我扔掉了所有的昨天,从此我的脚步就轻盈了。——泰戈尔 2.我是你路上最后一个过客,最后一个春天,最后一场雪,最后一次求生的战争——保尔 艾吕雅 3.虽然我已经十年没见过他,但我知道我会永远想念他。后来我再也没有交过像十二岁时那帮人一样的好朋友。也许,每个人都是这样——罗伯莱纳 4.在喧闹、混杂的生活中你应该与你的内心和平相处。尽管这世上有很多假冒和欺骗,有很多单调乏味的工作和众多破灭的梦幻,他仍然是一个美好的世界。记住:你应该努力的追求幸福。(此文于1692年镌于巴尔的摩圣保罗教堂) 5.我开始真正爱自己,我不再继续沉溺于过去,也不再为明天而忧虑,现在的我只活在一切在发生的当下,今天我活在此时此地,如此日复一日,这就叫”完美”。——《当我开始爱自己》 6.如果有一天你不再寻找爱情,只是去爱;你不再渴望成功,只是去做;你不再追逐成长,只是去修;一切才真正开始——纪伯伦 7.我原谅了从前的自己,就像谅解了一个野心勃勃的傻逼,体恤了一个笨手笨脚的勇士,释怀了一个难以启齿的秘密。 8.原来可以这样爱你,什么也可以说,什么也可以不说。让我采一束月光吧!插在今夜我寂寥的窗前。过了今夜,我不会再有力气为你写诗和流泪。——海烟 9.我不知该如何珍藏明里这份温暖,也不知该将她的灵魂带往何处。我清楚地明白,我们无法保证将来能够永远在一起。横亘在我们面前的是那沉重的人生于漫长时间,让人不由得产生一种无力感——新海诚 10.此刻有谁在世上某处哭,无缘无故在世上哭,在哭我。此刻有谁在夜间某处笑,无缘无故的在夜间笑,在笑我。此刻有谁在世上某处走,无缘无故的在世上走,走向我。此刻有谁在世上某处死,无缘无故在世上死,望着我。——里尔克 11.不惋惜,不呼唤,我也不啼哭。一切将逝去。如苹果花丛的薄雾。金黄的落叶堆满心间,我已不再是青春少年——叶赛宁 12.多希望我知道如何放弃你,你什么都没留给我却活在我心里。 13.最要紧的是,我们首先应该知道善良,其次要诚实,再其次是以后永远不要互相遗忘。 14.我向旧日的恋人道歉,因为我对新人如同初恋。——辛波斯卡 15.我永恒的灵魂,注视着你的心,纵然黑夜孤寂白昼如焚。——兰波 16.我常想,如果我拍够了足够的照片,我就不会再失去任何人。事实上,我的照片让我看到了我失去了多少——南戈尔丁 17.知识让我们愤世嫉俗,聪明让我们铁石心肠。我们想的太多,同情太少,除了机器我们更需要善良,没有这些品质,生命就没有意义。——卓别林 18.你别赤脚走在这片草地上散步,我怕我的花园到处都是星星的碎片。——伊迪斯 索德格朗 19.身体里的碳可以制成九千支铅笔,赠给诗人。身体里的铁,只够打成一枚铁钉,就钉在爱人的心上。 20.岁月有加,并非垂老。理想丢弃,方坠暮年。岁月悠悠,衰微只及肌肤。热忱抛却,颓废必致灵魂。——塞缪尔 厄尔曼 21.情不知所起,一往而深——汤显祖 22.渐渐觉得,友谊这个东西已经被世人捧的太高,它跟永恒其实没有太大关系。换了空间时间,总会有人离去。也总会有与当下的你心有相同的同伴不断出现,来陪你走接下来或短或长的人生。所以不要太念念不忘。也不要期待有什么回响。你要从同路者中寻找同伴,而非硬拽着旧人一起上路。 23.世界上最大的勇气,是压力下的优雅。——海明威 24.为你,千千万万遍——卡勒德 胡塞尼 25.你,一会看我一会看云;你看我时很远,你看云时很近——顾城 26.草在结它的种子 树在摇它的叶子 我们站着 不说话 就十分美好 ——顾城《门前》 27.曾国藩的交友原则,八交九不交: 八交:胜己者;盛德者;趣味者;肯吃亏者;直言者;志趣广大者;惠在当厄者;体人者 九不交:志不同者;谀人者;恩怨颠倒者;全无性情者;不孝不悌者;迂人者;落井下石者;德薄者;好占便宜者。 28.那一天我二十一岁,在我一生的黄金时代。我有好多奢望。我想爱,我想吃,我还想一瞬间变成天上半明半暗的云。——王小波《黄金时代》 29.从童年起,我便独自一人照顾着历代星辰——《孤独》白鹤林 30.这样看你,用所有的眼睛和所有距离,就像风住了,风又起——《沉溺》冯唐 31.人时已尽,人世很长。我在中间应当休息,走过的人说树枝低了,走过的人说树枝在长。——《墓床》顾城 32.万物皆有裂痕,那是光进来的地方。——莱昂纳德 科恩 33.成功只有一种,那就是用自己喜欢的方式度过一生——《明朝那些事》原出于美国记者 34.生活永远不可能像你想象的那么好,但也不会像你想象的那么糟,无论是好的还是糟糕的时候都需要坚强——《人生》莫泊桑 35.谁这时没有房屋,就不必建筑;谁这时孤独,就永远孤独,就醒着,读着,写着长信。在林荫道上来回,不安的游荡,当落叶纷飞。——《秋日》里尔克 36.也许,我太会隐藏自己的悲伤;也许我太会安慰自己的伤;从阴雨走到艳阳,我路过泥泞,路过风——《你若懂我 该多好》 37.我想无论是在塞纳河还是滹沱河边,我们两个人都已经放下彼此,所以我并没有等,她也不会在那个路灯下了,那个灯光下笑盈盈的她和慌张无力却又自大的我,永远属于了二十出头的我们,仅止于此我很满足,毕竟我们都要朝着让自己幸福努力。 38.一个人有两个我,一个在黑暗中醒着,一个在光明中睡着——纪伯伦 39.若我会见到你,事隔经年,我如何向你招呼,以眼泪,以沉默——拜伦 40.枕上诗书闲处好,门前风景雨来佳——李清照 文末 欢迎关注个人微信公众号:Coder编程欢迎关注Coder编程公众号,主要分享数据结构与算法、Java相关知识体系、框架知识及原理、Spring全家桶、微服务项目实战、DevOps实践之路、每日一篇互联网大厂面试或笔试题以及PMP项目管理知识等。更多精彩内容正在路上~也分享一些杂文~ 文章收录至Github: https://github.com/CoderMerlin/coder-programmingGitee: https://gitee.com/573059382/coder-programming欢迎关注并star~","categories":[{"name":"美文","slug":"美文","permalink":"http://yoursite.com/categories/%E7%BE%8E%E6%96%87/"}],"tags":[{"name":"美文","slug":"美文","permalink":"http://yoursite.com/tags/%E7%BE%8E%E6%96%87/"}]},{"title":"Java面试集锦:25道线程类相关面试题与答案(一)","slug":"interview/java/A-thread01","date":"2019-11-14T13:45:00.000Z","updated":"2019-11-14T13:45:00.000Z","comments":true,"path":"2019/11/14/interview/java/A-thread01/","link":"","permalink":"http://yoursite.com/2019/11/14/interview/java/A-thread01/","excerpt":"线程是什么?进程是什么?二者有什么区别和联系? 线程和进程各自有什么区别和优劣呢? 创建线程有几种不同的方式?你喜欢哪一种?为什么? 概括的解释下线程的几种可用状态? 点击查看更多","text":"每天进步一点,不做curd工程师与Api调用工程师欢迎访问个人网站:https://www.coder-programming.cn/ 1. 线程是什么?进程是什么?二者有什么区别和联系?(1)线程是CPU独立运行和独立调度的基本单位;(2)进程是资源分配的基本单位;是执行着的应用程序两者的联系:进程和线程都是操作系统所运行的程序运行的基本单元。 区别:(1)进程具有独立的空间地址,一个进程崩溃后,在保护模式下不会对其它进程产生影响。(2)线程只是一个进程的不同执行路径,线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉。 进程是执行着的应用程序,而线程是进程内部的一个执行序列。一个进程可以有多个线程。线程又叫做轻量级进程。 2. 线程和进程各自有什么区别和优劣呢?进程是资源分配的最小单位,线程是程序执行的最小单位。 进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。而线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。 线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点。 但是多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。 可参考文章 3. 创建线程有几种不同的方式?你喜欢哪一种?为什么?有三种方式可以用来创建线程: 继承Thread类 实现Runnable接口 应用程序可以使用Executor框架来创建线程池 实现Runnable接口这种方式更受欢迎,因为这不需要继承Thread类。在应用设计中已经继承了别的对象的情况下,这需要多继承(而Java不支持多继承),只能实现接口。同时,线程池也是非常高效的,很容易实现和使用。 4. 概括的解释下线程的几种可用状态?线程在执行过程中,可以处于下面几种状态: 就绪(Runnable):线程准备运行,不一定立马就能开始执行。 运行中(Running):进程正在执行线程的代码。 等待中(Waiting):线程处于阻塞的状态,等待外部的处理结束。 睡眠中(Sleeping):线程被强制睡眠。 I/O阻塞(Blocked on I/O):等待I/O操作完成。 同步阻塞(Blocked on Synchronization):等待获取锁。 死亡(Dead):线程完成了执行。 5. 同步方法和同步代码块的区别是什么?在Java语言中,每一个对象有一把锁。线程可以使用synchronized关键字来获取对象上的锁。synchronized关键字可应用在方法级别(粗粒度锁)或者是代码块级别(细粒度锁)。 Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。 (1)、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。 (2)、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。 (3)、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。 (4)、第三个例子同样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。 (5)、以上规则对其它对象锁同样适用。 6. 在监视器(Monitor)内部,是如何做线程同步的?程序应该做哪种级别的同步?监视器和锁在Java虚拟机中是一块使用的。监视器监视一块同步代码块,确保一次只有一个线程执行同步代码块。每一个监视器都和一个对象引用相关联。线程在获取锁之前不允许执行同步代码。 7. 什么是死锁(deadlock)?两个进程都在等待对方执行完毕才能继续往下执行的时候就发生了死锁。结果就是两个进程都陷入了无限的等待中。 8. 如何确保N个线程可以访问N个资源同时又不导致死锁?使用多线程的时候,一种非常简单的避免死锁的方式就是:指定获取锁的顺序,并强制线程按照指定的顺序获取锁。因此,如果所有的线程都是以同样的顺序加锁和释放锁,就不会出现死锁了。 9. 如何避免死锁?多线程产生死锁的四个必要条件:互斥条件: 一个资源每次只能被一个进程使用。保持和请求条件: 一个进程因请求资源而阻塞时,对已获得资源保持不放。不可剥夺调教: 进程已获得资源,在未使用完成前,不能被剥夺。循环等待条件: 若干进程之间形成一种头尾相接的循环等待资源关系。 只要破坏其中任意一个条件,就可以避免死锁,其中最简单的就是破环循环等待条件。按同一顺序访问对象,加载锁,释放锁。 10. Thread 类中的start() 和 run() 方法有什么区别?start()方法被用来启动新创建的线程,使该被创建的线程状态变为可运行状态。当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启动,start()方法才会启动新线程。如果我们调用了Thread的run()方法,它的行为就会和普通的方法一样,直接运行run()方法。为了在新的线程中执行我们的代码,必须使用Thread.start()方法。 11. Java中Runnable和Callable有什么不同?Runnable和Callable都代表那些要在不同的线程中执行的任务。Runnable从JDK1.0开始就有了,Callable是在JDK1.5增加的。它们的主要区别是Callable的 call() 方法可以返回值和抛出异常,而Runnable的run()方法没有这些功能。Callable可以返回装载有计算结果的Future对象。 12. Java中什么是竞态条件?在大多数实际的多线程应用中,两个或两个以上的线程需要共享对同一数据的存取。如果i线程存取相同的对象,并且每一个线程都调用了一个修改该对象状态的方法,将会发生什么呢?可以想象,线程彼此踩了对方的脚。根据线程访问数据的次序,可能会产生讹误的对象。这样的情况通常称为竞争条件。 13. Java中如何停止一个线程?Java提供了很丰富的API但没有为停止线程提供API。JDK 1.0本来有一些像stop(), suspend() 和 resume()的控制方法,但是由于潜在的死锁威胁。因此在后续的JDK版本中他们被弃用了,之后Java API的设计者就没有提供一个兼容且线程安全的方法来停止一个线程。当run()或者 call() 方法执行完的时候线程会自动结束,如果要手动结束一个线程,可以用volatile 布尔变量来退出run()方法的循环或者是取消任务来中断线程。 14. Java中notify 和 notifyAll有什么区别?一个很明显的原因是JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。如果线程需要等待某些锁那么调用对象中的wait()方法就有意义了。如果wait()方法定义在Thread类中,线程正在等待的是哪个锁就不明显了。简单的说,由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中因为锁属于对象。 15. Java中的同步集合与并发集合有什么区别?同步集合与并发集合都为多线程和并发提供了合适的线程安全的集合,不过并发集合的可扩展性更高。在Java1.5之前程序员们只有同步集合来用且在多线程并发的时候会导致争用,阻碍了系统的扩展性。Java5介绍了并发集合像ConcurrentHashMap,不仅提供线程安全还用锁分离和内部分区等现代技术提高了可扩展性。 16. 什么是线程池?线程池是一种多线程处理形式,处理过程中将任务提交到线程池,任务的执行交由线程池来管理。如果每个请求都创建一个线程去处理,那么服务器的资源很快就会被耗尽,使用线程池可以减少创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。 17. 为什么要使用线程池?创建线程和销毁线程的花销是比较大的,这些时间有可能比处理业务的时间还要长。这样频繁的创建线程和销毁线程,再加上业务工作线程,消耗系统资源的时间,可能导致系统资源不足。(我们可以把创建和销毁的线程的过程去掉) 18. 线程池有什么作用?线程池作用就是限制系统中执行线程的数量。 1、提高效率 创建好一定数量的线程放在池中,等需要使用的时候就从池中拿一个,这要比需要的时候创建一个线程对象要快的多。 2、方便管理 可以编写线程池管理代码对池中的线程同一进行管理,比如说启动时有该程序创建100个线程,每当有请求的时候,就分配一个线程去工作,如果刚好并发有101个请求,那多出的这一个请求可以排队等候,避免因无休止的创建线程导致系统崩溃。 19. 说说几种常见的线程池及使用场景?1、newSingleThreadExecutor创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。 2、newFixedThreadPool创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。 3、newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。 4、newScheduledThreadPool创建一个定长线程池,支持定时及周期性任务执行。 20. 线程池中的几种重要的参数?corePoolSize就是线程池中的核心线程数量,这几个核心线程,只是在没有用的时候,也不会被回收 maximumPoolSize就是线程池中可以容纳的最大线程的数量 keepAliveTime,就是线程池中除了核心线程之外的其他的最长可以保留的时间,因为在线程池中,除了核心线程即使在无任务的情况下也不能被清除,其余的都是有存活时间的,意思就是非核心线程可以保留的最长的空闲时间。util,就是计算这个时间的一个单位。 workQueue,就是等待队列,任务可以储存在任务队列中等待被执行,执行的是FIFIO原则(先进先出)。 threadFactory,就是创建线程的线程工厂。 handler,是一种拒绝策略,我们可以在任务满了之后,拒绝执行某些任务。 21. 说说线程池的拒绝策略?当请求任务不断的过来,而系统此时又处理不过来的时候,我们需要采取的策略是拒绝服务。RejectedExecutionHandler接口提供了拒绝任务处理的自定义方法的机会。在ThreadPoolExecutor中已经包含四种处理策略。 AbortPolicy策略:该策略会直接抛出异常,阻止系统正常工作。 CallerRunsPolicy 策略:只要线程池未关闭,该策略直接在调用者线程中,运行当前的被丢弃的任务。 DiscardOleddestPolicy策略: 该策略将丢弃最老的一个请求,也就是即将被执行的任务,并尝试再次提交当前任务。 DiscardPolicy策略:该策略默默的丢弃无法处理的任务,不予任何处理。 除了JDK默认提供的四种拒绝策略,我们可以根据自己的业务需求去自定义拒绝策略,自定义的方式很简单,直接实现RejectedExecutionHandler接口即可。 22. execute和submit的区别?我们执行任务是用的execute方法,除了execute方法,还有一个submit方法也可以执行我们提交的任务。 这两个方法有什么区别呢?分别适用于在什么场景下呢?我们来做一个简单的分析。 execute适用于不需要关注返回值的场景,只需要将线程丢到线程池中去执行就可以了。 submit方法适用于需要关注返回值的场景 23. 五种线程池的使用场景? newSingleThreadExecutor:一个单线程的线程池,可以用于需要保证顺序执行的场景,并且只有一个线程在执行。 newFixedThreadPool:一个固定大小的线程池,可以用于已知并发压力的情况下,对线程数做限制。 newCachedThreadPool:一个可以无限扩大的线程池,比较适合处理执行时间比较小的任务。 newScheduledThreadPool:可以延时启动,定时启动的线程池,适用于需要多个后台线程执行周期任务的场景。 newWorkStealingPool:一个拥有多个任务队列的线程池,可以减少连接数,创建当前可用cpu数量的线程来并行执行。 24. 线程池如何关闭? 初始化线程池时线程数的选择?关闭线程池可以调用shutdownNow和shutdown两个方法来实现 shutdownNow:对正在执行的任务全部发出interrupt(),停止执行,对还未开始执行的任务全部取消,并且返回还没开始的任务列表。 shutdown:当我们调用shutdown后,线程池将不再接受新的任务,但也不会去强制终止已经提交或者正在执行中的任务。 如果任务是IO密集型,一般线程数需要设置2倍CPU数以上,以此来尽量利用CPU资源。 如果任务是CPU密集型,一般线程数量只需要设置CPU数加1即可,更多的线程数也只能增加上下文切换,不能增加CPU利用率。 上述只是一个基本思想,如果真的需要精确的控制,还是需要上线以后观察线程池中线程数量跟队列的情况来定。 25. 线程池都有哪几种工作队列?1、ArrayBlockingQueue 是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。 2、LinkedBlockingQueue一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列 3、SynchronousQueue 一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。 4、PriorityBlockingQueue 一个具有优先级的无限阻塞队列。 推荐大厂笔试内容集合(内有详细解析) 持续更新中…. ProcessOn是一个在线作图工具的聚合平台~ 文末 欢迎关注个人微信公众号:Coder编程欢迎关注Coder编程公众号,主要分享数据结构与算法、Java相关知识体系、框架知识及原理、Spring全家桶、微服务项目实战、DevOps实践之路、每日一篇互联网大厂面试或笔试题以及PMP项目管理知识等。更多精彩内容正在路上~新建了一个qq群:315211365,欢迎大家进群交流一起学习。谢谢了!也可以介绍给身边有需要的朋友。 文章收录至Github: https://github.com/CoderMerlin/coder-programmingGitee: https://gitee.com/573059382/coder-programming欢迎关注并star~","categories":[{"name":"Java","slug":"Java","permalink":"http://yoursite.com/categories/Java/"},{"name":"面试","slug":"面试","permalink":"http://yoursite.com/categories/%E9%9D%A2%E8%AF%95/"}],"tags":[{"name":"Java","slug":"Java","permalink":"http://yoursite.com/tags/Java/"},{"name":"面试","slug":"面试","permalink":"http://yoursite.com/tags/%E9%9D%A2%E8%AF%95/"},{"name":"多线程","slug":"多线程","permalink":"http://yoursite.com/tags/%E5%A4%9A%E7%BA%BF%E7%A8%8B/"}]},{"title":"不可不说的Java“锁”事","slug":"java/Lock","date":"2018-11-15T12:06:09.000Z","updated":"2018-11-15T12:06:09.000Z","comments":true,"path":"2018/11/15/java/Lock/","link":"","permalink":"http://yoursite.com/2018/11/15/java/Lock/","excerpt":"面试 乐观锁 VS 悲观锁 … 自旋锁 VS 适应性自旋锁 … 公平锁 VS 非公平锁 …","text":"来源:httpstech.meituan.com20181115java-lock.html 前言Java提供了种类丰富的锁,每种锁因其特性的不同,在适当的场景下能够展现出非常高的效率。本文旨在对锁相关源码(本文中的源码来自JDK 8和Netty 3.10.6)、使用场景进行举例,为读者介绍主流锁的知识点,以及不同的锁的适用场景。 Java中往往是按照是否含有某一特性来定义锁,我们通过特性将锁进行分组归类,再使用对比的方式进行介绍,帮助大家更快捷的理解相关知识。下面给出本文内容的总体分类目录: 1. 乐观锁 VS 悲观锁乐观锁与悲观锁是一种广义上的概念,体现了看待线程同步的不同角度。在Java和数据库中都有此概念对应的实际应用。 先说概念。对于同一个数据的并发操作,悲观锁认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。Java中,synchronized关键字和Lock的实现类都是悲观锁。 而乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作(例如报错或者自动重试)。 乐观锁在Java中是通过使用无锁编程来实现,最常采用的是CAS算法,Java原子类中的递增操作就通过CAS自旋实现的。 根据从上面的概念描述我们可以发现: 悲观锁适合写操作多的场景,先加锁可以保证写操作时数据正确。 乐观锁适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升。 光说概念有些抽象,我们来看下乐观锁和悲观锁的调用方式示例: 12345678910111213141516 ------------------------- 悲观锁的调用方式 ------------------------- synchronizedpublic synchronized void testMethod() { 操作同步资源} ReentrantLockprivate ReentrantLock lock = new ReentrantLock(); 需要保证多个线程使用的是同一个锁public void modifyPublicResources() { lock.lock(); 操作同步资源 lock.unlock();} ------------------------- 乐观锁的调用方式 -------------------------private AtomicInteger atomicInteger = new AtomicInteger(); 需要保证多个线程使用的是同一个AtomicIntegeratomicInteger.incrementAndGet(); 执行自增1 通过调用方式示例,我们可以发现悲观锁基本都是在显式的锁定之后再操作同步资源,而乐观锁则直接去操作同步资源。那么,为何乐观锁能够做到不锁定同步资源也可以正确的实现线程同步呢?我们通过介绍乐观锁的主要实现方式 “CAS” 的技术原理来为大家解惑。 CAS全称 Compare And Swap(比较与交换),是一种无锁算法。在不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步。java.util.concurrent包中的原子类就是通过CAS来实现了乐观锁。 CAS算法涉及到三个操作数: 需要读写的内存值 V。 进行比较的值 A。 要写入的新值 B。 当且仅当 V 的值等于 A 时,CAS通过原子方式用新值B来更新V的值(“比较+更新”整体是一个原子操作),否则不会执行任何操作。一般情况下,“更新”是一个不断重试的操作。 之前提到java.util.concurrent包中的原子类,就是通过CAS来实现了乐观锁,那么我们进入原子类AtomicInteger的源码,看一下AtomicInteger的定义: 根据定义我们可以看出各属性的作用: unsafe: 获取并操作内存的数据。 valueOffset: 存储value在AtomicInteger中的偏移量。 value: 存储AtomicInteger的int值,该属性需要借助volatile关键字保证其在线程间是可见的。 接下来,我们查看AtomicInteger的自增函数incrementAndGet()的源码时,发现自增函数底层调用的是unsafe.getAndAddInt()。但是由于JDK本身只有Unsafe.class,只通过class文件中的参数名,并不能很好的了解方法的作用,所以我们通过OpenJDK 8 来查看Unsafe的源码: 123456789101112131415161718192021222324 ------------------------- JDK 8 ------------------------- AtomicInteger 自增方法public final int incrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1;} Unsafe.classpublic final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5;} ------------------------- OpenJDK 8 ------------------------- Unsafe.javapublic final int getAndAddInt(Object o, long offset, int delta) { int v; do { v = getIntVolatile(o, offset); } while (!compareAndSwapInt(o, offset, v, v + delta)); return v;} 根据OpenJDK 8的源码我们可以看出,getAndAddInt()循环获取给定对象o中的偏移量处的值v,然后判断内存值是否等于v。如果相等则将内存值设置为 v + delta,否则返回false,继续循环进行重试,直到设置成功才能退出循环,并且将旧值返回。整个“比较+更新”操作封装在compareAndSwapInt()中,在JNI里是借助于一个CPU指令完成的,属于原子操作,可以保证多个线程都能够看到同一个变量的修改值。 后续JDK通过CPU的cmpxchg指令,去比较寄存器中的 A 和 内存中的值 V。如果相等,就把要写入的新值 B 存入内存中。如果不相等,就将内存值 V 赋值给寄存器中的值 A。然后通过Java代码中的while循环再次调用cmpxchg指令进行重试,直到设置成功为止。 CAS虽然很高效,但是它也存在三大问题,这里也简单说一下: ABA问题。CAS需要在操作值的时候检查内存值是否发生变化,没有发生变化才会更新内存值。但是如果内存值原来是A,后来变成了B,然后又变成了A,那么CAS进行检查时会发现值没有发生变化,但是实际上是有变化的。ABA问题的解决思路就是在变量前面添加版本号,每次变量更新的时候都把版本号加一,这样变化过程就从“A-B-A”变成了“1A-2B-3A”。 JDK从1.5开始提供了AtomicStampedReference类来解决ABA问题,具体操作封装在compareAndSet()中。compareAndSet()首先检查当前引用和当前标志与预期引用和预期标志是否相等,如果都相等,则以原子方式将引用值和标志的值设置为给定的更新值。 循环时间长开销大。CAS操作如果长时间不成功,会导致其一直自旋,给CPU带来非常大的开销。 只能保证一个共享变量的原子操作。对一个共享变量执行操作时,CAS能够保证原子操作,但是对多个共享变量操作时,CAS是无法保证操作的原子性的。 Java从1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,可以把多个变量放在一个对象里来进行CAS操作。 2. 自旋锁 VS 适应性自旋锁在介绍自旋锁前,我们需要介绍一些前提知识来帮助大家明白自旋锁的概念。 阻塞或唤醒一个Java线程需要操作系统切换CPU状态来完成,这种状态转换需要耗费处理器时间。如果同步代码块中的内容过于简单,状态转换消耗的时间有可能比用户代码执行的时间还要长。 在许多场景中,同步资源的锁定时间很短,为了这一小段时间去切换线程,线程挂起和恢复现场的花费可能会让系统得不偿失。如果物理机器有多个处理器,能够让两个或以上的线程同时并行执行,我们就可以让后面那个请求锁的线程不放弃CPU的执行时间,看看持有锁的线程是否很快就会释放锁。 而为了让当前线程“稍等一下”,我们需让当前线程进行自旋,如果在自旋完成后前面锁定同步资源的线程已经释放了锁,那么当前线程就可以不必阻塞而是直接获取同步资源,从而避免切换线程的开销。这就是自旋锁。 自旋锁本身是有缺点的,它不能代替阻塞。自旋等待虽然避免了线程切换的开销,但它要占用处理器时间。如果锁被占用的时间很短,自旋等待的效果就会非常好。反之,如果锁被占用的时间很长,那么自旋的线程只会白浪费处理器资源。所以,自旋等待的时间必须要有一定的限度,如果自旋超过了限定次数(默认是10次,可以使用-XXPreBlockSpin来更改)没有成功获得锁,就应当挂起线程。 自旋锁的实现原理同样也是CAS,AtomicInteger中调用unsafe进行自增操作的源码中的do-while循环就是一个自旋操作,如果修改数值失败则通过循环来执行自旋,直至修改成功。 自旋锁在JDK1.4.2中引入,使用-XX+UseSpinning来开启。JDK 6中变为默认开启,并且引入了自适应的自旋锁(适应性自旋锁)。 自适应意味着自旋的时间(次数)不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也是很有可能再次成功,进而它将允许自旋等待持续相对更长的时间。如果对于某个锁,自旋很少成功获得过,那在以后尝试获取这个锁时将可能省略掉自旋过程,直接阻塞线程,避免浪费处理器资源。 在自旋锁中 另有三种常见的锁形式TicketLock、CLHlock和MCSlock,本文中仅做名词介绍,不做深入讲解,感兴趣的同学可以自行查阅相关资料。 3. 无锁 VS 偏向锁 VS 轻量级锁 VS 重量级锁这四种锁是指锁的状态,专门针对synchronized的。在介绍这四种锁状态之前还需要介绍一些额外的知识。 首先为什么Synchronized能实现线程同步? 在回答这个问题之前我们需要了解两个重要的概念:“Java对象头”、“Monitor”。 Java对象头synchronized是悲观锁,在操作同步资源之前需要给同步资源先加锁,这把锁就是存在Java对象头里的,而Java对象头又是什么呢? 我们以Hotspot虚拟机为例,Hotspot的对象头主要包括两部分数据:Mark Word(标记字段)、Klass Pointer(类型指针)。 Mark Word:默认存储对象的HashCode,分代年龄和锁标志位信息。这些信息都是与对象自身定义无关的数据,所以Mark Word被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据。它会根据对象的状态复用自己的存储空间,也就是说在运行期间Mark Word里存储的数据会随着锁标志位的变化而变化。 Klass Point:对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。 MonitorMonitor可以理解为一个同步工具或一种同步机制,通常被描述为一个对象。每一个Java对象就有一把看不见的锁,称为内部锁或者Monitor锁。 Monitor是线程私有的数据结构,每一个线程都有一个可用monitor record列表,同时还有一个全局的可用列表。每一个被锁住的对象都会和一个monitor关联,同时monitor中有一个Owner字段存放拥有该锁的线程的唯一标识,表示该锁被这个线程占用。 现在话题回到synchronized,synchronized通过Monitor来实现线程同步,Monitor是依赖于底层的操作系统的Mutex Lock(互斥锁)来实现的线程同步。 如同我们在自旋锁中提到的“阻塞或唤醒一个Java线程需要操作系统切换CPU状态来完成,这种状态转换需要耗费处理器时间。如果同步代码块中的内容过于简单,状态转换消耗的时间有可能比用户代码执行的时间还要长”。这种方式就是synchronized最初实现同步的方式,这就是JDK 6之前synchronized效率低的原因。这种依赖于操作系统Mutex Lock所实现的锁我们称之为“重量级锁”,JDK 6中为了减少获得锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁”。 所以目前锁一共有4种状态,级别从低到高依次是:无锁、偏向锁、轻量级锁和重量级锁。锁状态只能升级不能降级。 通过上面的介绍,我们对synchronized的加锁机制以及相关知识有了一个了解,那么下面我们给出四种锁状态对应的的Mark Word内容,然后再分别讲解四种锁状态的思路以及特点: 锁状态 存储内容 存储内容 无锁 对象的hashCode、对象分代年龄、是否是偏向锁(0) 01 偏向锁 偏向线程ID、偏向时间戳、对象分代年龄、是否是偏向锁(1) 01 轻量级锁 指向栈中锁记录的指针 00 重量级锁 指向互斥量(重量级锁)的指针 10 无锁 无锁没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功。 无锁的特点就是修改操作在循环内进行,线程会不断的尝试修改共享资源。如果没有冲突就修改成功并退出,否则就会继续循环尝试。如果有多个线程修改同一个值,必定会有一个线程能修改成功,而其他修改失败的线程会不断重试直到修改成功。上面我们介绍的CAS原理及应用即是无锁的实现。无锁无法全面代替有锁,但无锁在某些场合下的性能是非常高的。 偏向锁 偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价。 在大多数情况下,锁总是由同一线程多次获得,不存在多线程竞争,所以出现了偏向锁。其目标就是在只有一个线程执行同步代码块时能够提高性能。 当一个线程访问同步代码块并获取锁时,会在Mark Word里存储锁偏向的线程ID。在线程进入和退出同步块时不再通过CAS操作来加锁和解锁,而是检测Mark Word里是否存储着指向当前线程的偏向锁。引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取及释放依赖多次CAS原子指令,而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令即可。 偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动释放偏向锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有字节码正在执行),它会首先暂停拥有偏向锁的线程,判断锁对象是否处于被锁定状态。撤销偏向锁后恢复到无锁(标志位为“01”)或轻量级锁(标志位为“00”)的状态。 偏向锁在JDK 6及以后的JVM里是默认启用的。可以通过JVM参数关闭偏向锁:-XX-UseBiasedLocking=false,关闭之后程序默认会进入轻量级锁状态。 轻量级锁 是指当锁是偏向锁的时候,被另外的线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能。 在代码进入同步块的时候,如果同步对象锁状态为无锁状态(锁标志位为“01”状态,是否为偏向锁为“0”),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝,然后拷贝对象头中的Mark Word复制到锁记录中。 拷贝成功后,虚拟机将使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针,并将Lock Record里的owner指针指向对象的Mark Word。 如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象Mark Word的锁标志位设置为“00”,表示此对象处于轻量级锁定状态。 如果轻量级锁的更新操作失败了,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行,否则说明多个线程竞争锁。 若当前只有一个等待线程,则该线程通过自旋进行等待。但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁升级为重量级锁。 重量级锁 升级为重量级锁时,锁标志的状态值变为“10”,此时Mark Word中存储的是指向重量级锁的指针,此时等待锁的线程都会进入阻塞状态。 整体的锁状态升级流程如下: 综上,偏向锁通过对比Mark Word解决加锁问题,避免执行CAS操作。而轻量级锁是通过用CAS操作和自旋来解决加锁问题,避免线程阻塞和唤醒而影响性能。重量级锁是将除了拥有锁的线程以外的线程都阻塞。 4. 公平锁 VS 非公平锁公平锁是指多个线程按照申请锁的顺序来获取锁,线程直接进入队列中排队,队列中的第一个线程才能获得锁。公平锁的优点是等待锁的线程不会饿死。缺点是整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞,CPU唤醒阻塞线程的开销比非公平锁大。 非公平锁是多个线程加锁时直接尝试获取锁,获取不到才会到等待队列的队尾等待。但如果此时锁刚好可用,那么这个线程可以无需阻塞直接获取到锁,所以非公平锁有可能出现后申请锁的线程先获取锁的场景。非公平锁的优点是可以减少唤起线程的开销,整体的吞吐效率高,因为线程有几率不阻塞直接获得锁,CPU不必唤醒所有线程。缺点是处于等待队列中的线程可能会饿死,或者等很久才会获得锁。 直接用语言描述可能有点抽象,这里作者用从别处看到的一个例子来讲述一下公平锁和非公平锁。 如上图所示,假设有一口水井,有管理员看守,管理员有一把锁,只有拿到锁的人才能够打水,打完水要把锁还给管理员。每个过来打水的人都要管理员的允许并拿到锁之后才能去打水,如果前面有人正在打水,那么这个想要打水的人就必须排队。管理员会查看下一个要去打水的人是不是队伍里排最前面的人,如果是的话,才会给你锁让你去打水;如果你不是排第一的人,就必须去队尾排队,这就是公平锁。 但是对于非公平锁,管理员对打水的人没有要求。即使等待队伍里有排队等待的人,但如果在上一个人刚打完水把锁还给管理员而且管理员还没有允许等待队伍里下一个人去打水时,刚好来了一个插队的人,这个插队的人是可以直接从管理员那里拿到锁去打水,不需要排队,原本排队等待的人只能继续等待。如下图所示: 接下来我们通过ReentrantLock的源码来讲解公平锁和非公平锁。 根据代码可知,ReentrantLock里面有一个内部类Sync,Sync继承AQS(AbstractQueuedSynchronizer),添加锁和释放锁的大部分操作实际上都是在Sync中实现的。它有公平锁FairSync和非公平锁NonfairSync两个子类。ReentrantLock默认使用非公平锁,也可以通过构造器来显示的指定使用公平锁。 下面我们来看一下公平锁与非公平锁的加锁方法的源码 通过上图中的源代码对比,我们可以明显的看出公平锁与非公平锁的lock()方法唯一的区别就在于公平锁在获取同步状态时多了一个限制条件:hasQueuedPredecessors()。 再进入hasQueuedPredecessors(),可以看到该方法主要做一件事情:主要是判断当前线程是否位于同步队列中的第一个。如果是则返回true,否则返回false。 综上,公平锁就是通过同步队列来实现多个线程按照申请锁的顺序来获取锁,从而实现公平的特性。非公平锁加锁时不考虑排队等待问题,直接尝试获取锁,所以存在后申请却先获得锁的情况。 5. 可重入锁 VS 非可重入锁可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象或者class),不会因为之前已经获取过还没释放而阻塞。Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。下面用示例代码来进行分析: 12345678910public class Widget { public synchronized void doSomething() { System.out.println(方法1执行...); doOthers(); } public synchronized void doOthers() { System.out.println(方法2执行...); }} 在上面的代码中,类中的两个方法都是被内置锁synchronized修饰的,doSomething()方法中调用doOthers()方法。因为内置锁是可重入的,所以同一个线程在调用doOthers()时可以直接获得当前对象的锁,进入doOthers()进行操作。 如果是一个不可重入锁,那么当前线程在调用doOthers()之前需要将执行doSomething()时获取当前对象的锁释放掉,实际上该对象锁已被当前线程所持有,且无法释放。所以此时会出现死锁。 而为什么可重入锁就可以在嵌套调用时可以自动获得锁呢?我们通过图示和源码来分别解析一下。 还是打水的例子,有多个人在排队打水,此时管理员允许锁和同一个人的多个水桶绑定。这个人用多个水桶打水时,第一个水桶和锁绑定并打完水之后,第二个水桶也可以直接和锁绑定并开始打水,所有的水桶都打完水之后打水人才会将锁还给管理员。这个人的所有打水流程都能够成功执行,后续等待的人也能够打到水。这就是可重入锁。 但如果是非可重入锁的话,此时管理员只允许锁和同一个人的一个水桶绑定。第一个水桶和锁绑定打完水之后并不会释放锁,导致第二个水桶不能和锁绑定也无法打水。当前线程出现死锁,整个等待队列中的所有线程都无法被唤醒。 之前我们说过ReentrantLock和synchronized都是重入锁,那么我们通过重入锁ReentrantLock以及非可重入锁NonReentrantLock的源码来对比分析一下为什么非可重入锁在重复调用同步资源时会出现死锁。 首先ReentrantLock和NonReentrantLock都继承父类AQS,其父类AQS中维护了一个同步状态status来计数重入次数,status初始值为0。 当线程尝试获取锁时,可重入锁先尝试获取并更新status值,如果status == 0表示没有其他线程在执行同步代码,则把status置为1,当前线程开始执行。如果status != 0,则判断当前线程是否是获取到这个锁的线程,如果是的话执行status+1,且当前线程可以再次获取锁。而非可重入锁是直接去获取并尝试更新当前status的值,如果status != 0的话会导致其获取锁失败,当前线程阻塞。 释放锁时,可重入锁同样先获取当前status的值,在当前线程是持有锁的线程的前提下。如果status-1 == 0,则表示当前线程所有重复获取锁的操作都已经执行完毕,然后该线程才会真正释放锁。而非可重入锁则是在确定当前线程是持有锁的线程之后,直接将status置为0,将锁释放。 6. 独享锁 VS 共享锁独享锁和共享锁同样是一种概念。我们先介绍一下具体的概念,然后通过ReentrantLock和ReentrantReadWriteLock的源码来介绍独享锁和共享锁。 独享锁也叫排他锁,是指该锁一次只能被一个线程所持有。如果线程T对数据A加上排它锁后,则其他线程不能再对A加任何类型的锁。获得排它锁的线程即能读数据又能修改数据。JDK中的synchronized和JUC中Lock的实现类就是互斥锁。 共享锁是指该锁可被多个线程所持有。如果线程T对数据A加上共享锁后,则其他线程只能对A再加共享锁,不能加排它锁。获得共享锁的线程只能读数据,不能修改数据。 独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。 下图为ReentrantReadWriteLock的部分源码: 我们看到ReentrantReadWriteLock有两把锁:ReadLock和WriteLock,由词知意,一个读锁一个写锁,合称“读写锁”。再进一步观察可以发现ReadLock和WriteLock是靠内部类Sync实现的锁。Sync是AQS的一个子类,这种结构在CountDownLatch、ReentrantLock、Semaphore里面也都存在。 在ReentrantReadWriteLock里面,读锁和写锁的锁主体都是Sync,但读锁和写锁的加锁方式不一样。读锁是共享锁,写锁是独享锁。读锁的共享锁可保证并发读非常高效,而读写、写读、写写的过程互斥,因为读锁和写锁是分离的。所以ReentrantReadWriteLock的并发性相比一般的互斥锁有了很大提升。 那读锁和写锁的具体加锁方式有什么区别呢?在了解源码之前我们需要回顾一下其他知识。 在最开始提及AQS的时候我们也提到了state字段(int类型,32位),该字段用来描述有多少线程获持有锁。 在独享锁中这个值通常是0或者1(如果是重入锁的话state值就是重入的次数),在共享锁中state就是持有锁的数量。但是在ReentrantReadWriteLock中有读、写两把锁,所以需要在一个整型变量state上分别描述读锁和写锁的数量(或者也可以叫状态)。于是将state变量“按位切割”切分成了两个部分,高16位表示读锁状态(读锁个数),低16位表示写锁状态(写锁个数)。如下图所示: 了解了概念之后我们再来看代码,先看写锁的加锁源码: 12345678910111213141516171819protected final boolean tryAcquire(int acquires) { Thread current = Thread.currentThread(); int c = getState(); 取到当前锁的个数 int w = exclusiveCount(c); 取写锁的个数w if (c != 0) { 如果已经有线程持有了锁(c!=0) (Note if c != 0 and w == 0 then shared count != 0) if (w == 0 current != getExclusiveOwnerThread()) 如果写线程数(w)为0(换言之存在读锁) 或者持有锁的线程不是当前线程就返回失败 return false; if (w + exclusiveCount(acquires) MAX_COUNT) 如果写入锁的数量大于最大数(65535,2的16次方-1)就抛出一个Error。 throw new Error(Maximum lock count exceeded); Reentrant acquire setState(c + acquires); return true; } if (writerShouldBlock() !compareAndSetState(c, c + acquires)) 如果当且写线程数为0,并且当前线程需要阻塞那么就返回失败;或者如果通过CAS增加写线程数失败也返回失败。 return false; setExclusiveOwnerThread(current); 如果c=0,w=0或者c0,w0(重入),则设置当前线程或锁的拥有者 return true;} 这段代码首先取到当前锁的个数c,然后再通过c来获取写锁的个数w。因为写锁是低16位,所以取低16位的最大值与当前的c做与运算( int w = exclusiveCount©; ),高16位和0与运算后是0,剩下的就是低位运算的值,同时也是持有写锁的线程数目。 在取到写锁线程的数目后,首先判断是否已经有线程持有了锁。如果已经有线程持有了锁(c!=0),则查看当前写锁线程的数目,如果写线程数为0(即此时存在读锁)或者持有锁的线程不是当前线程就返回失败(涉及到公平锁和非公平锁的实现)。 如果写入锁的数量大于最大数(65535,2的16次方-1)就抛出一个Error。 如果当且写线程数为0(那么读线程也应该为0,因为上面已经处理c!=0的情况),并且当前线程需要阻塞那么就返回失败;如果通过CAS增加写线程数失败也返回失败。 如果c=0,w=0或者c0,w0(重入),则设置当前线程或锁的拥有者,返回成功! tryAcquire()除了重入条件(当前线程为获取了写锁的线程)之外,增加了一个读锁是否存在的判断。如果存在读锁,则写锁不能被获取,原因在于:必须确保写锁的操作对读锁可见,如果允许读锁在已被获取的情况下对写锁的获取,那么正在运行的其他读线程就无法感知到当前写线程的操作。 因此,只有等待其他读线程都释放了读锁,写锁才能被当前线程获取,而写锁一旦被获取,则其他读写线程的后续访问均被阻塞。写锁的释放与ReentrantLock的释放过程基本类似,每次释放均减少写状态,当写状态为0时表示写锁已被释放,然后等待的读写线程才能够继续访问读写锁,同时前次写线程的修改对后续的读写线程可见。 接着是读锁的代码: 123456789101112131415161718192021222324252627protected final int tryAcquireShared(int unused) { Thread current = Thread.currentThread(); int c = getState(); if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) return -1; 如果其他线程已经获取了写锁,则当前线程获取读锁失败,进入等待状态 int r = sharedCount(c); if (!readerShouldBlock() && r MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) { if (r == 0) { firstReader = current; firstReaderHoldCount = 1; } else if (firstReader == current) { firstReaderHoldCount++; } else { HoldCounter rh = cachedHoldCounter; if (rh == null rh.tid != getThreadId(current)) cachedHoldCounter = rh = readHolds.get(); else if (rh.count == 0) readHolds.set(rh); rh.count++; } return 1; } return fullTryAcquireShared(current);} 可以看到在tryAcquireShared(int unused)方法中,如果其他线程已经获取了写锁,则当前线程获取读锁失败,进入等待状态。如果当前线程获取了写锁或者写锁未被获取,则当前线程(线程安全,依靠CAS保证)增加读状态,成功获取读锁。读锁的每次释放(线程安全的,可能有多个读线程同时释放读锁)均减少读状态,减少的值是“116”。所以读写锁才能实现读读的过程共享,而读写、写读、写写的过程互斥。 此时,我们再回头看一下互斥锁ReentrantLock中公平锁和非公平锁的加锁源码: 我们发现在ReentrantLock虽然有公平锁和非公平锁两种,但是它们添加的都是独享锁。根据源码所示,当某一个线程调用lock方法获取锁时,如果同步资源没有被其他线程锁住,那么当前线程在使用CAS更新state成功后就会成功抢占该资源。而如果公共资源被占用且不是被当前线程占用,那么就会加锁失败。所以可以确定ReentrantLock无论读操作还是写操作,添加的锁都是都是独享锁。 结语本文Java中常用的锁以及常见的锁的概念进行了基本介绍,并从源码以及实际应用的角度进行了对比分析。限于篇幅以及个人水平,没有在本篇文章中对所有内容进行深层次的讲解。 其实Java本身已经对锁本身进行了良好的封装,降低了研发同学在平时工作中的使用难度。但是研发同学也需要熟悉锁的底层原理,不同场景下选择最适合的锁。而且源码中的思路都是非常好的思路,也是值得大家去学习和借鉴的。 参考资料 《Java并发编程艺术》 Java中的锁 Java CAS 原理剖析 Java并发——关键字synchronized解析 Java synchronized原理总结 聊聊并发(二)——Java SE1.6中的Synchronized 深入理解读写锁—ReadWriteLock源码分析 【JUC】JDK1.8源码分析之ReentrantReadWriteLock Java多线程(十)之ReentrantReadWriteLock深入分析 Java–读写锁的实现原理 作者简介 家琪,美团点评后端工程师。2017 年加入美团点评,负责美团点评境内度假的业务开发。","categories":[{"name":"Java","slug":"Java","permalink":"http://yoursite.com/categories/Java/"},{"name":"面试","slug":"面试","permalink":"http://yoursite.com/categories/%E9%9D%A2%E8%AF%95/"},{"name":"锁","slug":"锁","permalink":"http://yoursite.com/categories/%E9%94%81/"}],"tags":[{"name":"Java","slug":"Java","permalink":"http://yoursite.com/tags/Java/"},{"name":"面试","slug":"面试","permalink":"http://yoursite.com/tags/%E9%9D%A2%E8%AF%95/"},{"name":"锁","slug":"锁","permalink":"http://yoursite.com/tags/%E9%94%81/"}]}]} \ No newline at end of file diff --git a/index.html b/index.html index ad1fe77..79b7c62 100644 --- a/index.html +++ b/index.html @@ -2043,8 +2043,8 @@๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
try { document.getElementById('last-update-show').innerHTML = timeago(new Date(lastUpDate)); } catch (error) { - document.getElementById('last-update-show').innerHTML = '2020-12-07 日'; + document.getElementById('last-update-show').innerHTML = '2020-12-08 日'; } diff --git a/mylist/index.html b/mylist/index.html index 7f2bd0b..5af5b8e 100644 --- a/mylist/index.html +++ b/mylist/index.html @@ -970,8 +970,8 @@๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
try { document.getElementById('last-update-show').innerHTML = timeago(new Date(lastUpDate)); } catch (error) { - document.getElementById('last-update-show').innerHTML = '2020-12-07 日'; + document.getElementById('last-update-show').innerHTML = '2020-12-08 日'; } diff --git a/project/index.html b/project/index.html index c713542..e9a7b97 100644 --- a/project/index.html +++ b/project/index.html @@ -724,11 +724,13 @@ diff --git a/tags/HEXO/index.html b/tags/HEXO/index.html index 694f090..518cb0b 100644 --- a/tags/HEXO/index.html +++ b/tags/HEXO/index.html @@ -1361,8 +1361,8 @@๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
try { document.getElementById('last-update-show').innerHTML = timeago(new Date(lastUpDate)); } catch (error) { - document.getElementById('last-update-show').innerHTML = '2020-12-07 日'; + document.getElementById('last-update-show').innerHTML = '2020-12-08 日'; } diff --git a/tags/Java/index.html b/tags/Java/index.html index 4340ca5..916dfe8 100644 --- a/tags/Java/index.html +++ b/tags/Java/index.html @@ -1369,8 +1369,8 @@๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
try { document.getElementById('last-update-show').innerHTML = timeago(new Date(lastUpDate)); } catch (error) { - document.getElementById('last-update-show').innerHTML = '2020-12-07 日'; + document.getElementById('last-update-show').innerHTML = '2020-12-08 日'; } diff --git "a/tags/\345\244\232\347\272\277\347\250\213/index.html" "b/tags/\345\244\232\347\272\277\347\250\213/index.html" index ddb83fc..9e3c688 100644 --- "a/tags/\345\244\232\347\272\277\347\250\213/index.html" +++ "b/tags/\345\244\232\347\272\277\347\250\213/index.html" @@ -1097,8 +1097,8 @@๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
try { document.getElementById('last-update-show').innerHTML = timeago(new Date(lastUpDate)); } catch (error) { - document.getElementById('last-update-show').innerHTML = '2020-12-07 日'; + document.getElementById('last-update-show').innerHTML = '2020-12-08 日'; } diff --git "a/tags/\345\267\245\345\205\267/index.html" "b/tags/\345\267\245\345\205\267/index.html" index bc26c2c..9dc615b 100644 --- "a/tags/\345\267\245\345\205\267/index.html" +++ "b/tags/\345\267\245\345\205\267/index.html" @@ -1089,8 +1089,8 @@๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
try { document.getElementById('last-update-show').innerHTML = timeago(new Date(lastUpDate)); } catch (error) { - document.getElementById('last-update-show').innerHTML = '2020-12-07 日'; + document.getElementById('last-update-show').innerHTML = '2020-12-08 日'; } diff --git "a/tags/\347\231\276\345\272\246\347\273\237\350\256\241/index.html" "b/tags/\347\231\276\345\272\246\347\273\237\350\256\241/index.html" index 0bf22c5..39af6e4 100644 --- "a/tags/\347\231\276\345\272\246\347\273\237\350\256\241/index.html" +++ "b/tags/\347\231\276\345\272\246\347\273\237\350\256\241/index.html" @@ -1093,8 +1093,8 @@๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
try { document.getElementById('last-update-show').innerHTML = timeago(new Date(lastUpDate)); } catch (error) { - document.getElementById('last-update-show').innerHTML = '2020-12-07 日'; + document.getElementById('last-update-show').innerHTML = '2020-12-08 日'; } diff --git "a/tags/\347\276\216\346\226\207/index.html" "b/tags/\347\276\216\346\226\207/index.html" index 907b9fa..ae40162 100644 --- "a/tags/\347\276\216\346\226\207/index.html" +++ "b/tags/\347\276\216\346\226\207/index.html" @@ -1089,8 +1089,8 @@๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
try { document.getElementById('last-update-show').innerHTML = timeago(new Date(lastUpDate)); } catch (error) { - document.getElementById('last-update-show').innerHTML = '2020-12-07 日'; + document.getElementById('last-update-show').innerHTML = '2020-12-08 日'; } diff --git "a/tags/\351\224\201/index.html" "b/tags/\351\224\201/index.html" index 1b8cefa..45824ba 100644 --- "a/tags/\351\224\201/index.html" +++ "b/tags/\351\224\201/index.html" @@ -1095,8 +1095,8 @@๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
try { document.getElementById('last-update-show').innerHTML = timeago(new Date(lastUpDate)); } catch (error) { - document.getElementById('last-update-show').innerHTML = '2020-12-07 日'; + document.getElementById('last-update-show').innerHTML = '2020-12-08 日'; } diff --git "a/tags/\351\235\242\350\257\225/index.html" "b/tags/\351\235\242\350\257\225/index.html" index 9b61d87..1a08f87 100644 --- "a/tags/\351\235\242\350\257\225/index.html" +++ "b/tags/\351\235\242\350\257\225/index.html" @@ -1369,8 +1369,8 @@๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
try { document.getElementById('last-update-show').innerHTML = timeago(new Date(lastUpDate)); } catch (error) { - document.getElementById('last-update-show').innerHTML = '2020-12-07 日'; + document.getElementById('last-update-show').innerHTML = '2020-12-08 日'; } From a9f8e839997c107fb06a0889db0cd867690e7538 Mon Sep 17 00:00:00 2001 From: "573059382@qq.com" <573059382@qq.com> Date: Wed, 9 Dec 2020 22:57:16 +0800 Subject: [PATCH 04/37] Site updated: 2020-12-09 22:57:08 --- archives/2018/11/index.html | 6 +++--- archives/2018/index.html | 6 +++--- archives/2019/11/index.html | 6 +++--- archives/2019/index.html | 6 +++--- archives/2020/01/index.html | 6 +++--- archives/2020/03/index.html | 6 +++--- archives/2020/05/index.html | 6 +++--- archives/2020/06/index.html | 6 +++--- archives/2020/index.html | 6 +++--- archives/index.html | 6 +++--- categories/HEXO/index.html | 6 +++--- categories/Java/index.html | 6 +++--- .../\345\267\245\345\205\267/index.html" | 6 +++--- .../\347\276\216\346\226\207/index.html" | 6 +++--- "categories/\351\224\201/index.html" | 6 +++--- .../\351\235\242\350\257\225/index.html" | 6 +++--- .../README.assets/QQ\347\276\244 (1).png" | Bin 0 -> 48796 bytes .../README.assets/QQ\347\276\244 (2).png" | Bin 0 -> 35590 bytes .../wx\346\224\257\344\273\230.png" | Bin 0 -> 39356 bytes coder-java-interview/README.assets/zfb.png | Bin 0 -> 45458 bytes ...345\205\254\344\274\227\345\217\267 (1).png" | Bin 0 -> 42067 bytes ...4\344\274\227\345\217\267-1607525012143.jpg" | Bin 0 -> 20017 bytes .../\345\205\254\344\274\227\345\217\267.jpg" | Bin 0 -> 20017 bytes .../\345\205\254\344\274\227\345\217\267.png" | Bin 0 -> 67023 bytes index.html | 6 +++--- mylist/index.html | 6 +++--- tags/HEXO/index.html | 6 +++--- tags/Java/index.html | 6 +++--- .../index.html" | 6 +++--- "tags/\345\267\245\345\205\267/index.html" | 6 +++--- .../index.html" | 6 +++--- "tags/\347\276\216\346\226\207/index.html" | 6 +++--- "tags/\351\224\201/index.html" | 6 +++--- "tags/\351\235\242\350\257\225/index.html" | 6 +++--- 34 files changed, 78 insertions(+), 78 deletions(-) create mode 100644 "coder-java-interview/README.assets/QQ\347\276\244 (1).png" create mode 100644 "coder-java-interview/README.assets/QQ\347\276\244 (2).png" create mode 100644 "coder-java-interview/README.assets/wx\346\224\257\344\273\230.png" create mode 100644 coder-java-interview/README.assets/zfb.png create mode 100644 "coder-java-interview/README.assets/\345\205\254\344\274\227\345\217\267 (1).png" create mode 100644 "coder-java-interview/README.assets/\345\205\254\344\274\227\345\217\267-1607525012143.jpg" create mode 100644 "coder-java-interview/README.assets/\345\205\254\344\274\227\345\217\267.jpg" create mode 100644 "coder-java-interview/README.assets/\345\205\254\344\274\227\345\217\267.png" diff --git a/archives/2018/11/index.html b/archives/2018/11/index.html index 93be034..989b0d0 100644 --- a/archives/2018/11/index.html +++ b/archives/2018/11/index.html @@ -1092,8 +1092,8 @@๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
try { document.getElementById('last-update-show').innerHTML = timeago(new Date(lastUpDate)); } catch (error) { - document.getElementById('last-update-show').innerHTML = '2020-12-08 日'; + document.getElementById('last-update-show').innerHTML = '2020-12-09 日'; } diff --git a/archives/2018/index.html b/archives/2018/index.html index b81178f..3ba6a67 100644 --- a/archives/2018/index.html +++ b/archives/2018/index.html @@ -1092,8 +1092,8 @@๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
try { document.getElementById('last-update-show').innerHTML = timeago(new Date(lastUpDate)); } catch (error) { - document.getElementById('last-update-show').innerHTML = '2020-12-08 日'; + document.getElementById('last-update-show').innerHTML = '2020-12-09 日'; } diff --git a/archives/2019/11/index.html b/archives/2019/11/index.html index a08165f..138d4fd 100644 --- a/archives/2019/11/index.html +++ b/archives/2019/11/index.html @@ -1094,8 +1094,8 @@๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
try { document.getElementById('last-update-show').innerHTML = timeago(new Date(lastUpDate)); } catch (error) { - document.getElementById('last-update-show').innerHTML = '2020-12-08 日'; + document.getElementById('last-update-show').innerHTML = '2020-12-09 日'; } diff --git a/archives/2019/index.html b/archives/2019/index.html index fecc531..2d34c1a 100644 --- a/archives/2019/index.html +++ b/archives/2019/index.html @@ -1094,8 +1094,8 @@๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
try { document.getElementById('last-update-show').innerHTML = timeago(new Date(lastUpDate)); } catch (error) { - document.getElementById('last-update-show').innerHTML = '2020-12-08 日'; + document.getElementById('last-update-show').innerHTML = '2020-12-09 日'; } diff --git a/archives/2020/01/index.html b/archives/2020/01/index.html index c693da7..73b7be4 100644 --- a/archives/2020/01/index.html +++ b/archives/2020/01/index.html @@ -1086,8 +1086,8 @@๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
try { document.getElementById('last-update-show').innerHTML = timeago(new Date(lastUpDate)); } catch (error) { - document.getElementById('last-update-show').innerHTML = '2020-12-08 日'; + document.getElementById('last-update-show').innerHTML = '2020-12-09 日'; } diff --git a/archives/2020/03/index.html b/archives/2020/03/index.html index f4e46b3..98f84b9 100644 --- a/archives/2020/03/index.html +++ b/archives/2020/03/index.html @@ -1092,8 +1092,8 @@๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
try { document.getElementById('last-update-show').innerHTML = timeago(new Date(lastUpDate)); } catch (error) { - document.getElementById('last-update-show').innerHTML = '2020-12-08 日'; + document.getElementById('last-update-show').innerHTML = '2020-12-09 日'; } diff --git a/archives/2020/05/index.html b/archives/2020/05/index.html index 49f0209..cfc6233 100644 --- a/archives/2020/05/index.html +++ b/archives/2020/05/index.html @@ -1358,8 +1358,8 @@๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
try { document.getElementById('last-update-show').innerHTML = timeago(new Date(lastUpDate)); } catch (error) { - document.getElementById('last-update-show').innerHTML = '2020-12-08 日'; + document.getElementById('last-update-show').innerHTML = '2020-12-09 日'; } diff --git a/archives/2020/06/index.html b/archives/2020/06/index.html index 6a684e8..2280264 100644 --- a/archives/2020/06/index.html +++ b/archives/2020/06/index.html @@ -1086,8 +1086,8 @@๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
try { document.getElementById('last-update-show').innerHTML = timeago(new Date(lastUpDate)); } catch (error) { - document.getElementById('last-update-show').innerHTML = '2020-12-08 日'; + document.getElementById('last-update-show').innerHTML = '2020-12-09 日'; } diff --git a/archives/2020/index.html b/archives/2020/index.html index b6be458..3e03d09 100644 --- a/archives/2020/index.html +++ b/archives/2020/index.html @@ -1754,8 +1754,8 @@๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
try { document.getElementById('last-update-show').innerHTML = timeago(new Date(lastUpDate)); } catch (error) { - document.getElementById('last-update-show').innerHTML = '2020-12-08 日'; + document.getElementById('last-update-show').innerHTML = '2020-12-09 日'; } diff --git a/archives/index.html b/archives/index.html index a63bd92..d2a3478 100644 --- a/archives/index.html +++ b/archives/index.html @@ -1101,8 +1101,8 @@๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
try { document.getElementById('last-update-show').innerHTML = timeago(new Date(lastUpDate)); } catch (error) { - document.getElementById('last-update-show').innerHTML = '2020-12-08 日'; + document.getElementById('last-update-show').innerHTML = '2020-12-09 日'; } diff --git a/categories/HEXO/index.html b/categories/HEXO/index.html index bbb1dec..26dac1d 100644 --- a/categories/HEXO/index.html +++ b/categories/HEXO/index.html @@ -1362,8 +1362,8 @@๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
try { document.getElementById('last-update-show').innerHTML = timeago(new Date(lastUpDate)); } catch (error) { - document.getElementById('last-update-show').innerHTML = '2020-12-08 日'; + document.getElementById('last-update-show').innerHTML = '2020-12-09 日'; } diff --git a/categories/Java/index.html b/categories/Java/index.html index 542df23..61163ae 100644 --- a/categories/Java/index.html +++ b/categories/Java/index.html @@ -1370,8 +1370,8 @@๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
try { document.getElementById('last-update-show').innerHTML = timeago(new Date(lastUpDate)); } catch (error) { - document.getElementById('last-update-show').innerHTML = '2020-12-08 日'; + document.getElementById('last-update-show').innerHTML = '2020-12-09 日'; } diff --git "a/categories/\345\267\245\345\205\267/index.html" "b/categories/\345\267\245\345\205\267/index.html" index bc27e75..0166bfa 100644 --- "a/categories/\345\267\245\345\205\267/index.html" +++ "b/categories/\345\267\245\345\205\267/index.html" @@ -1090,8 +1090,8 @@๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
try { document.getElementById('last-update-show').innerHTML = timeago(new Date(lastUpDate)); } catch (error) { - document.getElementById('last-update-show').innerHTML = '2020-12-08 日'; + document.getElementById('last-update-show').innerHTML = '2020-12-09 日'; } diff --git "a/categories/\347\276\216\346\226\207/index.html" "b/categories/\347\276\216\346\226\207/index.html" index a5fb85c..42ae787 100644 --- "a/categories/\347\276\216\346\226\207/index.html" +++ "b/categories/\347\276\216\346\226\207/index.html" @@ -1090,8 +1090,8 @@๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
try { document.getElementById('last-update-show').innerHTML = timeago(new Date(lastUpDate)); } catch (error) { - document.getElementById('last-update-show').innerHTML = '2020-12-08 日'; + document.getElementById('last-update-show').innerHTML = '2020-12-09 日'; } diff --git "a/categories/\351\224\201/index.html" "b/categories/\351\224\201/index.html" index 57a36a2..223d65b 100644 --- "a/categories/\351\224\201/index.html" +++ "b/categories/\351\224\201/index.html" @@ -1096,8 +1096,8 @@๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
try { document.getElementById('last-update-show').innerHTML = timeago(new Date(lastUpDate)); } catch (error) { - document.getElementById('last-update-show').innerHTML = '2020-12-08 日'; + document.getElementById('last-update-show').innerHTML = '2020-12-09 日'; } diff --git "a/categories/\351\235\242\350\257\225/index.html" "b/categories/\351\235\242\350\257\225/index.html" index f58914a..6f8f794 100644 --- "a/categories/\351\235\242\350\257\225/index.html" +++ "b/categories/\351\235\242\350\257\225/index.html" @@ -1370,8 +1370,8 @@๑۩ﺴ Coder编程 ﺴ۩๑
๑۩ﺴ Coder编程 ﺴ۩๑
try { document.getElementById('last-update-show').innerHTML = timeago(new Date(lastUpDate)); } catch (error) { - document.getElementById('last-update-show').innerHTML = '2020-12-08 日'; + document.getElementById('last-update-show').innerHTML = '2020-12-09 日'; } diff --git "a/coder-java-interview/README.assets/QQ\347\276\244 (1).png" "b/coder-java-interview/README.assets/QQ\347\276\244 (1).png" new file mode 100644 index 0000000000000000000000000000000000000000..5b24bcdefaa5088e17faca9933e7013510ed283e GIT binary patch literal 48796 zcmdS9^;;WV)Gj=@yIXOmxTZM8wYU{6Zbgfe;!v!(YjL;Y#ogVZxD_Y3e0kpU!~6XS z=UlmxnPgVh%wGFmd*4eU)Kp|KP)Sh%0Kkx!`=|~8FmeCAkzt{~V6Eq6Ko9U%63P+) zP#cT(Y>EK=PGv5qt_%R)3;+NQ27pKCPvAoUaOVVoV-o-Xr2+ua*UZ*W!q5wdW{R>O zfw%u&xgEs`P)R`kqlBjC%D(b&wCF(C$tscB^WO!jE{FtjCW<&_J?5 z+l2k|FE-1iM*aOygW$**8GX-MFT!^%A%*+&P=^6XY4)TxsrOi;6(n+$>-VnC0qil} zR#MU9OVx?(!p-SL?gjnTP`&oCwH(f5u8hr3KP*!0KzsX}xihDOOYouaBiU>(Ax&(_ z0S`g0y^R0#Y+Dar{@Z2d-k8Vg9v?Uq<<3y3!p+%wK4kRb+3@q*$=$Q3$Ll`YS~6q^ zo}3;eEeR`|BL)d;qt4-TC`GKXg~g<$Ho(y~GW=U_AshQCApbrv&0JNfM-kIlCALX+ zDE+KbhEm}X&g>iCI8VGPhfLwBAt8%43Xhx&VizGiph$xqYr=hmz`K^*rNBfw@X{aW z9RL&?y@OAZsz!EH3JxRnPFrw&pu UpU<*~y=jS^ zx)94NFdf46g^#*N313A{p~x8JIc88CE}!z=p#n0-MOz#}J_z6eL+ZIfEBkx>fpnjD zM>>>4OU3aUUdjk?@xfqh^!Sp0%%55$-wifS1+Tycy;`^<>%#N}2lS*St|BO_K+vUT zhQtae^W&07etW8}8D+i~VOD8|1;lvJ&_NF_!2Ky3ASm=`@weCtMy=~hlT?cD2tmZ@ zRH{b3?|2|9pBe%cy4I={OxM&XPTp{@lf>V1hb#ne=-|X3cR~~Juw!wwd@Z)7LpH)8 zah!e0iZ7W-m!hfmOnL%ELUcX2f=cUj!Z8&AP93MD?%eNLsx;>vSd$%2ejnAfMUsGl zLDE)TDpb|;tsVr=wn@X4lYnP<)v)sm=7)DW)fN#t$K(KJffMMlZdb}UTX{8Mpxw>K zVtu@Bdyfv8ZgErcb TnEb)2@V@hVqtx|G^naE+;}vYTZya;)@v_~wRgXFfZi*ZwcrWQ@DFI{JvD zb2oH#7w!#S%=ik%kFswm(R{X Ok;`9OdIR{5iz6`cE-MZ`*wZmt18VZV$RoiIJjEWg9G+Jt2o9;2{B zA;)!zU^>*_MzF}#GHYz45Es>u#x% ;hp7mm)a1_GykFv=ZvLy#$L_0+Hj&W3Gb z1dMFRlb`B{Q=cItscXxG6uGO=b7mj<838~_wq4U8>S%vldyD0esfd8)Dc%yOj$je1 zE3#-IU%Ks7C0pzB88+EFmibC?)PxY8hK7bZHz=7Zh1Zz@(s>AGQA@Js7*A&Ful+ n}^aBqbBy@99( zJD5ngX5WGV-2kM&D3NLwrX$F=AMpdc$Ea_czwoRs_$uV$1lAD212T&?8Eaq30Ipk9 zp#2pp!G8N1( #ah4 zY+4^+{X;?iXFF#oGnMj*KkB?Ub`uv~BX%8^{B`NQ1m}Ip_JrUu_Ct`s=0L2qN;KfF zJ5DK6KAxT VGD7_C!C&H)l;|jIAHzj5-R9}e5Qw_^)Wv?%nW^c4)@OcxRFXtxH2hNb zO~&M^yp;>So~AhD3$-1fm*h_Tl!`Y5BXp@MCy1i@7=kS~+B{eNhwik6B`mq(chXka z@voRFB_!A|EYwZgUz$So1DraS#<;H^;XfDjk71pT#m$s_cQ@!f^upLO2*2l{^eYxb z72iIrr>00uYZKo335I)d3`#}dp>Xkhj@0F--l%vd!nV5mPV8Eb+?gcBxM1Bt4;fd| zn}A>!4<~5KMUX @}=;yqgI;=MJ9Q>{e*1w&^XVqc&VufBgY9BNJIodH& z8N;m{RA}Tw?iI2>BvWL3e#kn^+w&6n&72hPkIc0?-wh-NrRq~iq>Q3Fy>GUoeUXpK zPSxDipUYb8nE;MapTMz$2%WziojV6Pnzn`_JxB#on8lN8ah~}ya!XhacbMe7GEG=Q zDWN%N*Y^Xr $jXM6g7OrsC7d z?J6LZZ{V;YiU%;q)p?blHg$s&D`)l{c(#Hx(=j@^|1O3i(K>jJxHx%TD*?a2!~1!% z+9E#gWLdryr{Px#P2G~Ln|-<``U4D;Qacqs&HhE3y(gA5%x6EO2%bMw`AZwGD#%7J zzA*Y{USU~=Wy`Re2JqM-rf4Q?`iA> )5n#3+A zw4X7kQ%aL%cuXXgI|ah3mFIk$=p*fo1==4i{qzV~E+^8UXB5}jwzL!IM(zp54 VZiXg~eI5Qq z;ohpwk%zbHTf95Qa#^gCI9Ja}%=L|W431Yc3NJWJA;y!fJ%Rh>^J`YuIV5e(6cKX2 zma1}xkne6$Rqy-#QDdsy30~7ET#^X!LZy1T64XCx{hIZb$Js#5&9f|1GmGvg!N7Po zFDohhMvu`3>mQRXGSK|#OQ7hByU6 q#~L5B;;eQ!U@%aYW2p~ z674(G+*+L)mk?gYLSBte{wxnmmpVd4IA@(RHyLrs+$}o5eV^*K>Ic13$(t2Sw#Rn? z+cKa_bG>S-#S@p;w}kSyraZTED;dgr;ZFYffZ_9s?JVQjfSDcw2&aD8ZjVBX5`oJc zKMRVJM?Bu+6 lo1k(AP zeQDY0FxWDE&17v3y;J*XV1_v!XDu=-pzE+4JCtQZ5KsPZzv=`aZk}&wYAr&- dt~-N>eVx{Z-2th^-OZ5bx3Zkh5^o(GiPkYy~G zrpdpIj)A!47>#_|JOobkg2B+pg5h18{7JFBmF=jKuy?|%WK7M((;T|6cRivvS$%eT zshd{cR+wLg<&{(@TK%KftdfyG9>8gxSecXFjb(eLYMOkMGdwzW&qa;DgXCm694KL4 zc}Zru?ce{C3#zqOsde+hT3f6-BZA)BKMD(SjeGqk_^I}gAu0bOUd2 `q+ z^Yh42@;tT`?@RM(J7Z}7e7V!yvge(VE@N ^66` }|Pz0CTF6-9_d zVIoF}SRANEvj4HW_S5!8rf0sC6#D({6H19dNy$HpI$p@AXrJJCqkO%73$0q nkKG(CJ17fc7JG-`Ff;ivM0rq=`$mqMv7eCl0e1V3Z^A z97G4)Z0*|#a6_NoKLk1$K#_K_bYIDXgY^j5r@;Wl{kue}<3P_@(vek>=hwjuD_pn& zvumex7yx?E%`>*nj^+W>+QsN^b})FrH9m#*FG>J0kwG!RV~G7aIzUXB5paqgyw0@V zX$b<-J>4q;5P@`J11#`*_4Rf@lIpX9N6MDGSs1k@dd|H;4=3|lvXIUMKHi^5?*?yl z6i5<Bb&?5Bwln%;Uplrm^DRlPWOG;5IE^H9fq;Lw-gw =X7>!cj3H{N$@+MQrX6*1JDPP0PJR9F0)hAc@X zeXxDyfBM0I^i<@c^=n9X1i3b0!m8DfmcX&Y?;dOVoaSjwme3I-O^Dpf?<}(LN}hY? zUL*`vBRHqD@3KyFtLl0a_+orPl&{H{%Q8mM*--+MeZ2xF_x&D4{Nr6New6?GHw1%H z)=T~ldKBFcAGs_?Kk5${IzT4>779GNz_;AUeD1Kacevwk*mvjI#jR|j0TcP5mHh&B z{dm5^X}^%^QY-l3$cvQmO2=4k+EI^aa+<`YtDt&_5AtZWP!vFlr?>og2Y DQx)OI70@qv7vYS|4gLAYHI@`POSLl{|)pOz!z;^=d4NhqmT(hDp;_&8SjwH|wZH zdPwWa3RX-r_(y$JXMW+`x8;uh`liKDe9m(3es 0V}>U{?g#Z%DPri=77 agGS7pG6M|Gl_mM8=jt8G|}s0g`0pqg*WVJ?o3>{m9@kKm~pED8R2W2wUZ_1#YI zAfHt|1z_3y9SA#2Y {betVz3~0B|Ays z>ZGbD4S!e_%F-c&GE3VZJD%H!Ok#E=kmVK8teku8ub%oodrhhGi)hAPv`SSuwmd FdSz9q+KUw!gp579%pw$Glx(mU}IT;nIhzmyVq+Kb=2B173_66Ygj1=^ow z?v3w}Fmn3;F;_*V0lfy_VOlhEeh@!*DT|8x+y$V0IU9!aZm+44ugYORe@j-I4;w(b z%Uo3Mjd!XwR Yt%xP;J$X!AbP*{XHQbIV|Ax8r>HtT*SkZ zd9r1*;pC{6@B?3XguCc%1qNPMD=IwkYj|q7+%9?CioPs}86KX5i;~C3LSEgng7#gt zM$844i)1&WF;#!h4OmD`uwE-y^oL4Fy{pyQY|iq%a-RObg<5R~ZZn=hDf1=Alg6W- zuG{<|dIa~qVM `A{y1 zH4?VT^j6AAwKyH!+>4UzeTz$Qtf7k9@CSShl-Le+t;7QJYIp#pOwQTGw)sFdFfZ<6 zuH5vGB`@y{&!McZ#32# mG}zkkk|6=3#{@4~JywvALj zwWq8ydrw+YjO)zyWg{AE(-jggfmh)pNKGanaB2G@s?xBbaZ*c_<=^PvNxXey2b35~ z9JujiQ>9IexUpXj+tEb#aUGvlirt|i;hVs08L#VD!Gd8+dxJWTFMf)f7p7I*^J-_r zbaG!JMSoL55-&EQ5bD|&q=ClR{PrQL?}XveB(zovow6{&p~e*1@a+~L0CZ{#JkLQr zXxkPr`#7?~uWChEN#oWgcEVI1pqV?IKfQIikL7HsV4O1V6#Gf7@aerAYOjXcT+X5@ zocK=F<(qQzWb%g;oU%YjroXb8fkcvx@y;gF;F%aLJxhQmDXv6V{NS&cYK{+J;D>Qm z#DUTTX_x;~c!h6q_CY^M`yN9e03V}RD*MO0)zIOR$Uj5xQd>@zbwl)XRXdD=75@J3 znkxf|qa=b&Sd`!_<_-QVosAN^d8{VA+aq05PeR0ZXo5snt7D!wh)fPvApNZ^GKkKA zOu6!G?K9%IR`kS0*0LbnC`Jm=wC?-)I~I(7?he_L;_G;_=L&+BT>~}!vP)(=gAdU} zo4JA&cY&4)qP)tl`-s5bI39~TI5~&6M{g#~lLSbpF%(`frYl7P^gn?9Ml={1?Tx(i zv5j+8`qWP|#9CIuGY+m+)T%rKi<+G!iNz+3xH4liGX;>wBku2(pFsBiOqI`X+m?G> zxNod%7Skxi!Fw8-BZV~OpED@RM})X2Qj9o`=E_5Cx( @3oYuXPL zncl^=tQ?ft`WxGmRSOA5bt1kx&EDTMDmTlpO4*?!-u;YFJ-%bzrjdTqSP|+MTgdVW z@kW3yZwvrP7?FwEQ{k0jTgR#=m#h=?gQe?J3LNz*LC~@=;cnJ!Dz!?qzo5M*D#U=Z zZ?!m&k>N*^GA+=1L&Wh{FI4Z^J_`{XDtoZ?eioRrS-gL SJ#k==6*C!7emg#2D5iBFTl%xzfHe}^dk&)xzz>?Lt9QUB9&=E(da?S zGuvkeV=}G@sGXuL-W-}<9@_wiH+E$WJj-~qdbn8>7S-)x#kf+wSNgzqJ197#qY0Y2 zeBDv ze$Qd#6%=R5(Mce17;f*0TzT)T z2nV@u`s;LgQ?jhO*TAb{ODdM%XoCLtr&D*p*=vp7>QJJ&fbtGf;|3D&u6={!v}e@O zL@>RR$19+9yyE7teNNwXilI~4ywa!9Ki$x!qm_~0^0L)N_tydz(7t5L(P}hlo<~rf z6)L@mG!h$P8P!v9{M(y!KLBn2fnic06Gvh~pxpp*Y@4y@a*eEIY}aesyxXfI45ZlE zGVwFfJp4L-kv2sY@~T*w21ECo (Y9 zRa3;(GTt`etDFqcM kB1 zJcnpkERcYl7_Qw-NwH0vg=uR>TKR
j`s@^Is4#5MQQC1(=tXU0)eXS{idG?MD6DI zS_!iVaMSs5bQ_~@M;g+YD#`<6`L)T+d)JaL%V?;nqdR3r*9Hyl6q+F5se6=t)jzBK z%WJHoKI#}EZzSC!L{?vR&L>*a6|v))*(9(JK9_$@oC%U}&i2nZY_|*I({xx&o2NF> zs@Jgux@4 pq3oGhS60 zfl8)tfoCRtGn8?4c9|Cgj2jaAzOu^jujACAU2m4mn$t trGfQ{ (t zf8O~EwjO#X g*E{C 7g2h(aY7+)2$Mx7N&jFRno+ODP}yk;LDm&V#o4YG)55FuCY1t~ zXgfGG)oGS}dL`{puFk!A2*?<+%>qmk7>P$zc_9PLApUEHP%Y)urtq>|m6gZ3V`)e| zJ9EkO1n25MnLWqQ>ZqIvI32n#yV>wwxq6Re+Oc5w;9&%mYYn&Hh1b8dN8MR(#W!4L zLv)si@S*o&Nw2h$ux=u39ie%PGArHQQ94@XJ_z@`goV%q31?4V1TCAR-535pmUE3U zHUNX)H%-r%g6jEI@2D33)XfGGwt?U^LP-4W#DCPhTFY7-N;N)ExBo_2M1?np59+2F z)0*zH3-P8^Jzs{3SfvV0HYY2c@n?}_Un});g*^4AEyg1y+lvtfL-0AS1UdeXQA{x~ zJ3$nvmDr(qI>m6{ tlgB%)UL{iop_FLOqKR0IV~oB%3Tg fEovzv zpp#iNoen#ArpMKVUQ{Zxi7FJUqTbT$tseyuRi`83%?@j6z=ZRd+10FEMU(=4X^W3< zEZ5nos9i#{V1M;-9kTe{t5P{w<`!QT;%yDRO^F8+M&PnkNqnsnnume1kpXCR1bEJz zhLD>U%<~Ep%`u{`$m%}#uxecXgM1v18&e;t)F5Z>3f^HGa2wS0H$x2yUJ0?8cL&1L zW}0tLXFUsZuKQg=wwOcbLm6D`7{x#c_6;hr$6lHl_)S^g%SYVA1XPl+RK9y{9K6?X z2%jc=6c!>-tPYCB-QriY1P_ExAHR}m`bzIqxL0LF(k~)$xM!3_%;7nlY8tswD0ULg zyFZE+3uEtUWeEqMX5>piQ 4;YP;D&{$8f&cNyX`bQ >6q5+iipM<{==Ie&`>zO2`q``3ty-3MQ@9uK#ukZ95&P?00_Ex+&?nmyL zq08OR%lILelz;pHxoN{E%39C3*x(0bh&paedngc31q_C|ixjJHdj%$7@1hsIp<{N) z*pY?lwW~)%89A44{Nuv?3ajNUJzC=FCzFLIp-27CtbR9iK+FUT`kPy B=K>ZHu!wnl)z5&?{=p-D>gORWez8J1 zR(Dl1U+owe2GA2J>|d_9W25fKzyH+OD?j&VUNcIQLcTzYxHvx~wetOTSKd7x>7JYh z POy+7aLQ0XX00(BFev8MMbCi;tBhVauL%=yDzLV7l;Qz0 z%8?CTJDR~s8s1qRK1w5AgZi?|(>Yl=&3}-wqsv>bNJlX*(p3QbMf$H-$)fG8c<3Nn zQ1*m% ? EQ9Q#vcTP@NA7~kp7es>^(ymNtD(m3gGTO+Sus+0Z~2G4 zK;|*gutF~ZAT@x3S>-eeebLOOng&x-nSt_O;y1%;^?^*)##_M+1MP}6F6j OcN`x{|=-iH-gFhjdvl#E4iL-5H%MAhJm0pJ) zIS!udZ6)=%2k|tI1~+8zqjpC?+h;2%RgxAJbN~3Lnt!e}e`o}gyQ5D5*%D*- d^zFRiqL#J_;V-9oAD#6$&N5AC!*5I!<&0;^ zZ>Ii{14`=OasEe{b3%9okk`yY2!E=6^n|@t%#fNm;1-?aU}jbh4}-r|e0U=-DZ(Ps zyFJ&SSlC=vu~Pogzij{MqSOw7=esoG9V*!Mm9DTC`@lG(^bNLh5}OEqv3$kd5{lXm z&WB@O&OX^H7_gWU5}d3N2EgG4+vS{)?=X7bl3!8dsz3&8EXY$K$WUAKa-J|{G_8OL zWDrPkK~6`(rlp>WjN+)j>`S)aYshoUsK8=>;)GWnl+l0IPsbb$d`}jCKRoeOHMcwv zC;Tc@g%h8#|K!8>+;JrundBe5kG`jxT|h#Xsd?7Ye%US*+MpbUb!H0 d?2X=Aqgqbdp94e|MZL?u!PP*oWcQKfZqnV;g~s5g zh+ts)!KXXr{b^!-ly=owW`#F=#ftJLk=EZo87=zp#`@;xlv;6-yFMo3VfW#(X?m6Z z@s&wv2LCDjHfJ@eRCZigZeSCwUWCAyh*p13tMG0~he+ZTFT%)B494Di7}ZsJK>G9} zzOqlns;t-36tpF%?m}aW>v6O&-i0OG#3RNb1eW%YQ!T)F+X0dVdL<3)oHXJ0|B|{T z`|nY^SL=+77MmR#Cj3IX_;dL;#^~lxwDVWUM-hU&wzvkZWxHGqxV#vFIgCT!?LY4w zPB&{Ga Mo0lqdLlVS^600Sor$`7D>&~UUgVCKfhy;OxOWnTH*hCT@r z(gNe(EUQ^uYy57FWRSTMXe=ybFP=Rf{*{6(8^~$BXHE#T)Zu{zSwTFmapiK9XlJp8 z`tVfj*3#ZuC=i|5GqP};oGPgjbtuXSwu-Y`Fkfj;t&}smCJhgY os>`>!)QE5(t0YbChaw91z4~jqRHx^dlDZ@}T{uF=Nvz%y zzQuU~UPkdJ65_N5p3q~1LduNCZ=0XyA$dIk4GV;$U85%4ZMnmLl1*^tSYeqj{c+`L zq}~;DtDX8l`-3AdG)4x)_t0%sb_ 9VPrxY z_vL<@1UEU)^#18N=zg!JZ*R!LcU0n5W$RYX8Ljq~Dj!IaYmJ#JV!1{KV<{eG*Z5(+ zgPvhWo!Os7zgpqo4%#16U8aDd2I|l{>4)aafkP$NXshE(_ d)Ose4Z+-4{pX>d3#mJSr^FY}ym1vUzp_u@ zc~qkOu)`Q9shT``c9;A9mKGZyj%R@-nA#kS3$je>Ymq5a`3Yf6+*r-j2*^KpaQsox z*7|{*^lmH%R$hxn0#=RSm!}raR)x5av`hklrgBY7*^tRSF5Zu(x;%xdLwMcHb=18! zk^@C{R>?dq{*pQ`_SM}P6=LmCgB6sn2rk2o85)4m{~w$*us!RJ91e|*Y(Nz{MYf`7 z%kXEDsyoGwfL1=Jg7?|GYKV*xj_T;|ZX)Q!z~PjDwF-55txM(nCnjJLj>DYOGM?q> zsc?b*O08&hGxa4IejmBMs+mQ$-+7d`ICR?QGR KG?(XAYSXT`lZAV #Y!h z&|7=h{#m#Pwio-2M(P(wlj1w!C;m6sO&4Ux{q$n817BOVTG)D5 XEY!xOyuzBl(}%evY?>nw1&E_IWchO*CEpKyuKyHxB-9b8ZoCjH1|WFh3K< z97-wVub-%*vu=#!
rkkr_(B1J+xAD)L#Pi3MZKQQ u?F zbPP$E_SNsBwN*n{GRjeTw0~}6qj* z`le_x*bg*%bve;JcqMWzs8qhdYL(DoV?V-}*VvJOSEACbs>&p>b5Y zQa%;(%eq;eYWP8kLNciix;^R?Z7-hy*Lgnsp!J+cZT8 tKf=_GryFnNk9S4~R<9Rp%wyzRzdS99C$(VWT#HK-gl(+pb=*eU2#5xN_BM<` zx0uH4!Q!n6r(yLF7NaDZ{0{!}@A= wb&-!lnk7;C&`*J 13? cr?)-#jGM*EDfE{>EG)`j6H%JS3f#eW_X7kIw^T zhe`lDGN*2})hKcpiSDh|#1cwlITaA<;_Yc7_}oH=S%qn*>LPg9L?>KI^B+s=8A>HW zo3;fR2|$ZN3HV9xCq6BP=bpw2-I3n@y7+akNp}1Ffh&t*jc<*0j?tvQvdP=Y|4#z3 zAocCm@2z3SRo{h(1s&^V`pSO@nh-bY-x~^F@@6y?FX?lr8ox+WP9OKE>u8O`G^9r? zN0_?90tP?gICmmd0JmE@z(tyo>oD_hxxZzYCtOwcKw(;anSqkY#o?nTcNqOSM-Mn< zT~r)OCr*+|VVn^Drk(ssg#VqC|N9ax%hNV8-}s-s74%^Q%F`9dopa+4ysE;Z6^Nwx z^&x2vEYO~dO_D~eePf+Iv&s{gPhF;5*SJ6G4eWW ?#pdUi% q=GzfKvH@O-ZjjE?mNn zG?iFyjTQL9?Q$F8gJ6=FS0Zzoku!T4nxRpJhv1tW`mVB8-B4=@lKQohMWoD-1qKYQ z^BU>XQF11dmHhrJw^LaWH|6w{mvdFz<6!sd%Cl~@Szd;V8>!l~p9&+GGu-Dz@1lEj z>eo}yAprFeG}FkJ0+9fRnPa2r%i(9uA<6b6$f6Yp=I<~0pZrcoUo5j+>syckTCETC z0$(+7rmQW_yL!n!gSWR;H+10{OR7Aj7{S3`_95&*lpP$p<`G=Q<|zu{;1>yk>i2;W zW{u^7LFgjy&tr3#VDr|}gYWtu?S%0-9V2Rz8j5}`n6m?*CJaD~JBd}@fsQ(*72T!m zN_*o)n)s`H5W%^>z+QDXX`Ogd(=X(%2m35&W&~B&&++#y%UE6gex;;!|D>B)&{jrQ zyD2v^yv*_z!J>2j?Bwrvzg>a%7>P+_L!-su!dvI$y%%2*qlLUt)&JVD3BB~fu6pgG zlh)J{v6K-1*05ujIduG>4`n5>^0QhX@cLPmU{R3d(7up_Rtye7E=a1fq1UVNk4rHr zk4$x{;^Ly@j8bqZ9KG`pKNa)TMl>4Uu9?A(q+}ftvQ+`%TtRca+Q8KW=6&n?dCP94 zjcQPH+qP%Cc+xUOd~P;^PyH3(GPXTS7%2Q#8+|izfNR-M3)xToadyl=oo$lypv-Q} z7FcLA`S?do4%x-Ua9DRj-it-4`>O%ACYp1?$X=oKRGmTtFPGGo`Er@jl+N!IuK?yI zx(TGC1p2BjR-GYpZnm6XL%}GFCH?|>R^{quV%(irNHKf>Yev>onR%w19Pz@J2N_0^ z*d02JrVJ8BX50}xg3@5j#}fhWsi#z5@z5ikK>1UdQrR@~=gem776dWes6-TqqISp{ zE7Pe~bDn)*OBG6~*dlM9vWVjk{NPWd2xUgqBX?!ES1|6hya##?$KL&V%btaFn5>}v zHGXBgobq__ZmNERBMO-b2Lvj7+1pxqc-|y>u0*mVxIHBsA_x!ePqzE)PXt8FcWL~E z3^7sDFUvw4J6iKiJ|S?JHQ5}nV6&osza1u9z!P6E8&%~V4wp!TCR>5SKK`yS7C=p{ zaQyO_A-du5Jxi99`c+r?NIX_5zf3TV1gq6o!;wszpSg` 8qg}k>T9#VDI z6+`*c_m?W1tibkQBr-B&inPLqT@uU8kEBB@gnE}yFBacHg@t?%>VOUtRzX}Y&N$%a zDY`gD$SO2rSQ)W%GKNXjwM}Gbf9pkmVc9DmlIor!;0|xj?@o+Vc0dX8U)W>RqF~KW zQ2%o}*}OCrgIlY#S!<^8Gvyl?!mihDU+0fYxKkEo(lIc(*Q!!kYi8mb4LMqLOZPqo zsECw48D^Gza_TRSw5%2{;_)^TtNyp^^K2+N)K<)dt=h}h0%e0rI1DA*LJ4Vlb`@vo z=(HT1hDn OMtUS0vr4v`^js7m8 z3Guzn&4 ~kU2_AXq{HN0~ZnE{74x@B6&{&8n*hVR?pn#Qam7? zsxj~TK~l((1*t~hx@H(-%oD6-{^!7F+f}@~TQo;gi*>NiWG0X~DeCmQVjcyHIqD7D z-E7qIT{?3N-|;ZiYCX=Ck0;LUNpQp{D5}==pUeK85j@Lr(l~}k2`wwRyBufomTB#^ znKm>|&{ajMwfP_@wsHR1H$S?O*ROW-&iy<>2!tL8X}Erw-$U!ZuUDeDtB3f9mq3#? zR!wE(mX+mZoDKG$S^hr&E zi}2ZW3!c)TO0IGVpflP4@G2Le>T#c~y^6tfyKP7_{h%9=jG@3jM9i9e5St2`ckqf~2oWAxR$M;h^zyQm)=Zn~Ec!4wy zD}q)VJ9PTaiM0uf`L{*6-p_+$xjs)40z{`|;BKBQ;=#!pm# qb|l0yJqf zRt(6w`n)z~$rVL;cY>};K3??h*7ffJRPM_AZFyIR4?6O$j-aOdoq4r?qK#JaQCHrX zXFv(wygMg5GDjZR 65l=(dkvbz}!BU@^0l zh~>mpKcaVee0Du1zv^WzD@FB_GBZp0UMiWyk)l)T)`cI)1^IW8dQ~+U5ge-$sg^`o z|7M?uNY&aRVhBm(L3&KgIS|CYDAAtbJ|=1B67vTslzUjW?iCJPzzTC!c0W@3Rr)E_ z=w1O3C%4)Sp*WP2vvD_pt}2}u8`}nz- joM z%yVv=V2~L@k|3c y-MLpd-~cNF z50yIJ$2fqWEdZ1qyo3c$A$`x&MKEYcD*(C4CD!bf+~^X6#0@n{@Coc0?Nv$ztMnYeWUDMG0?2)~6@a(6NJ-3;x3~xcI`&_~{1kfZuVXua0E?e1 zBCN4OB3HGkBiIHiX$(JG;3qZDQmexyf9rbd^Q3BZGs(3b1OXyQrbj2sk|YKZgoFQZ?PjXV8wVb$#9{l-0b(jdECn4x uig%ta5-Lo zk)K&kC!LpkuT~U1&1aD~JNa7V<1XK&wN!LY%9zJopTo*j-|C@aWp+DCAPJG4v0QVm z-}Ioql{dWul5=3&QSuhMm8oh>xPOsbp6b6Bu-iUYmDXQ>PSybgSiQ7;@WW+vM9g3& z=@5g6nwqFw-rrXnH=@BGcD^~EPPwq}=RWjUI`r7SnX2#JCicK(h>Yo;y*q{aMU1Su zz6Df$WCsvn(Fv{kvrt82l|hF^C${UdYQ5pJ+9D13e3mE3lWK!6p=vf&;jPBB3Z-le zOBrZfy8r;fxJ+yFy{bqsNE6!xK!+Sfr(WE{6s0%i>(B68FYsG0@H;Q_?mwdR7GxQs ziJA|=pu_3lqv(+1=-}h%;G=2KhikZ3XY2c%=3W61=d`I*%PIl|ac-O5x*~&n1;9DK z6&@2(-793v6AX?2Y- ml#C8K zN*w<=am49#@X^T!cE7_r?x#^-K~<;h+4)@Th86TJ0#;enVjEETPI zX$MPo7^x1_I+yRyLz gyv>ZgEzQTX{Eq& MftyZl4=(tRPM~HXNH2ozwSUcxTI>O>pcM@`G6IZ4+ucF8DMzR zCj0%WO`yKY %tT6z*PW?Gg8{dDe9sz5A8)wvNafr51yEat&Y0UVm z89%w%+pfn$Y3L>5@8`t( ilNTQzZC_!n zXqDNtl+ICCz(f$F5(g_RD`pdllzLdU2C9brANO{}rfygDLi^U3p2zoFt4F{(zRhmb zmK&I&!?N-OO@pJMpZ~IYNEq0VHorkepA~e*jK;L-Jx#0dQ{zqSGv2!1n8qr3{?l9I z6|CfI>nbb$1zzN7K)i^c{oU{tp{g`pV*Yrbq~*6>tUPiPoR-|#|0WI}pXt^mneSCq z;i|;P+VnnyA2)9nNPN5%099B>I9AEfXg*+4%_%1((Cfo83C`!+B8`k=s*LZyR*!%M z3$qZ0o-7J1d(5*g8<4Uo_FmBKJz8r0fG1rT#wtXyuP*}W)vS_cO6-+u3e<84jrvA( z^jj}hR{Wo8ELG~9M!{s-nOx3Fcjb{==#ZmxXZ 3NxaHKF=e7CfbSFQJT?MMVJ4=UQuNYck$j=%TlvQ lqMA zb65 wy8!IjJM0E-CAzTOS?4YV<9oy$u){LSSRhow xrF{eB|K=rxV)ppICzD6)F?*0R+5srJ zXnBUEX_}#HoY`nqF}f;spFLmq6w2=_R;C4Ytn7AqY6mknsk&NIC;L=jW!hv&sq-Q` zgZ#&GQLiTriM)kwk7Me?_D_;j|FD`F07WG14RH{yu+9VtfMO|+J)I;}GKzIMruepa z^#0?WFk10jFO}~8g17b83iU`ogV&-lFzd1{Y&a~nr=!Xi^!FlUlTc_5v9 e8{ZYAV=Y?)gHeHqYcw3IZ4Cmwicyd183V Yx{7WJX31v?a)YJq z+MszNI>{RUbdlGY96cMeBD|H~PQfhRUgXCn%iY>(kq`KJh~OnwUgQ$Zci5Q4YYU3E zQgGns1}0z@%gX9o<#UVGdM&p~(dD6^G`|pxx}vKTJ2gCfZEr+8YFFoQnEd+K)H z^~c=lH-ZBCLazM{Kfe<14a^E^QSBSdO5>-}`YQ_xet-R8R)fQOZ<_!$=Y->EoDfw> zlivBtEGsMiT|T=Q0avAICi|uJpT+c;PPQx $LWK|@9+2Ky<)MqozxB9-|N~ZR_`8GTHUMa 2^~}BlrJo=I4!v|zwP(r{^e;12k!xME^Je>4QJoT%G3C%wEo;h zZIMPjS)Bq_8gFIw^K&i$QtfA5f@az6;+JXsl(3?|P P+we5r%9UGN1XyVoszVh3Z__OK%%W;bRLiufcDDDS4yuOQhehNJ{^rIp znFCw%R1Fis-ZP77(1)E-mz(QJKr8PN7qH27ZIy>z0-R4|{Q`SST!cyYgfUj_s!+eB zs|u}QVG%&?^Q}p3&Q;~7vQX)j1up6oq-?80z~blJ +{U4HqtcwRJI3yPZzPy?Duy{Tk4oKT93oba#bFHP-|G3X8_^-3VFNht;z$OgX9Wu ze;!O=O55;;*LL#4>lq#~#Ka!fEMI)c+cZ0ZHK4@jFu}GN6y4DwaE;=J*-sSX3!vaY zjw<34R>74`v+4cEiDQ#%a>+;B%73?m2^_+v%ENBye>xa60OWk4XD6t#q*$Il89);# z_u16kJ*}%^Fu2^=0YK@h{I;E?>@6&oi&pKbNShfTg yN3bz3NZ_Ab_%aoqx;y zcJ88j%O$Ks4y(7Zdoqc<|Bac{^Ev@Ek@QuedpYZyFO?tuS4Ai3{RNexN=i#S gBI>j7TPu?H z9(wnT(*Hxkhc@!}DpQ)v&!G9L1FJ(1tLK_ls^5AP$RJ|iBqO{EyHr_qRcX9cc#J(* z@3nVopvQ@92TeoUbmTKst(j0^q;Ka%6DFxkQzc?j%`2DgyF$wsbQcFSl6p>Pxl-(V zLrU)8w mta3@sxxl0)trp?u%v&40fHPE+KXoR8}~!AR(r`$U^szR2KS z!O~de(l_SvBR)Iyvt6oOq^6beo*c`K^4koy`_slp*AN+_^jsIk+Vy4i2v`wIq1&q~ zt@N01^_Zx3A#Q0 oIc88uO&X!nDIDn$gDL9RhFCE~A&&ugC2!MgXYAF%Wp0=w#$Fn| z#)n 7`rd-PF|Q*k>meXNTfl}6y KFT+W&+9KEDdA9cRyN%sPB@?5uaymS+(h|^ z%Ru>m+WkE1(*JaTi9i6!Ur$C^qQa1n(>o*>;M`|}`&L<8C{53+Ysy)*_jJyn`OWaA zPw`h1v|003NJlH1!^(nCl?9r^YGQ`ryqcKPIQ8;;xBHiR{-!U~Gw)u999A{!F|Oa& zfxqEsYCTbOxPYe#o}QT*I^;+ntpJplf6vbTLpY}|%qRs4E1PDr`(uB#UMftp=I5lD z%`&*^G7vdY`N8i67x;l=@cyHcE?p-%LlRds#U--bvf#e^XAQr1R(tQVM;lg~)uV@1 zWzRwN10B{_(TVL^WtYNX0icuGRB5-G-7DBRgF(I+hGgZi^q<0INX6JTMP`fit#M2w zNObBYey9BX8h-sn7*EQ;Ph!<7=5UXG>KgKg62AMEj~3|V{dB{`FSn0^)_LLsLVx2o zUX;IIBaYPD9g0yG`zKT!RucdS=M$3vzynvLDiI?I1a>|fdLMHawku?dgo?Qf+mxS} zyD&K)P=|*)AFxWUU(cLW=)6f a%`_X_n1SlNu9Eg*@>O5?3;sN!c_=1_I^ z?55!<*3Pu%ZAE}+*dzdsRmM-Qb-}C 9>% zQx-%)N5zIAsRJkDlF2koe0mi+BC?EV;OT>F`GIe@T(&&z>0i#G-dP6Ih*Wh|dfBd| zC1>HP(zvHI%~W4$smk+<0GjHN$*M=dO5w2hS=ZlB5J0%yp}$ZWcq@LUAlK(v5;5qM z44*AKe=sd)aEHsQ@^L4Nx4XPD53r`3R!jy90x&FK*1fd_xi}9`T`|c8Q%o^B_7>!G zUtj2tl&@?+r8|7$n8-8zaky2!A>xs7(m>0n@~bvTPMbP;8A;Hm7QkRsO7fNU;-k7q zDgWig@_(D$It#7j(qd&=9v*`e_J&Xva99X&Smi~9?rC{nH=D>+RTjFXPLsR$`A~nQ zIN86VO0mOlmy0_0)5fOC0=MJ_pUK2_1`dk=E)^6{%Jt`P9bg<*KQu}3x6>ibVkdlw zlWovpb^)2n<3IAXN=D?9#6#JsgE2~r{0D&%z9}|7WYXrg^ncXjGLoiMr&i0TJpLp9 z>ga>V(m{vC?*U7O$CPQG!9JF1F|#zfHYugw?%T7(KMD;UR==_o4vS7~)8ADq4AgBV zB=c5?OBv{-b^+pyWkr!v_bP03YFiqIC6te%byxrh!@`=K3GGUYU`yLoNJxhb^?UfO z-}4(U`mf?T@*uXV`cXF -TiXk?Jo^SN{*cx+9{)az4{W5@3i6Q}2K7!uGg(1)%A3ng^_+vGTpDNP11~^KC{C zmCQp$+@&6Qs~R{g4t3U()gxe~TLM(Y%+4Q7%M+xNtlYh)T_7now6#|?DzJFFix_wT z!0?oa(jsz%B^jk7C-`k|$NV0C+c|-dO1OuKNWzj@-iHsJDvlgS?>~;32Xko`zwv_n z{d)fM4s^ZQgD>xgjfPMo&bkb5$Nbzx9UzP2CU~#zP;I%q$R3;v%pQV0r3cs{Ze?*1 z(Aq15SwJKbT;!rBCaYHH$p}x0R+sP;w|WGul)VMh%->H?ztlRcXniZJo2)#75H>yS zlFv_t`iodQ1pq*iBBIG_*zRIwd*G+6%mdKMfR%Y>!AvwPc^ A_rh(20pAd(gQdu)qKC{%WX=SM$5@f42ErZ{9i4HwZwZ#c%0$|C~(@*_4bwix= z3Exu$kX D>_1CR- zs(S^1Mz%Q;!h;C!S13-o&~M8ZR+!|Pq^vzZ^CmPe5<|#v%t+$cvz#;9=%B-^UELuc z&7IL!DeaOkJRU{L@F^0Ok@7XQmmyzV;TXWmef9fv#1H_o0 zZCKAo(dd9vldFqh7H5Qx^0v-R$U}}@Zk1P7w~u&|4*7^P`s>xvO0V#s!=2Gz=RZHq zZ@q#ZT*gf9fep9bwt3Fzf6ym7Vu)%q+1sF6uvl5_!o-G^#V!L)F(8Ym7GQwMZs$VV zLuAUffnla9^Id=x09ED}VZbP@UAxt-i&c++)i)d#0Lb-ul0yA{^15K3I9?m A`yxY5R#c-P7{KMClqU0Ev35EEd&R74ne!Vhul~+x{LL osP+)k9*{)n=xbEgau6~b`=T_a__X`TOsz2WhOpg! zH%{N5?OpM(WCgE1iD#GKeaGVX3vkd!_U-9TXw#CQ%p8{9az=4j%tTt+E&xa}JQPzT z47A&^y#@80DrZogs7gqv<38RS_X>bBzO9)72{H0-{7!!3C6h8p@U`ZV62!s3Y5Mhu zF%A3xz5ke+mPhYDh6WzWZ@rRS5h6S`c^N6`-u%W(KI`9f_TSon%3avTrr}Q)v>8n` zYOHE?uK?sOYSViWUS!H}Gny_A=JDG^6u42$s;vt8WDE~|uj<`!uikkD&o9LrFXE)n z;_xvL`y5a9Jz&WSUU-O?e-D4!j+R4k`2Wz5OK|EJk%9Q=wb@%pr0yGNkGG$foov@@ zx7t~ !7G=s9h!DvO2qDXU#xPyo<$QM> BTDaiGPW6DsBfuDAhIjr8* z=Znw3t-vcxZLAtGi#K=5M<#oFq&V@@^ud#Krw>?x*?Op2&9B7XyTk1mlX|;`s1~08 z{A|#E<+t9(rKM?kAGo`x1^iK@?Qe(G)tUE84kz+fNa3)`3*2&A4q!pIZL}&cD3pHL zp$ZDx2$g2#``pqm%z0DB+q@Nkeam6(`V+R?#ar*eYftg+KWnMVt~I>i265pJaKicV zVz7N^`x3Cc5}tlQF1V5ZywggUmHhf2@y0`Vc_VH39~$*F8uc{{{_s9r#sOu-+(g35 z(SY~)ef-xKfCw_gGwYJ7iYF9GuqN95*Jvtrk5=i%uD@Dy$jH>aS%!U$$_BkJ_V;?Q z{eMBb{>Yo| pJfW<_-D%7J2XW;-Xt|%BTGYAboE846rKQynT_p??(R1 zb4h=ec=Ip(=HvL?7FzQw8ut%k%->@02SI(j(;S`HE|T&DLt{>A7p9SEPCqtG =#^jjCnHDs(wGeDKx z;Q|nGCTp7uXvUqlxd@o7p6`I%>PjFtybXZd>=KAZ>H>k$8d7^+4=S}Q4<%CkI3yY% zU$I5OO03|^UtGCMKKrORdO}v(LRRFnkD$^OFh{mqm9mWR4X5yB1b^i{Z&W>Z-+ZEK zrrT@Wez|;~3+ARvs{K%v=eYoyE@=Z$o>u^%#_t=bV!ZQ5x$eJt%^mVD&tcD7w(b~6 zxUc-`^E~G|=Zh5@`Ne$^8}1vx+WoqB@Au@NUd~2yFv@RwZ*Rma&xj58<|fU>phNp= zA-2XUmBae2i?yn55!nS8_-U8dbwCoB$jS%gr;AuKnN)q0jCq2qCPRHC?OIpcT&&b} zyBLs<=fOIuLO!ncD+HxvJIgI@<*_^@Nl9Sv!yW!p@ya84V#gU76wE@o6x)_GXa y}@s4VY=d}WJ#?ooIigovgj>Qf~w1EFPbO6bU_l 6dxL9tf82b4$PI(ES#&l;@d#P(nX6{B{4_ BedYKQ}!uimuSSKcVLeH z9x@SA#Nmr>>(b-=AXAJfQgN8s;*#PTBd+2YFi67j^p}u95?bskBmfWW1@X1h1h1Sh z1p@sUtHLnxLCo4A(|RSFZX&$(2XE~xI`|ek gHTrQinA42IPQgoC+3SK=&h$kFK;W|6N0i&X0j$9vaK8BO{69Cz zzx>XWNs3*n|50(8Xz=@;p`UO*)kY^wMDw6NTu3}mFu_vx6Qonxpz7sC@&Qk2)z^~2 zy#j!tt7Z~VTpvM8T@v`3gHP7mNYq2;$}!29j7O`mI1D^KU4 z{P!ZXO?*>IyViQ@X<`MaCw(f{FC4g8JXC8^Kjkh7yym7$uJtd!$?ET8YdI+Qw?E~H ze_wgfmD?ZYU2i~^Q@_aS6%l>#DCdj+j=_BvCx!0DKDkb(NsRqsj&kMyx=H@=`P9!~ zu89u%fH>u>-1*mvA?KoLU|+FUS+rMpx(M`;oJ?6#mv ^odBA?*w^6IJcx#Tyy=9Aa_3{-vZ>zFEBT$j zqEb#9P9Hul_jf-OV=mf9q0&BkN| cxv39dej+^2FSESBMek zqh)X(o;jWys2U;sdb-H#Ox<@NXlFsFr;AuSIq>#ZtDHdwgI8(&OG52EW!qGZc8j;t zTvg8x1mzZ2KHA}LEme6qpJPYoJU*=R9OGyOKz!g>{{6-T$+-1@VNWU; -X|ypN6)$4sCP97DMQT nH-af}HA~X*YBR(TeIY(?><~?+qd}fV&^UshKAfz*Kjy$F5 z8$T4IKZ{(ylQjUyHH*;~Hi=wm*3I&zt@=AE96I#F&Pn5QAOE^D_CgFic<;}%-;I02 zA_PGg_ehaM&Zb%OtOwCyC#A$w*;W}_0LY -@zD{vF<%oyE}+~1dHh+G)wB7fwP3G?OUQDK{`z*kUeh%;EUUE) zb|zfnoOYhKVR7Y=|MH$$$FKhpQg1on95cM>n?DevE ;EcV1-oFpC2;-KjX0X1up|(})ik~h9abg o5F^@C^(!Hh zTU~(TK&H5MeP2hmMJH9qZYlr}qpU2*4;*U)R)_;KyGL83yM}})Rix6Vn+E1izucMd zbqZHRpq7K__%p@vXK?o}?0%g~@4#uML5I?SL*gneI{5J1$0vIO-si2o1Ldwja>ahc z$JLe*+(2~{0Zdj%s4T78dS#)@VV0`8l^3`$OqQJ+SgYBZDGv49x~k;1{Uo(?ZxKNA z7h3_8=M@1oU1Dv}tLB_r1km)QeOSPc4mvdV$!|DgKJBevDqnprH~zEYq_J@7Ao&@9 zlM_QGHV>KjZk#Kd_6koIRX(6TB;5AvVe#6@f!0CQw^FutGD{}Osm|2)qS9D_VQ+H7 z+gy_;NMOxjA?#$yt*-Z2d%Qo* &gie?CVV5Eb0lgx2rUQo9Kj7ZBzMMT-1Vln;VzxwK?GLI;KXH^ z)x~5cU}mO+17&~tff-GozAEv&s=k$q;Im*>X};T4$ZHrS*13U9X)Jr`KDX-^3X-__ zVk>`eUZMLJR*vD%JN)Y^{mkl5Z!?ox_MV@b&nlt&XH&EugVFWV;PdIX_bFxpp!Xl1 z`_$L>=_w-CsE1W=roZN%C8Ttk=>x}xvyS}@0kw=r?+B >6M%*YdT#w1hZ5irHHMIV4ba-#R zQC}%Pcq^df{7F$(tl#!!m?c^cc1C?syss~7nbG@?bVhx-^4d;xzoQgyiOaCNY^km9 zD(1#q>i5at?`%rndP`AS*+atSM2%It(4qNa{odK`6-*>(H3$gF+ p`3 zwH3PVJ2T|trUpE~k9DXph)zrX1bdrZ@8J$I$t#tIJIEu19}db*uJ>REF$5qiCyPFK zTr4S2liNv!vf?h75FZ^ugFevb?*k3~fH-ob{QagR7_Gj2PM}Ao4@4RF@*=m?nfF1k z)Y;)r-(AcTlg=wFb|{UAjZJ00{yKwAZXl(rL(BKMUBA$urCODup?U6gyMGq!b?CR7 zzL NY<+C7>eqv3gQ2YRo*-JPiZq+DfhLn)f&+yeO1TjcLRNX&M zfCLjfKmroNW0Elj!;n9*Z?|yHzS^dK6gxy5kXghaeJqFn1Bo1cWOyG}(a;Mc`p9si zT#w68{XWO6*&9agoNMCS@c5IkdA#XgGE1;DXOjev;`fj}VvqFaL2?iK8Zwat)w=g# z(mX5$O93X+rp!eWSxPCTtb0o90QIowlr~`+p+5{U`(;J?Pu2SpBE8sg;8lem9Y%O7 zmE)(&Fen|Ca;el~u@UL>k8BlS+og(gRlZjxMn~sy#(cT**fgsnUMEDmfI x}+#e9i=}%5tL!l)I?aKTT7(7;%refe~xE zMDwA0S=7b%jQj=z&Dq?g<}bCWTas~n|Ft>=tY?Y e^d@Sc|EyGV-r!j)l)0Gcjt z1yH)rg=X19mZqf&jhX8Lq=3oROl67YN^^@az+|`U9;%epAz<-uiH}0G6q&xnGxE z+nSD`6iIlWL9T!}RlJ_YTVY#4 R=m}fkD2=MX~h_xa{18?RUL&abtrEx%7?1u z3<8iFU2kb#ec#2H*mv)9|6ID`a=j`TUnP;yj##C;GUrA_pWj8E=-Cs6CPt819sG&> zm&{!0%CroCKq?VR&C+z?cazt<(Csc-HO`CrzE!JfW$go~+tE5!#janPI$2liK`YcJV0la)Ao`4O zsWI2DFV3jBvf5J=x!MNL;`oW=G;^h7_8_wZFqImESnQ?3G)B2A7)fLod;r24&&Wot z_Mf7zlin4Th *aKoLY*Sf-pHq8l#LqZzaDyq6y0K}L!N0q}ElQf1z z)87?JcNi6TNG=fM$LK%$N5zTXpy-`CHW?rZ6cA)Vxxj>?5x&zDEF<*y$YQlM|J^Us zcD>JiUhhSi$YI4TnYRjuhbj`Z8!#cEuIYY>*>li$sqD5t&zH2suZm_$y^M2M{!6?6 zz;UktP?pDDb!m3aV9m$cdaDfma;r-Qr0kkja!UbWCM#}ovkR`uSOIdQizK2waqZnd z{~Z6x-Wf#m?cp34+Z9TV*6c%4GM2gOSt3^3GQ@{YXr8< `v5aED^L(=dxSy7Iwx6$y&bJ&NP0`EP&G z{qxV;6CesL;UZPKGwK|L>wYFyE2VJtcs&o2J+jngARJNZt!V!C=RR8TpI-9TEC5g{ z8qO#Mvr6;b?m}KY{oEVODp}eq;$*I>R!ifpQr9ro-ScCg!wSyZeUH26$9g^Whgk=x z4r{;JJ|M#wmar(x5 ~A N@xqb(zAC zA1TSDN|M;?#AkoA3r-kJO)a@kS`O?Vew+K$KN{f`qiRUl`~|JE+sly9S2NhMhP@04 zo4(YluCf;GRj8rN_p0j8y~2S8ST=8!1*&+53r??htj26FAO=zZcv}HU3al#89|K}! z)>=z)lMB`$WmD{Oqst6uOd9}iLjiy@wjF@C`8H?d6?#yvxuxU2?jL^!fToJrQaiC< zh&Y@s!+v5P1+Est$P~RQ1`$g;Er>#Y^$%qI_sBB*p=JVV&d;+e@XD=^`Rz2QDlc%E zbtZGIP*u9Gfb zCQiROvp~^eYa_)IJ0D{45WE3C$@vfTg)^*H1cpVCwow z2b 37xza_Zk2uwZSS3;Q~Vdk>PX# z^2OqOY$K3RX=qGfuq5Va?8FScH)6tGbwsh8L1K_(k|2 UtWadk3LU$ImxlTrqGj!Bnc z-JwuBb &5y4l zn|IM^ZD0^s>8(W4qd-&QifB}54a%z`CK?m&AJe87CNWkI06^nfVf+>P;1GL!?t;nu z#w*^sIVs=f>R<8Xzxw_{T%VPcs~s7cx(sLhS8^9vMPH>SjvOM69HO;OJ!-7DH>DJO zS}MX%HGi>9-A_|Wfsn263xMV?YmJx+16A91g@`D^VI^`>Ra~d$FPpr=FV~?ytNsDY z+vt*jinAOmTTt$B0f C*oXrQP19h3U%y0*yj|E!BSG6Xf=BB0cErvBrQi>hn#P{)f%)R37 _eux&1{H2WB))Z`0t2Gi?X@l!58k~dp~k@s-k|Jr&F1>BRjyn`4HGXS}~!~Z~g z4|WKth2C i*_+2(Vl-dl!|Nf@^!GnS(*SSn-QoTa!UTa?+@h`& zC-o;D9_x&IWdvvZrA=S`4_nl}=ccpe_LaQ*Er7ax?N!x0L7hvOf=8wM3SGa@b+STv zfAie*d_c`r0p! DV$?Yrq $L3(Ay+ zAWLcmSL|`2`qv5`6IEBqhZtNWYWn&woKc^T40OhSu6fcg;fomhg2QUQxLqM^H198k z!}3)Nb~(^m-793^r!w mrbI9Q?Fz;Ipm9*<6gDt^j^No=N(?TM45 z$wVX9!sZbsjcK#y37YBflx)tZ&xvm@>;BQ_QRxofsF0)~d@7Fh+J8>*TGE^uAL4S& zEjKQr51)vhtV$#pfK-izI4kp9HXxNI2TiM_(LL7%(2`jpl?Fp;P?g4Q9Uw`;^$%Fd zw!Mf|9hyB#ak6yIpn0Sn@Kyk?>`>2^Gj41X%=whdj~M-q#i_~7uFqjbYP46D=S9rP zk{b(^WgX-(!d$!DQ1Bke0|XgDD{o!FTbu{rfSvKdn!U9J@2TH3U$MgPp%0%p;6Juj zmi(l$tYED>?RwIB(^3Z0a6Z(ZJHI{m>B+V_)VA`1LYM3FR%tndh3;SGeNgH8WnL*J z()6FDd4)Z{$oqN(&0EP-{b%W3ch66Kjw#@ ve){l ={zKM3$!I@)=!BM=AIN>~#x%4#(9Ke|9hi2YdRRjzkqT!{ zO^x{@HQIIXWkN#7YG*gaTLGXE;e5l9ZE^QXy>u893kJpL_8b$K#n|vXW7|}uD8_}x z`(BmpUIB0>n3uwdF7=30T5ee;cRX5}c{R#i;nI7N3dRwyDUG0t{*}Q-kV(k3G+#Ac zocb~JWWHBbWy|>HFSG+7CKVDY7r9D$O|#i9FyjVXzKXp$g~O`Vy+XDez?QGHs~a{T z>_+)sReifxs8_(U>qGIAMPkO-HURQTml=+Vp(IChP+}I+VGXl*B98OS03f#((9;H} zWMvD=%>@|JJ(76?z{#A Hig{^zo*L3bS;3uhmrv^VbMfHuqGk#>%z x>w*HYVzsvn+x(`qm8mrY?E7AZt1Wd%&WLP%3(d&K^_CJoh)yC!FwPNMvjv? zEd5gRSs{nZTjP2Q@)&ej)72~JgC_?IB@(Bc-SVB~{O1?FRsUOg;Fo^CrD;e=Z#EJ$ z)31aM*5KR)dFPD3p~H@?>HcTouxhnhIjmZ(`yO}qkJ^0-VxQ*vb gE&KZ6l9exseY5Qb*L=gFUJ*m>y`}REkUW$M&U$JkfC<2Sb zeDb=mIs`23iLfrZqKg8IX$9agGnry63^<7731*sKdsP(44YcZBW%}6+c&m|7-YRE3 z7F6IB;jo-B?O-BA({Y?}?aGUZ2pTL7OXU~>fS3?I??khn)7t=et6e(eC^7Wn_{!<< zlX8cjlsoSl0Q}eA^J~w@S9bC%Ps`Vy#b1BVyZ(wwH*zgB_)z-5QFQo;^pPRrBd5^O zBj}($*gLxEqBixe_LX}DKW1b!tGev5+`zrGmM^y{8bO6=I)8BAglsLBwgaGgbgxjC zfCYqpyX;y~G%nzwoH5BC7rDa)Lkw*LAh#EYlWJO}uvDQ4@hKO87|{m6d#V7y8PTgR zSjsP@W2+g~7a8n~%l-vO(<1~ !ng6!8xnNgp^;4E=a-E-lH*3tS|rt4i}-K-aXg zW6kn?E= -Tis?Sgs0rM>x_ zbj>LOJmAtc03@=OpY3;6hkzxYbiHL-3nRwV$WLu|<*K}&2_skLRS|`>tmf2to^riM zHBZ4{?-9+Zi4h^L%Fh|J8Fw}uoHFmh4(<4)A?$j$a(|FxSXq)+5v#EIDZ^o@iZpVe zuJI%$TBx_$_2y63>pVT>JK6m ?yg&P3PA*|eEk%F-M8iytxzjwbl_7z+nIWyw-O`TRNjmV3DrkPjEHhok%m6w=(f;f z!K&^R06!88Lc$~tOA+F>+hw~~0L1BSj#}sG>DsijCN(o;h3SYP0KCU$qU9iG;@<$^ z*Pg+^L+FqX>!H$%YrA?h->Zs*giRN Dke9$KtkQ9%9F0h zMF9nXuy^s==7JM41m#v2hG@5SST @eD}!$9sbA&60vNgT+X2XBE;Bh7L}xB7EC6W!{N57haj2?p zX?3qk_qjglo@O>w**Sw*_5o~3;jISr5%sRvE4|Kogeqlq2v}(xmbbz6mbL@Pfg{?b zt_Si?3k@Ja1YR)(BoP*}%dM__yd8juX|FtMpF!Pb5-ByX(s-*{&D&5YKaeMn5JoF+ zz3X#WZ1h&(kUBIp&+0;DK^{PkGqLP)Ws&DzN0KC1oSTep 9_w3m&vn%prG%}ic2(D$!Y;*Ig`Fwg zF9{$UeHvz49RgOmC5QCHBqVW}*<*5ukOREfLzX(E1A29V%
RFhqqF@3T(v#Aw zrCI`4@8uqcnuS&$&cPVdrao3)HLUNg4go8T!*YyQ70tgJ-_`;!L5ykzK#IB?c5P@( zWN*Q${Z({WX}ndfrmux55)A5>VGb*jKNuQ+dV8eN!@WJLjTuBF&Poj)n?BVJwg1KW ztpFU4g+%k`!aR}js+z-F-S{3;#C7uXY(fb)Ur1fErmNUb5(KP-K?b*2{iaRetaaI2 zlJ*45Si8#isv=yKdD(EUs#zTZRu5)=X5~7U02<#0Bd63hJb)^>tpL_w6o~}=o-|FC zx48ho$-K{?ZSu(i2pRUBPcR$8B9&xdG6%giE&yj@^o!xGasfDJv;nBBDgelx5nn^k z&QjB-EMDoemO5df%Q27wP+I81^hY8=B*lF;RM{G $Q`OR<;fchR;%@SG+&MVP!$9fWra+`0WT< z?fQW=<_CF|IyK l?4EM;liMOhUWgxV-CowraFY;V~sQV*Jw z@&v2OkQsoa9)-$!zvf!k6kbDZh569aVLe(hNyJqbvM+cgiZ!w=pQG*fCbh}+OUBh9 zV2LqpiP|M+Y`fknd2FC>qVbc`=AmJ&ISEOeoHNKtxrA!|)7k(urQ``}ut?{yoRsxP zu7#MGv!>%X6WWOpNZYlY^;^?#*!M>_C`bxOD-9yjB_iF79;I}NNK1n#t#pU<2BSug zMmhvWkC2iWDBV1t@9{kU!E@jIGL9XvPo2B2Ghc7ijU)%h#aD6C^h6zmr+viMcn+%S z!((TlMear5@_xU4WJCqiA;H8Ap2!_Zm*kJ7nLdo<;h_yLfzDHW%5b2NunpYQEKp*o zvC!O2pW$h&d}OE-34k51_~(dm=~@j@kK4GNTy0@n%e|j;7h;bIz}p1ZI0BkStX5b9 zN5g4n>;@*5AXc#kCQ#1IuJaS5E1pnxMO#Xx-4}Uqf_jp6ixA6Z(Z0~L*;bH&hivQX zJaZ#?W5?p_*+&obAeY*Gu$ab~_3RAF&?>$Qmd$Kir6yi;>|gVhprK(%wH GMJ$rI^PC=O!cggG^k2+F~0vK9zK}B8yJdphDl|juBp{~BdriU?J%Br|LiA10%X$$Hbr{NQNC!S?%`pe z;vQUm-NZxnI1|6$k9T|D8_cMijlyT?=iu4@R7(xwcn}d90my$f(&*1raAwPiclv?> zf|9WXm_y|RSpkUEc7_NxR75ruJ(4<5iaVRQ(Zzsgl!L&qOWsQ2>dc-hOV96bU~d2E zl3AC1e@y%(l(t)kbGErxgQ`%%=L1ARGWqe3B$vQlWjB6~t(782`2LN8TKROY1USp- z&!|u&8F-lrDC=d^1b7+l8p+fnBjCZ~f0Nvu;ih_O|HuaN2i58h`~+g 1TWe1y{k5 zHi_fbo0I7m)RB`&zXVn~4%NGHuO+3z-u-{MHp?qR^Bbht)) 7~wA zG(_$Nxq*0(-)yCt4-F;UXVhcmQh^`~4f*e7a&c(+GIR#sC(P?s9I%9zd)d+us3Q_| z52Pbf-NEWO<#KOCO(dH~!cUArxU8TeX-g7|pAB(PkJ+^oFn$1o9aF+U7*!hjZ7|C7 zLaM}&hneU73#;%c=jc Mu@#a|>6DFwL)h%$3Nj{)3VEp3*eCyj M;#Sou_v| z-S?wBG X~Cfo zp^o*AX)-;GH#8^rG2K4(93Z_suDZ^^ix>+v4F0f4j>GNjq?LM0b`3{KSSj%p;)f_e z>*MTtdtY{N@CIl!Ge4R{4l_>g&Fh4u6hM-`I*hSMP*nsPv+GcwEbfDc4x1YbL*!TF z+c%We&Y(<*LW>&?hw_c U}j>c}IRu0Y=LomEN?JUyFpj9-)jiw)0cZmb)!{3_SR z0?RP-jJVTH44yl-NP*kBMeCh$(}iV8Sdfxul)m-eYgGU8 6cD;DotSPm!a z{tK^BFQ40b9J@R?noCTG#B{!4`=DFujl1YyUPr#x>oE$ckz(a09_VdPTk=R}zKa7` zU$g4YnLthRoO%6n7w3x(_m3sc3~&%jIYr<~Re^ilt5F6cGuvakBN;9>)qMrE5Z3y- zK8=yyW^*k<%dgE(;;`3z@cSrD&Ydi`)=%rni*co^ISeykV=}PU fC=nbUedWb*Zl#4C!u0Yj1;Xsqo ?>PMnur<#-@A-2 zl~0qt7pwI!CU%=eu5v4Sg&tg+( %sqdkCG9OUO%8R!(8sHJub`O7@Rnu{4eKI8Eg&g-ymO#&5U_0vx}7WJF>_l z97%WK VAf&}(J6tHOe!di#w0h$GaK_T&>XXpE&>?N`+`@u5RiG@;4})qdvo!PWe` z`^~C6f~7=s(6)V7d)(noeNV7X^ {!2wpHp-$1SF-;+^bz`qe-$^0LVv`V)n4}}XHAgf zhq?g6JX5_5#8?tK@A4FWI6g>iQut>mQJln(v$L(+_xWF<(3o`}!l)UTi*s7la6ti# z1cJ93c)G304v8Xk-sK;5R3$XG5jD7>nPXzdB4QALDS&Ozstka0u-59cu(pU+LBqw- ze+_X29H&l;1RN2PZ(Z&1@fl2TA|+sau0? Y-hjRd=Ga&5 z$qTjuo~P2tR^9VB%f07`SrhmyV#1f~DRp42teBB}-AY2m6ju|D9}VQZasG{djVefQ z{2ftk!ef@Zbd-RGOS(Zh(*O`?uPd3Xm1zIDQM>2Ba={v kT8`r=aW_+9qQP@e1q!#BHOvS1>P)u=v&}c)=KbQ zc)<;1(QI`DD-pQl>Ve(EBwn{amU?sLz#rb~N`GP!@w{NoFGhw9{4_f)>rty4T6krv zn=8>`=Z+ZSoW6^!2 jbQGh95v5Pja<8k9R?jk&vgbiXMj2R>xSnL8E9NmHhY z5)}&f{y1*#V?-HsY9V47&_{}Yoo%YQ6x>k<*iPbQTkLXxK)VT()uFZWOc~#lx(dbX z>y*67`4}P+9V44B+*+~*af1Wmss?95VB?2tPI3uO>qIOn-1HjcUm4r zt z%r|Hl+KxG0v*92Bd}s?;`moW02Kf&!q!-4bKmdzmC*XMA?r (V5FXUDGveo5#o-1B><1bqlu@*AxDjxpe>e_Y?$DeARJ>u1A6xUMJqcR{m zrZ3 OU9F|%E(Y}slSS_P`%2-8{fw9rsX zC!Ff-T ZG= zgG0+{TdMDDfT88DTa_dyhM-A=k;1ZxqproX(vfby=88X~S>3&qBO~Y-$xM7nr^7!( zJxt%}Nc*?yUeXLvc$fW&N)N0emM_8_GJ;=);F1^#K4Nl!YS_?DlFr-ntSXG8GrQ zo%4;R2zkt6n8Li&v}X6)oRsEi)pB^laTpctZr1D@loi?-oJ=6kIQsrTHvkc!wT8JLfl}yIEboqW zo7s_uG~S8i4HK ES;jG9OPFv;R^{bXF%79Ju)WqlD+N}SIvF{rtpQ&wT_6`$uOypz9Lpm zRFu(k4f@xy-)4@U!`iTxSYKRcG}P!X@|>uLQhLHcUqradzjObNRSj8o6BTP9laK$B zk)vC{(npUjk$4UztYyN_Y)Db3OKOIm?)gp5WXJi$pq$}dnSu`N_(_81EH<}abcDBf zI1H#UA5{ie-e(5jX+j=OhQIEW0491f 2+Fp?N6miT3Hz7v+n6fsT{ZcVVX2z9B;VXhch4Nkb+s) zbq=z;`0+YjqODJjW@x^shB__;i34@B09$Wr!o}IzT!RMVzti8b!Dtf5&cDu>MF6`O za20UiiACBr_jHHK4V)Qi#S1Xhlmb$`YeVT*S;#89S9Y)^Yclwwa$$=TfeFJfg(wNV zfFGYlr~lIs^V?fGq8!~3(lIT%{Py6m?&n_<(g$iJQu#wHIK_B8VsRh5?^bylpLc|7 zx}uNTq!mKMD=95XyxgAQwo&WbsxIGi@L%_!tV9ap8GU%86YPd{fk7^%Z_79UDcKBI)MUZ6qblO~AuIv9Gay@bl~ddJ zo{WlEp_%dFL%Q!sq`7v-^Nu3v$6N+Ud3r>h7a#!5V$6h{YWcM*LYK7HFYinzocp)l zX7{B}`%_zn*E^A)>#9UxNWFn_JbJ@Q0TAg>97T6w36YcZ!=y2K(nE k+CJq(aRlv9@J=^|3+z#`3jP3C=98x|6cBPZrUS^KPb{T?s zWp=;+=lNni6ZEo{=iw*m|6DPNBOucFsbpbjYWfm*a`KPb63M{<2VT~6ypJo+M!eRX zVOuRDuucbHJ;mlO3>=pcc)*5+E&|RY2tTlxy7NFn;8C4LD_O(P(R>B3S)>pPr<^d^ z=UvL&-~WA^%xrVaOkGfJmPLMr)$xP;{L|N@*w6S0cb(E`J`!qQAIe6hq)N0I?U^4s zk;h l#O-m9NG3Y3(;v9CC4GQ093@DUDRNd(j` z9Kfc#4CFCv4v6iSl>ur!ut`dv^Am4yqq UBB~^mAR_I|y09^<;T6ur04wOL;FI&xw}X?3`FaOa#EXn$ej2_9{XD|sE4&Hi z{y~fckGD{5E~$ysebokFV_|)N^0x>atU-=W78-0#O#>dR&!b;m_l;e1PY_)zArrZY zgnYL_oC$SK wo`l#409WNbkiKq}o&h0idynj8hPJC%t;$nHIub3zM68 zYHQK?W?#hCQgM2YobkMlydLJvoS0zto$d0A;(^z^Q1j{TTCgFFRfQ$kuqAOI=ZC7> zJ%OY;w`>+5Tv2g-7q;@Y(M+Iz%ODbpMTY_(x{NPjJF%h_9|aJ*ME-m#Pd%aEV726p zHhJ(>BHV|zsr057X9>`Y0{n@G%W9$nzpGbgkwdA$vqmMmh-<;&^QZ-4*4pa*^4azw z{_*j;Y2oN!79OUbJ#B5f;OH@pO^KxDtY%!5-8&<8tycArTx8f$yibactT-(ecW`9SWAm^@sG0(TR? zFkt6~^Pot9iDK*X?Cz{aCjpqxjX!YKJMG8e _?sW-iRhEM@m@H|$k`dqaE zJGKX2tqo5{h{6$?)sC*@!zGs0>j&7cPX~km=@Uf?Ui%ODU=;`Fv`58GN4Qi@Di~)V z#rn~0ReZG4>JUf|e#>W~G+e9JV0x$b-A6z~{Y6e`Ge?O5@UQ`oYM1$n(a?NowWI!; z{cWbE%iJexV bw;Ge*=n=M2YWv zX$B&>{@*@LtyW*<&F_Qtw{~@hc%Trw99CnKO*Ym3#MCq+6BFD-DJZqPZSio?TjAzq zyl-@i(bZ=>_NDTr@?hJ_;m=4OxQF!|^oSviu6gR#%PCPUyy8fWR({IF)yXwvJ{;h5 z>8C8 a$1^y*8Ak!7F`;<%hMw95waAc7KrCD zE-47-89K}U^mTL@Fp}s@)0^*s*s&RMCfSTkV*N@t@`k EsB4?V z `p%qDrvXl z+}8T{jKX!fLa~ypQ>sxb^j!oKXI|c<#OI1p(uu_tij(jlvvnh;d4MGQ$0V+}a#l{e zeOFcVOF^`C%J2wB4sysGmg;2OL;m&bjaW+wOASFhSa?>g_@eyxtMUyI&USJ;+tjk% zLtwW!H)rAr5-f{)Z1^KT{`+?R#x0f;LIopgbKld0AS_R>EIiLP2X!#-gRo&GM6SDy z$o}lEFWEA`8;Vd#PR~;)GQ%hdVNI-q`-GIDKu32bSG_*pxG?OYI>i3>!V4oMQhmk@ zjv87rd^3# tmQ?l6wUZ5oU0q6FX(eba5-U>-xRjnVCFv=_OmoVcanim%qV48DcqBiP> z1e`Q 5;TVHnU@r4$R8kvqAUaHj 4&{a{9*@XpA~)IW914rS}foN>+pCh zLDJw?;Nwc~mC8#XyaoHN6TDgJ_dUK=XfIf+c6g7E`_&OHsY1LDA nu-1M1k3XcKiB%F%?oeW7Y)*_bIbF9@fj$deALGDi^D z3`s! GF`ZC?R0RCj>p38xh+ zDR@& u*$iWP(347#IATSs?Uhw`DnrSw@aTTEkmXX+We+EdJW2vS`oTd{>A%v6Y@x z@=q|?h4~TCviWV85=S8V$gly^UJ9BN&L;z(1% #50Po~xlnRCOK&l<+Rw#oTTLFA zw75zcqA4DEuh?PI#HM~1*=^lIrhPOinmT++2+Y%eS9WGc8 Il`H(M*5qxbLth^p`7KwQfFVN{~O+E87rhwtYX&C zAKAfuVUh!g=sD6PmQR@HPSr%uFmj>QQR6egzER0U#@WA?z1kTt4b-#KNe@vqAUHya zvc__dglzuky8wXuVE@+wHBIl+dz{o(g1CI*XM!cRKaXW)&Hcos9EfwqVF8iTrv5@K zzLkXWq)OFQ;%R_m)?VaHaSew= p9AJMp4+*K0}^ETbBB?F+5Dab?X9Ftod!@MlG<129eZp-4d zLgU=| =0VkUANwDH(Q`rKbIAH?=Y|TbagQp9$?PrOC|DS@pYiMALxfRHl~N?c z#1}dyjn(T=!w9XyI{@$%Fd-5A;F@huq-VnQR;6Fq*l@tHvZ>Td@#2dvDNkbfy%4;D zWU*{xuk!aTJlJwJP;Q_*vhjX4aGe07rA7EO8QOGx@Y}3W>&%m9<<2_FDnx|ZU@kE2 zdWJ%>lP|;d-IAC6+UOzl>g3(qse^m3l^g9_X0od+{~p*I8CamRsz!Jo2A_MLH{hRi z`^2j0`~y*wmfE3Gx#cWr!s1#-jY534Kaj$XXII}N^wLlNAp-^5!Ph+u%!Z?>$s%-* z-?mkhk?&q$8U4xJ!u;`8SQ=z^fC4XmACXXppVTY8cP7 zC*?n5v$Byv<&PXt^I={AxgcB%uzGqUgB8y=@}_4~&E!`f9@F7U*(VZ|&ZbHZ^x6 C_ zNn%?0 xD|uMvb;$UM#-2C7M;D6F3j~QF7FtCi29(lkCg0LD39+O+tnhaQ zYC`}-m*Wcu*Itek1Vmu+_{UGz2_yd?ms%jrDev*iF?AyheZN r2VoWQ<)Df+`D_l7KK6-RR{Q6IQF}Y5CZ{Gh$B^qPl?l5WQa^oP@LaBPp0Vf z7sJD)_4a>1$gL;VN1u2ZY*)YPH}wk}1Q_R?+tqXzd%^Wk{_U@S&k`glm5$pnx$92k zy!-Dk?a;qx=f@hj5fMj*ah31L)gc>M_l~HWBT*BtD9h79>r>{+?w{^$P1kbqo9N*A z7;b;xoDa!)F=^>7X>mEMiT(@&R$NsNSk8!QJoEqZ-rt{%iK@gyTh{NV>O`dKDCmzk z3ih9`trg`*%j >!#sIf-jq&&*(LJ}km8i e6EpZ;4-jP+hPZhUpqOE`+>1VT|+<|ubf zachs`{*I67u6I>4poN(?uzgUqzBDu_YhJ99u8S2s{g*gsjEuE1Txib!TUWsbvq*)_ z$7#3mY$>hi{Aaga!XO9ih0a}^V1tx6i&fr$$nqWLA^H~{q+N5oAOBSoF2B&RRAh25 z7DR4a3u`$(|Kn%OrLA)va!U%I7;bznk@B LAf-{xTd=Osufdm0J2tt}tktvwy#9jOyJUmy X z{?T!R +obrPc%eTK%F%z9cw3o7&A&t=u~%1^T>5!@5QtmH zSa~%|p8^!m>(|r~lc8+V=T06aD(FwpRksDHnjiGo2`{gq$MatArnQIaApygwLIG!D z*-aVw+Squ|%l+7ul70G& 69CX=EO;;?4_W`H1Vc7CTbA$}_ zkW}iXwb#|2T^4)Qp&6F>hd>JF%iA<^8vDcQ4> 3yv2L#0I=gX}}U$UhPt;Cb?%);rdj0-8r{j4(pU!jsDB z^Q0;NSeM`5R;@6L+rPaEOE@n!q!}|?$!p%%?--Y*BHXVx)u~MKO>0i`oZDtYu0~^v z LDDN7=l;)~(cd7CW8 +L6i72XH6@Nu{9MPoi5-Oe;+L$R zF~pu#-iYH%<#!!d(ghL6GryT95;HP}p9{Bz*+P`a;13?UqsIRdcMhDEq_$;HGEfY+ z%8snjLF4CmcTFViregva$ov(1#5NGGPwVDnq$x;NWoerZcde1aQAR37Rwg>T`!P3` z)6j82rq&;q`$%(ttdWMDTn1@&ud{`d%SdKP6oq7H`;EfIxs#O3e*y @mOPy6*<&&`ZfCaaUKlI`WO if&9V3sxVKi&;?z z9un9+J22N`Vdc=TZYiV*Ze;T=bB9Dq^99$VdKQOZOcbK8Fc$r%Z@?LUINK(-x9c?D zlVrsF2rO)Hr-r269s!AU9)G#V8pvvwb1!`EfMT(cwa&L)^74u-O$9j%HUY-XaqE~_ zIMK<|Ms9bp6PHD13sy<(3A>MV+uA0fcqN*Ki!0k(9?00w@yC-s+01)%3#2PnGiE1F zcEW|X * zu4$YpjW6x|t`*0t+$)o*33B1M59Mr@37JF(dH6z~)<;2alAeJx#lBl1$&LMMjnT7b zTx~CvBo4k^`wdkt$yG6i_zJO+`0ofeUQv6(v_^M}?#LQjN}2kLWcVVE00|Ry%lesL zi|8>=zC(cvisx#rBpWDA!Nce6S(Sz;f|!LK>?Gh9cDMN$q|N(shnvhLQH&>y*<0PR z+Nm(Ook<<>e*1NuW66@I6v>@Izzp=4+g{qgN)Rr}2{G}{5CFi-kF74fT(?%bHk|TY z vjQuD1ZPXl$YVPo7>4%UqEh?{?h%g!Jm#K@-33d>^ zHXCED`# maUD_iGsalLh3_&m zD4&ZY3FN@~4AMA6t@mbrv`m~#JOLw_KN}fy*(bg#X6Qad0@6B$UjZxm7KRGqLX5Br zTz@m%c)5h}Ff%~&W?lVagQ@7|1r=4?MGVV(LoVQ>r6u&)vVu1Su;BhpyEKezm_#^w zma$$iJrLuv0wjSY&1kEXPkDolfP4LSnuk{R=lC6bUeEU0uzfb(it>5_f#H2CM}B~L z=SX88-M-T &FdkWW|dy$z;KBTvvly4&Pb=Xw1eU5{pRokYUSpApvkDk(a?QYyJ-_nFE}`p zU;I{oRI%}ShGay_H3_b`hR!ogjsvQTSc2ES`fjxFtop>a^v0I=$;1Z5_uKr51mxs* z>uu7j_7n`l=!y4mtCxmJ^#MxJZKo>CvaqRsxEIzHgM3wQ3Ql+w=Z-2cY )N=08XOv#YnJ&%^M^n10HEWSyr?V1={3S1o~ zy-uhGxf$GyY O+DIB0&=^HLJ4y z@)}xF!4tYFmO}VeMO{`y5Xz;y+J8DYGE^QdKP^rN0CW3$Q=%gS<2`1jh*A7!_)8Rh zs|%)G;hCEtb$uR4&VMiZ(UqAPwx4-m-Qs7A{#{L%|Neb=qhWeuyR|Z-Az?6DXlkOT zcyqmSpWE8EQZcxmn%Mr_K5X!@1?zm6Qi%VWa=FO3Xz5k_DSPCU8uiHz#==ocp<>|> zGJES>Vv1q)n2GKzEc1xK2rE@Cp_DOa6B-X#pYA1>$r_(%o)6&&gcLxJ|0MF{0q>hu zj;Ln(K{kVEbA8DI%aDPNB?F~SRg2|gDR6Bp>5qZTU8iKO*cK@$a)IFmZ{8~+s<`f4 z)`fO38=6-W8*f;Ztk8r2^yb)};wsEb@BL=)UgeSdsfW)UG%{2nF+3JQ{N~imZL@>x zH0&IBg4cF6N7PC1Gz7j0LK%I8=9(pSm_-7xmE-C{!^|=nbMqzFJ|{E^-NSRZyJ`I} zJaqD7==^JyKjyD!hw93;YKlZFLZw_K({|a{tUO%oVhYJZD0Hty0F?34({OQ%vvU zcFhs6##*=d6zc(1xP2onD6G3o)VG{5+jLICn&w*rv#cSx#^{PHk#@wEPdz-r(UV25 z{>A#3d)9>h-I{E4Sn9dvsB6}=HdLAI1CDw?shyli`F-3Ev+p;6#WBxxX003uF@DC; z8(*dsCD_L@(wcs9_lVa Num7Dw tG{EzS%UYtSDr_c31(4^c7oRlp^cUc+pWz E7}SNV~xX4Y-iLkw;y!|Wueez5RK-MwRQVZ1~- zBz4N|I`)(J3R_4Gm$9t%#?LHjZV-D`Kj>K$R;)`qeF)pZN#$oZI{l)w9-{2Szgxn$ z78rYOf*`YTL&ZI!_@(sOuaiAl*9^pD?w8NL*;t87{&adboIbO&_L>5osM5vYJ5Giu zl`UP=fCUA@YV2iB$kKDhzA_gujaThoxIiv#B@m`pV~;{c2zs6*FX+ 7bZM=Ia&N{RvUrZq5->OzsKI{NGb7V>^o7K# zjQw)HopHofSWxZ-p4EjK@{i)85kteickAd(Txa`0F+m9^)|^T9$K)D!h;kLtY+h{? z6ML!wKl6vdfJnwNH})pc)a2@vC zjN-ko_H<3AGWv7*uQ5mG)w+{+HSHZB`NIk49_#uuO(D nuO6_z+f8%O1_=^=Aa~URQ5HrO7z?&jWcy zr1m$e+MAtMDwIo{fl>A%Q`~&LHavMX2@p8?$t-3`^ogr`utUp-ZT^w{XV^L|Q->lv zK5lr4ddYQ#Z+Op}H^((E7L#RL5%X5c)|wGvd