From 23d5f02d5e7329879b7a158932b424f4af0c45af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=B0=E5=B3=B0?= Date: Sat, 2 Jan 2021 13:58:01 +0800 Subject: [PATCH 001/303] update README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a97852e3..31238ba2 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,9 @@ ## 项目 - [微服务班车在线预约系统](/Interview/bus/README.md) 个人撸的项目是基于微服务架构的班车预约系统,采用**springboot+mybatis+dubbo+rocketmq+mysql+redis等**。当然,该项目也是前后端分离,前端采用比较流行的vue框架。 +## 设计模式 +- [面试必问设计模式](/Interview/mode/README.md) + ## 基础 - [Java面试基础一些常见问题-思维导图](https://www.processon.com/view/link/5e457c32e4b05d0fd4e94cad) - [Java面试基础知识](/Basics/Java面试基础知识.md) @@ -132,7 +135,6 @@ - [JVM整个流程图](https://www.processon.com/view/link/5e1182afe4b009af4a5cc54d) -## 设计模式 -- [面试必问设计模式](/Interview/mode/README.md) + From ac3072c73d8a22de417d668bedb431e3685b9757 Mon Sep 17 00:00:00 2001 From: Dreamcats Date: Sat, 2 Jan 2021 14:08:04 +0800 Subject: [PATCH 002/303] update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 31238ba2..cfa9d7c9 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,7 @@ ## 设计模式 - [面试必问设计模式](/Interview/mode/README.md) + ## 基础 - [Java面试基础一些常见问题-思维导图](https://www.processon.com/view/link/5e457c32e4b05d0fd4e94cad) - [Java面试基础知识](/Basics/Java面试基础知识.md) From 1e612f8c5603854c740379690844e16e31144cf8 Mon Sep 17 00:00:00 2001 From: Dreamcats Date: Sat, 2 Jan 2021 14:45:16 +0800 Subject: [PATCH 003/303] update mianjing --- .DS_Store | Bin 10244 -> 10244 bytes Interview/.DS_Store | Bin 14340 -> 12292 bytes Interview/{mianjing/sql => bishi}/sql.md | 0 Interview/mianjing/.DS_Store | Bin 18436 -> 22532 bytes Interview/mianjing/README.md | 28 + Interview/mianjing/ali/.DS_Store | Bin 6148 -> 0 bytes Interview/mianjing/jingdong/jingdong01.md | 190 ----- Interview/mianjing/jingdong/jingdong02.md | 299 -------- Interview/mianjing/jingdong/jingdong03.md | 196 ----- Interview/mianjing/jingdong/jingdong04.md | 717 ------------------ Interview/mianjing/jingdong/jingdong05.md | 227 ------ Interview/mianjing/meituan/meituan01.md | 127 ---- Interview/mianjing/meituan/meituan02.md | 213 ------ Interview/mianjing/meituan/meituan03.md | 219 ------ Interview/mianjing/meituan/meituan04.md | 568 -------------- Interview/mianjing/meituan/meituan05.md | 508 ------------- .../mianjing/{shein/my.md => myshein.md} | 0 .../mianjing/my\344\272\254\344\270\234.md" | 0 ...my\344\275\234\344\270\232\345\270\256.md" | 0 .../mianjing/my\345\255\227\350\212\202.md" | 0 .../mianjing/my\346\213\233\351\223\266.md" | 0 ...my\347\214\277\350\276\205\345\257\274.md" | 0 .../mianjing/my\347\224\250\345\217\213.md" | 0 .../mianjing/my\347\231\276\345\272\246.md" | 0 .../mianjing/my\347\275\221\346\230\223.md" | 0 .../mianjing/my\350\205\276\350\256\257.md" | 0 .../mianjing/my\350\264\235\345\243\263.md" | 0 ...56\351\242\230\346\261\207\346\200\273.md" | 0 ...56\351\242\230\346\261\207\346\200\273.md" | 0 ...56\351\242\230\346\261\207\346\200\273.md" | 0 ...56\351\242\230\346\261\207\346\200\273.md" | 0 ...56\351\242\230\346\261\207\346\200\273.md" | 0 ...56\351\242\230\346\261\207\346\200\273.md" | 0 ...56\351\242\230\346\261\207\346\200\273.md" | 0 ...56\351\242\230\346\261\207\346\200\273.md" | 0 ...56\351\242\230\346\261\207\346\200\273.md" | 0 ...56\351\242\230\346\261\207\346\200\273.md" | 0 ...56\351\242\230\346\261\207\346\200\273.md" | 0 README.md | 25 +- 39 files changed, 29 insertions(+), 3288 deletions(-) rename Interview/{mianjing/sql => bishi}/sql.md (100%) create mode 100644 Interview/mianjing/README.md delete mode 100644 Interview/mianjing/ali/.DS_Store delete mode 100644 Interview/mianjing/jingdong/jingdong01.md delete mode 100644 Interview/mianjing/jingdong/jingdong02.md delete mode 100644 Interview/mianjing/jingdong/jingdong03.md delete mode 100644 Interview/mianjing/jingdong/jingdong04.md delete mode 100644 Interview/mianjing/jingdong/jingdong05.md delete mode 100644 Interview/mianjing/meituan/meituan01.md delete mode 100644 Interview/mianjing/meituan/meituan02.md delete mode 100644 Interview/mianjing/meituan/meituan03.md delete mode 100644 Interview/mianjing/meituan/meituan04.md delete mode 100644 Interview/mianjing/meituan/meituan05.md rename Interview/mianjing/{shein/my.md => myshein.md} (100%) rename Interview/mianjing/jingdong/my.md => "Interview/mianjing/my\344\272\254\344\270\234.md" (100%) rename Interview/mianjing/zuoyebang/my.md => "Interview/mianjing/my\344\275\234\344\270\232\345\270\256.md" (100%) rename Interview/mianjing/zijie/my.md => "Interview/mianjing/my\345\255\227\350\212\202.md" (100%) rename Interview/mianjing/zhaoyin/my.md => "Interview/mianjing/my\346\213\233\351\223\266.md" (100%) rename Interview/mianjing/yuanfudao/my.md => "Interview/mianjing/my\347\214\277\350\276\205\345\257\274.md" (100%) rename Interview/mianjing/yongyou/my.md => "Interview/mianjing/my\347\224\250\345\217\213.md" (100%) rename Interview/mianjing/baidu/my.md => "Interview/mianjing/my\347\231\276\345\272\246.md" (100%) rename Interview/mianjing/wangyi/my.md => "Interview/mianjing/my\347\275\221\346\230\223.md" (100%) rename Interview/mianjing/tx/my.md => "Interview/mianjing/my\350\205\276\350\256\257.md" (100%) rename Interview/mianjing/beike/my.md => "Interview/mianjing/my\350\264\235\345\243\263.md" (100%) rename "Interview/mianjing/jingdong/\344\272\254\344\270\234\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" => "Interview/mianjing/\344\272\254\344\270\234\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" (100%) rename "Interview/mianjing/zijie/\345\255\227\350\212\202\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" => "Interview/mianjing/\345\255\227\350\212\202\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" (100%) rename "Interview/mianjing/zhaoyin/\346\213\233\351\223\266\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" => "Interview/mianjing/\346\213\233\351\223\266\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" (100%) rename "Interview/mianjing/pinxx/\346\213\274\345\244\232\345\244\232\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" => "Interview/mianjing/\346\213\274\345\244\232\345\244\232\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" (100%) rename "Interview/mianjing/yuanfudao/\347\214\277\350\276\205\345\257\274\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" => "Interview/mianjing/\347\214\277\350\276\205\345\257\274\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" (100%) rename "Interview/mianjing/baidu/\347\231\276\345\272\246\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" => "Interview/mianjing/\347\231\276\345\272\246\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" (100%) rename "Interview/mianjing/wangyi/\347\275\221\346\230\223\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" => "Interview/mianjing/\347\275\221\346\230\223\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" (100%) rename "Interview/mianjing/meituan/\347\276\216\345\233\242\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" => "Interview/mianjing/\347\276\216\345\233\242\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" (100%) rename "Interview/mianjing/tx/\350\205\276\350\256\257\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" => "Interview/mianjing/\350\205\276\350\256\257\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" (100%) rename "Interview/mianjing/yuanjing/\350\277\234\346\231\257\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" => "Interview/mianjing/\350\277\234\346\231\257\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" (100%) rename "Interview/mianjing/ali/\351\230\277\351\207\214\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" => "Interview/mianjing/\351\230\277\351\207\214\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" (100%) diff --git a/.DS_Store b/.DS_Store index 2023ea2acc66a7c539b2ffbb9da0f27c8cf0b037..97d45973f8010b8c6a0c9cea3c335fb26640d065 100644 GIT binary patch delta 78 zcmZn(XbIS$E5LYsvaL*mx>$9!u7Qq%g;}kRLbbUOkZEpVSzF7=A+Bm@>zR;SSyf$A hTQ`03bpd(C8I#{jh;8N-{KdDKRpAfIW@b@lW&pjw7?1z} delta 56 zcmZn(XbIS$D=^tgMud@bvcF6QKPP9rfJAk*iIK66g0Y3khe-UP80KDiBMF0Q* diff --git a/Interview/.DS_Store b/Interview/.DS_Store index b71c7eb5a33e7373d97499718958e31083ca0f0d..06ae7ad5195adc527a8273e7754aee242508f005 100644 GIT binary patch delta 405 zcmZoEXh~3DU|?W$DortDV9)?EIe-{M3-B;7FmW<4C~!|y&=-{kiZBBO85mL+QW=Uf zlgkQ{ayDLE%sAP=gk@s4@aC;*`xz(yQBdD_k(p&PI|mB~qu|5|Qk!`MUNSKrpS(w^ zL0zo6TGv2F!NROoN1@u>2*@oG}f7UU3P*{r~#%e~D%Y4c3oxB!+}tBk&pxq1M3NO~ zD-alP0|{4TCv6sFdCxqVUq_dd5h!&4h#4l^@JRD8LELWv7Rb<VUXl{vVMT<5)S{*c(ehAr*%m=spi8%ul(wXMaW*EqcJO+$ zNkdS60TNFjB>n*s54<4pjNl(2AtC+%5TTnMuOU8 z7zr2&jFJS#&=`*42^>?@AuEpGjrDrltpniS*4Nv1*I!uptx#ZN;}eqyrgHg%hYlZk zV!Ckj*zuz$j?T=?%$}Tia&GSF<-+lLt+Cef+@90CEvA zSS}n>=&oDM5B9QdY^{Ul#9oHD*Xy2TIk#J$-MqH4(zX1h<-&Bg;koU0#qHX@?K-z& zrMK>MSKa=iZ*>-(6&Fw3u&w^Jbxzj3X?b15iTX>oYMxhfT2^`MZo5{u+Si=2>-%np zQ}Vlkk&0*WJBcS%*J};iY0Y2!Gw9d(5}ykZ=1b29i2}_B3Cy7dcEhQ4tbBgHbX8{N z=oGz5SEx#>)TdABOZu9=qaWxO`knq_6Ks|}%TBSE*adcxy}=gQRaRl|u3DOa>N#!KY2ljFAHb2Ltzep_57Ffgwc<27Fn0jL!dr$~V8mYc z>u>hDz6ZuC5abqh!M26*e1>k4OKq^q$95Ojmr={dafaT-wMCxD?a+!SbEzR|9Z+%l z4(`^#J({3B17X!@L)5+^Q)QI7v?gd5(H{O>4>UI7zNl}5(h0?k9NqzgZGT&~eIQ?@ zi+2#prqC`|^6yfw-tS7Z(vKlpe`X<8^SS(y{M{;sZ`H5)y>7YY-L9_UE^_$f4k2xV zk${nakw9Dme10h6;BiUDyFo0b4xG3O!OOV{Ap+8{14Ld?#w8i=2C=A$I-)A#pemCl z1`DbqzsSiEmt?#f#6fjpL3LvC$xK=(OgtU+ih`V2DTvM4NWe%SDS>HZP0@X*pEYO{ z(5A>4Mw@W^m72d=7n+6Jzu~&=C|#@Hgt`oE{*vqXHQTW~p^Df|3nX{mvz?~fFLQgL z3#|k?N>Gr?Lv2|u%x-VLuyDF~{^gz1#qD!1B6;roD?2;++{x$8URb=>*}A{|@y;io z2YfL(HxOqk9=^bZ!ld0W29Dkaj?$r;Spmt`hjxHck{r5c(?>Xvvvm~$WDmWW2HW{8aqL$o|ZYb+Ps$F3BV<{`(Pc*9y$7e0YjHqn~G=uoJA zkTWqGs7-=3!tsAZEu4wc=lDp}&ZVOE-#8OfI#o0DrgIRYIHeS5PRUhJ9ZY!UY$RYL zU?iZU1ZL3aap)s$va_j=Nah}^4ideq^)(xk3P@bBis>LD^FdQ~q-J`@UaLc0L+0$^ zPSGhQg?(V-o8wU5^+w+l7h`ZJR2q9eT0bxf_B^*{#c0(GnXzFwV9!VV0HHCO6qNGy zGHaAsuUj397zBd`rHS=Gkp_@_kH+4eBjk09I#WZG%3-LWROU*Q$}0t>xoG80GgvQ^ zO&vGEra`Rkh)r~Pefr?o#sbD|7ijvm)eKDyigQ<&Ef zDt!N+*OL4Hm127U3PN)-5-<|To&?y$5@ zz5ASf0RU2N*KYun03eT^RG@G=?<<^IMq5Q6IpDJ=imAyDi;{ zwU!m5)&m*5A(qj1kcAeQQU6gLo%^HSvP{>ToBK;uNoH(3JCVyPFP%Go;pItn>f)uT z%Tv?S(^sxeUz?eEbwjtupb^vuW`})B~;4Xh`D?L7r4We><6cp-Zr2qF1%FQoO-XxRg@2L|;3iw_0NOqK%K<^Pf3`U%U(1N^!|AWMPla_Ot0j<1T6tCE0X5U!5(1=bv8DUe+*xjGQ84kWlT z2^b3Tua0^Jzd4W+m-18gfb4;I4@_cJ4k-c+r~-wy-f@w?GyVs^_4wcKq{sf0ymCQ# zR7FBm^)#>TmNjRceXD5RwjPw1DYe3~rmrAnLPmFo+MZ_GhT|)nbpx9`{KT~Dv{PoB z(e=+$H*VZo+-&Y29DaH9 z)wkR-5<~}JW<}Fu{34jimnoi;=Dsr1pXNpxVRD&nNY>+&H$rm*{#q;zpt&I#RxqwJ zn)?x~zz!V1H}D<&06)X8@F)BO|B)O~`_kM`x%URrT*0AHl8f33qPp!<;4PGOEYY+g zyN{_7-LZ9?grU2SQw4%wsSaEY_pinmM_6hE3ySM zhr+d7dJktC1Lq`Xvh*%ER6unWQEdW?)<#+p9WikzG|L%w_=IR6nnTQ=<_tUHOaaMJ zawW^j?E$u2HiqM@1!h_!QefUt(MXQ1VVoHljm~6j4IC*%qAci$UlSRAYFDlzCJu#S zIi`-(m>S}>50VW;@=ls(hdKl%N+c(p!?UmyI8#7!-sd>%<)b=(vXIufMa)o%>ZGGO z=28LMIcWN*_xRI=v?5zDb0}QP+4Bg^o&|>rsE)k}}Z-%N!EdN#7JII8=z#5e}HvIDLvN zI+>@Q;J003`~Tj5`u_i98Yk~s_JHhxXX*hmez$tJj7@vHXGATw*RFth6EjP!U6;#H z1q=RGJZ#Ih;@!i{+WG&9fO3<4X9>$)F0q8^%r^oa|Bb^a?rD+y|2h72M6SIKzxMh+ DNfIHq diff --git a/Interview/mianjing/README.md b/Interview/mianjing/README.md new file mode 100644 index 00000000..f278de2a --- /dev/null +++ b/Interview/mianjing/README.md @@ -0,0 +1,28 @@ +> 说明:秋招后期,有些面试我没有总结,大同小异,仅供参考,可能自己想偷懒了,还有一些有2面3面的,我也懒的总结了,工作量不小,不过大概差不多,面经就是需要复盘,查漏补缺,面试官问的大部分跟你的简历有关系,从简历问的又深又广,不仅仅局限于简历哦,提示:这里的面经是关于Java后端的。 + +## 牛客网面经 + +- [拼多多所有问题汇总](拼多多所有问题汇总.md) +- [京东所有问题汇总](京东所有问题汇总.md) +- [美团所有问题汇总](拼多多所有问题汇总.md) +- [阿里所有问题汇总](阿里所有问题汇总.md) +- [百度所有问题汇总](百度所有问题汇总.md) +- [字节所有问题汇总](字节所有问题汇总.md) +- [招银所有问题汇总](招银所有问题汇总.md) +- [网易所有问题汇总](网易所有问题汇总.md) +- [远景所有问题汇总](远景所有问题汇总.md) +- [猿辅导所有问题汇总](猿辅导所有问题汇总.md) + +## 自己 + +- [shein](myshein.md) +- [京东](my京东.md) +- [作业帮](my作业帮.md) +- [字节](my字节.md) +- [招银](my招银.md) +- [猿辅导](my猿辅导.md) +- [用友](my用友.md) +- [百度](my百度.md) +- [网易](my网易.md) +- [腾讯](my腾讯.md) +- [贝壳](my贝壳.md) \ No newline at end of file diff --git a/Interview/mianjing/ali/.DS_Store b/Interview/mianjing/ali/.DS_Store deleted file mode 100644 index 5008ddfcf53c02e82d7eee2e57c38e5672ef89f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0SynchronizedList->CopyOnWriteArrayList -- SynchronizedSet->CopyOnWriteArraySet -- SynchronizedMap->ConcurrentHashMap - -### 线程的创建方式 -- 继承Thread类创建线程 -```java -public class ThreadTest extends Thread{ - //重写run()方法,run()方法的方法体是线程执行体 - public void run(){ - for(int i=0;i<5;i++){ - //使用线程的getName()方法可以直接获取当前线程的名称 - System.out.println(this.getName() + " " + i); - } - } - - public static void main(String[] args){ - //输出Java程序运行时默认运行的主线程名称 - System.out.println(Thread.currentThread().getName()); - //创建第一个线程并开始执行 - new ThreadTest().start(); - //创建第二个线程并开始执行 - new ThreadTest().start(); - } -} -``` -- 实现Runnable接口创建线程 -```java -public class ThreadTest implements Runnable{ - private int i; - //重写run()方法,run()方法的方法体是线程执行体 - public void run(){ - //实现Runnable接口时,只能使用如下方法获取线程名 - System.out.println(Thread.currentThread().getName() + " " + i); - i++; - } - - public static void main(String[] args){ - ThreadTest tt = new ThreadTest(); - //创建第一个线程并开始执行 - //输出 新线程1 0 - new Thread(tt,"新线程1").start(); - //创建第二个线程并开始执行 - //输出 新线程2 1 - new Thread(tt,"新线程2").start(); - //使用Lambda表达式创建Runnable对象 - new Thread(()->{ - System.out.print("hello"); - System.out.println("'world"); - }).start(); - } -} -``` -- 使用Callable和Future创建线程 -```java -public class ThreadTest { - public static void main(String[] args){ - //使用FutureTask来包装Callable对象 - //使用Lambda表达式来创建Callable对象 - FutureTask task = new FutureTask<>(()->{ - System.out.println(Thread.currentThread().getName() + " " + "开始执行任务!"); - return 0; - }); - //实质还是以Callable对象来创建并启动线程 - //输出 新线程 开始执行任务! - new Thread(task,"新线程").start(); - try{ - //获取线程的返回值 - //输出 0 - System.out.print(task.get()); - }catch (Exception ex){ - ex.printStackTrace(); - } - - } -} -``` -- 使用线程池例如用Executor框架 -```java -public class ThreadTest { - public static void main(String[] args){ - ExecutorService executorService = Executors.newFixedThreadPool(1); - executorService.submit(()->{ - System.out.println(Thread.currentThread().getName()); - }); - } -} -``` - -### 堆排序的实现过程(唯一的一道算法题) -```java -public class HeapSort { - public static void heapSort(int[] arr){ - if (arr == null || arr.length < 2) return; - for (int i = 0; i < arr.length; i++) { - heapInsert(arr, i); // 依次从0~i形成大根堆 - } - int heapSize = arr.length; - swap(arr, 0, --heapSize); - while (heapSize > 0) { - heapify(arr, 0, heapSize); - swap(arr, 0, --heapSize); - } - } - - public static void heapInsert(int[] arr, int index) { - // 建立大根堆 - while (arr[index] > arr[(index - 1) / 2]) { - swap(arr, index, (index - 1) / 2); - index = (index - 1) / 2; - } - } - - public static void heapify(int[] arr, int index, int heapSize) { - // 调整成大根堆 - int left = index * 2 + 1; - while (left < heapSize) { - int largest = left + 1 < heapSize && arr[left + 1] > arr[left] - ? left + 1 - : left; - largest = arr[largest] > arr[index] ? largest : index; - if (largest == index) { - break; // 自己已经是最大了,直接跳出 - } - swap(arr, largest, index); - index = largest; - left = index * 2 + 1; - } - } - - public static void swap(int[] arr, int x, int y) { - int temp = arr[x]; - arr[x] = arr[y]; - arr[y] = temp; - } -} -``` -![HeapSort](http://media.dreamcat.ink/uPic/HeapSort.png) - - -### 垃圾回收算法 各自优缺点 -- 标记-清除 -> 该算法分为“标记”和“清除”阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。它是最基础的收集算法,后续的算法都是对其不足进行改进得到。这种垃圾收集算法会带来两个明显的问题: -1. 效率问题 -2. 空间问题(标记清除后会产生大量的不连续碎片) - -- 标记-整理 -> 根据老年代的特点提出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。 - -1. 慢 -2. 解决了碎片 - -- 复制 -> 为了解决效率问题,“复制”收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。 - -1. 快 - -- 分代收集 -> 比如在新生代中,每次收集都会有大量对象死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。 - -1. 根据特点选择不同场景下的收集算法,更加灵活。 - -### 数据库的三大范式 -#### 第一范式 -在任何一个关系数据库中,第一范式(1NF)是对关系模式的基本要求,不满足第一范式(1NF)的数据库就不是关系数据库。 -所谓第一范式(1NF)是指数据库表的每一列都是不可分割的基本数据项,同一列中不能有多个值,即实体中的某个属性不能有多个值或者不能有重复的属性。 -如果出现重复的属性,就可能需要定义一个新的实体,新的实体由重复的属性构成,新实体与原实体之间为一对多关系。在第一范式(1NF)中表的每一行只包含一个实例的信息。简而言之,第一范式就是无重复的列。 - -#### 第二范式 -第二范式(2NF)是在第一范式(1NF)的基础上建立起来的,即满足第二范式(2NF)必须先满足第一范式(1NF)。 -第二范式(2NF)要求数据库表中的每个实例或行必须可以被惟一地区分。 -为实现区分通常需要为表加上一个列,以存储各个实例的惟一标识。这个惟一属性列被称为主关键字或主键、主码。 -第二范式(2NF)要求实体的属性完全依赖于主关键字。 -所谓完全依赖是指不能存在仅依赖主关键字一部分的属性,如果存在,那么这个属性和主关键字的这一部分应该分离出来形成一个新的实体,新实体与原实体之间是一对多的关系。 -为实现区分通常需要为表加上一个列,以存储各个实例的惟一标识。简而言之,第二范式就是非主属性非部分依赖于主关键字。 - -### 第三范式 -满足第三范式(3NF)必须先满足第二范式(2NF)。 -简而言之,第三范式(3NF)要求一个数据库表中不包含已在其它表中已包含的非主关键字信息。 -例如,存在一个部门信息表,其中每个部门有部门编号(dept_id)、部门名称、部门简介等信息。 -那么在员工信息表中列出部门编号后就不能再将部门名称、部门简介等与部门有关的信息再加入员工信息表中。 -如果不存在部门信息表,则根据第三范式(3NF)也应该构建它,否则就会有大量的数据冗余。简而言之,第三范式就是属性不依赖于其它非主属性。(我的理解是消除冗余) - -### 对象存储为json文件,不借助第三方jar包,说说思路。 -递归迭代 -- [参考](https://blog.csdn.net/qq_35893120/article/details/84288551?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-4&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-4) diff --git a/Interview/mianjing/jingdong/jingdong02.md b/Interview/mianjing/jingdong/jingdong02.md deleted file mode 100644 index 27e6b438..00000000 --- a/Interview/mianjing/jingdong/jingdong02.md +++ /dev/null @@ -1,299 +0,0 @@ -## 京东java实习面经 -- [原文链接](https://www.nowcoder.com/discuss/419373) - -## 一面 -### redis为什么可以这么快,持久化机制 -Redis性能如此高的原因,我总结了如下几点: -- 纯内存操作 -- 单线程 -- 高效的数据结构 -- 合理的数据编码 -- 其他方面的优化 - -在 Redis 中,常用的 5 种数据结构和应用场景如下: -- **String**:缓存、计数器、分布式锁等。 -- **List**:链表、队列、微博关注人时间轴列表等。 -- **Hash**:用户信息、Hash 表等。 -- **Set**:去重、赞、踩、共同好友等。 -- **Zset**:访问量排行榜、点击量排行榜等。 - -[https://zhuanlan.zhihu.com/p/57089960](https://zhuanlan.zhihu.com/p/57089960) - -### redis持久化机制 -Redis支持持久化,而且支持两种不同的持久化操作 -- **快照**(snapshotting,RDB) -- **只追加文件**(append-only file,AOF) - -#### RDB -> Redis可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。Redis创建快照之后,可以对快照进行 备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis主从结构,主要用来提高Redis性 能),还可以将快照留在原地以便重启服务器的时候使用。 - -#### AOF -> 与快照持久化相比,AOF持久化的实时性更好,因此已成为主流的持久化方案。 -``` -appendfsync always #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度 -appendfsync everysec #每秒钟同步一次,显示地将多个写命令同步到硬盘 -appendfsync no #让操作系统决定何时进行同步 -``` - -#### Redis4.0 -> Redis 4.0 开始支持 RDB 和 AOF 的混合持久化 - -### spring中的ioc运行机制 -```java -public void refresh() throws BeansException, IllegalStateException { - // 来个锁,不然 refresh() 还没结束,你又来个启动或销毁容器的操作,那不就乱套了嘛 - synchronized (this.startupShutdownMonitor) { - - // 准备工作,记录下容器的启动时间、标记“已启动”状态、处理配置文件中的占位符 - prepareRefresh(); - - // 这步比较关键,这步完成后,配置文件就会解析成一个个 Bean 定义,注册到 BeanFactory 中, - // 当然,这里说的 Bean 还没有初始化,只是配置信息都提取出来了, - // 注册也只是将这些信息都保存到了注册中心(说到底核心是一个 beanName-> beanDefinition 的 map) - ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); - - // 设置 BeanFactory 的类加载器,添加几个 BeanPostProcessor,手动注册几个特殊的 bean - // 这块待会会展开说 - prepareBeanFactory(beanFactory); - - try { - // 【这里需要知道 BeanFactoryPostProcessor 这个知识点,Bean 如果实现了此接口, - // 那么在容器初始化以后,Spring 会负责调用里面的 postProcessBeanFactory 方法。】 - - // 这里是提供给子类的扩展点,到这里的时候,所有的 Bean 都加载、注册完成了,但是都还没有初始化 - // 具体的子类可以在这步的时候添加一些特殊的 BeanFactoryPostProcessor 的实现类或做点什么事 - postProcessBeanFactory(beanFactory); - // 调用 BeanFactoryPostProcessor 各个实现类的 postProcessBeanFactory(factory) 方法 - invokeBeanFactoryPostProcessors(beanFactory); - - // 注册 BeanPostProcessor 的实现类,注意看和 BeanFactoryPostProcessor 的区别 - // 此接口两个方法: postProcessBeforeInitialization 和 postProcessAfterInitialization - // 两个方法分别在 Bean 初始化之前和初始化之后得到执行。注意,到这里 Bean 还没初始化 - registerBeanPostProcessors(beanFactory); - - // 初始化当前 ApplicationContext 的 MessageSource,国际化这里就不展开说了,不然没完没了了 - initMessageSource(); - - // 初始化当前 ApplicationContext 的事件广播器,这里也不展开了 - initApplicationEventMulticaster(); - - // 从方法名就可以知道,典型的模板方法(钩子方法), - // 具体的子类可以在这里初始化一些特殊的 Bean(在初始化 singleton beans 之前) - onRefresh(); - - // 注册事件监听器,监听器需要实现 ApplicationListener 接口。这也不是我们的重点,过 - registerListeners(); - - // 重点,重点,重点 - // 初始化所有的 singleton beans - //(lazy-init 的除外) - finishBeanFactoryInitialization(beanFactory); - - // 最后,广播事件,ApplicationContext 初始化完成 - finishRefresh(); - } - - catch (BeansException ex) { - if (logger.isWarnEnabled()) { - logger.warn("Exception encountered during context initialization - " + - "cancelling refresh attempt: " + ex); - } - - // Destroy already created singletons to avoid dangling resources. - // 销毁已经初始化的 singleton 的 Beans,以免有些 bean 会一直占用资源 - destroyBeans(); - - // Reset 'active' flag. - cancelRefresh(ex); - - // 把异常往外抛 - throw ex; - } - - finally { - // Reset common introspection caches in Spring's core, since we - // might not ever need metadata for singleton beans anymore... - resetCommonCaches(); - } - } -} -``` -![spring-ioc](http://media.dreamcat.ink/uPic/spring-ioc.png) - -#### 大概总结 -1. Spring容器在启动的时候,先会保存所有注册进来的Bean的定义信息; - 1. xml注册bean - 2. 注解注册bean;@Service、@Component、@Bean,xxx - 3. Spring容器会在合适时机创建这些Bean - 1. 用到这个bean的创建;利用getBean创建bean;创建好以后保存在容器中。 - 2. 统一创建剩下所有的bean的时候;finishBeanFactoryInitialization(); - 4. 后置处理器;BeanPostProcessor - 1. 每一个bean创建完成,都会使用各种后置处理器进行处理,来增强bena的功能,比如: - - AutowiredAnnotationBeanPostProcessor:处理自动注入 - - AnnotationAwareAspectJAutoProxyCreator:来做AOP功能; - - xxx - 5. 事件驱动模型: - - ApplicationListener:事件监听; - - ApplicationEventMulticaster:事件派发; - -- [http://dreamcat.ink/2020/01/31/spring-springaop-yuan-ma-fen-xi/](http://dreamcat.ink/2020/01/31/spring-springaop-yuan-ma-fen-xi/) -- [https://javadoop.com/post/spring-ioc](https://javadoop.com/post/spring-ioc) - -### spring中aop的原理 -> AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。 -> Spring AOP就是基于动态代理的 - -大致流程: -1. @EnableAspectJAutoProxy 开启AOP功能 -2. @EnableAspectJAutoProxy 会给容器中注册一个组件 AnnotationAwareAspectJAutoProxyCreator -3. AnnotationAwareAspectJAutoProxyCreator是一个后置处理器 -4. 容器的创建流程: - 1. registerBeanPostProcessors()注册后置处理器,创建AnnotationAwareAspectJAutoProxyCreator对象 - 2. finishBeanFactoryInitialization()初始化剩下的单实例bean - 1. 创建业务逻辑组件和切面组件 - 2. AnnotationAwareAspectJAutoProxyCreator拦截组件的创建过程 - 3. 组件创建完之后,判断组件是否需要增强,如果是:切面的通知方法,包装成增强器(advisor),给业务逻辑组件创建一个代理对象(cglib) -5. 执行目标方法: - 1. 代理对象执行目标方法 - 2. CglibAopProxy.intercept() - 1. 得到目标方法的拦截器链(增强器包装成拦截器MethodInterceptor) - 2. 利用拦截器的链式机制,依次进入每一个拦截器进行执行 - 3. 效果: - - 正常执行:前置通知->目标方法->后置通知->返回通知 - - 出现异常:前置通知->目标方法->后置通知->异常通知 - -### spring中的单例模式和设计模式中的单例模式有什么区别 -- 单例模式是指在一个JVM进程中仅有一个实例,而Spring单例是指一个Spring Bean容器(ApplicationContext)中仅有一个实例。 -- 意味着: - - 在一个JVM进程中(理论上,一个运行的JAVA程序就必定有自己一个独立的JVM)仅有一个实例,于是无论在程序中的何处获取实例,始终都返回同一个对象。 - - 与此相比,Spring的单例Bean是与其容器(ApplicationContext)密切相关的,所以在一个JVM进程中,如果有多个Spring容器,即使是单例bean,也一定会创建多个实例。 - -## 二面 - -### redis为什么这么快 -略 - -### 介绍redis运行机制 -> redis 内部使用文件事件处理器 file event handler,这个文件事件处理器是单线程的,所以 redis 才叫做单线程的模型。它采用 IO 多路复用机制同时监听多个 socket,根据 socket 上的事件来选择对应的事件处理器进行处理。 - -文件事件处理器的结构包含 4 个部分: -- 多个 socket -- IO多路复用程序 -- 文件事件分派器 -- 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器) - -多个socket可能会并发产生不同的操作,每个操作对应不同的文件事件,但是IO多路复用程序会监听多个socket,会将socket产生的事件放入队列中排队,事件分派器每次从队列中取出一个事件,把该事件交给对应的事件处理器进行处理。 - -### hashmap多线程下安全问题,如何解决 -比如死循环分析: -- [https://zhuanlan.zhihu.com/p/67915754](https://zhuanlan.zhihu.com/p/67915754) - -### wait()和sleep()区别 -- 两者最主要的区别在于:**sleep方法没有释放锁,而wait方法释放了锁**。 -- 两者都可以暂停线程的执行。 -- wait通常被用于线程间交互/通信,sleep通常被用于暂停执行。 -- wait方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的notify或者notifyAll方法。sleep方法执行完成后,线程会自动苏醒。或者可以使用wait(long timeout)超时后线程会自动苏醒。 - -### synchronized和volatile区别 -synchronized的修饰范围: -- 修饰一个代码块 -- 修饰一个方法 -- 修饰一个类 -- 修饰一个静态的方法 - -> 个人的理解是:因为同步关键字Synchronized不能修饰变量(不能直接使用synchronized声明一个变量),不能使变量得到共享,故引入了轻量级的Volatie - -volatile: -- volatile可以修饰变量,共享变量。 -- 保障了共享变量对所有线程的可见性。即可保证在线程A将其修改时,线程B可以立刻得到。 -- 禁止指令重排序 - -### 线程池的参数定义,大小 -先放一波变量定义 -```java - /** - * 用给定的初始参数创建一个新的ThreadPoolExecutor。 - */ - public ThreadPoolExecutor(int corePoolSize,//线程池的核心线程数量 - int maximumPoolSize,//线程池的最大线程数 - long keepAliveTime,//当线程数大于核心线程数时,多余的空闲线程存活的最长时间 - TimeUnit unit,//时间单位 - BlockingQueue workQueue,//任务队列,用来储存等待执行任务的队列 - ThreadFactory threadFactory,//线程工厂,用来创建线程,一般默认即可 - RejectedExecutionHandler handler//拒绝策略,当提交的任务过多而不能及时处理时,我们可以定制策略来处理任务 - ) { - if (corePoolSize < 0 || - maximumPoolSize <= 0 || - maximumPoolSize < corePoolSize || - keepAliveTime < 0) - throw new IllegalArgumentException(); - if (workQueue == null || threadFactory == null || handler == null) - throw new NullPointerException(); - this.corePoolSize = corePoolSize; - this.maximumPoolSize = maximumPoolSize; - this.workQueue = workQueue; - this.keepAliveTime = unit.toNanos(keepAliveTime); - this.threadFactory = threadFactory; - this.handler = handler; - } -``` -![线程池参数](http://media.dreamcat.ink/uPic/线程池参数.png) - -ThreadPoolExecutor 3 个最重要的参数: -- `corePoolSize`:核心线程数定义了最小可以同时运行的线程数量。 -- `maximumPoolSize`:当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。 -- `workQueue`:当新任务来的时候会先判断当前运行的线程数量是否达到了核心线程数,如果达到的话,信任就会被从存放到队列中中。 - -ThreadPoolExecutor 其他常见参数: -- `keepAliveTime`:当线程池中的线程数量大于`corePoolSize`的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了`keepAliveTime`才会被回收销毁 -- `unit`:`keepAliveTime`参数的时间单位 -- `threadFactory`:executor创建新线程的时候会用到。 -- `handle`:饱和策略。关于饱和策略下面单独介绍 -如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了任时,`ThreadPoolTaskExecutor`定义一些策略: -- `ThreadPoolExecutor.AbortPolicy`:抛出`RejectedExecutionException`来拒绝新任务的处理。 -- `ThreadPoolExecutor.CallerRunsPolicy`:调用执行自己的线程运行任务。您不会任务请求。但是这种策略会降低对于新任务提交速度,影响程序的整体性能。另外,这个策略喜欢增加队列容量。如果您的应用程序可以承受此延迟并且你不能任务丢弃任何一个任务请求的话,你可以选择这个策略。 -- `ThreadPoolExecutor.DiscardPolicy`: 不处理新任务,直接丢弃掉。 -- `ThreadPoolExecutor.DiscardOldestPolicy`: 此策略将丢弃最早的未处理的任务请求。 - -大小: -> 如果我们设置的线程池数量太小的话,如果同一时间有大量任务/请求需要处理,可能会导致大量的请求/任务在任务队列中排队等待执行,甚至会出现任务队列满了之后任务/请求无法处理的情况,或者大量任务堆积在任务队列导致 OOM。这样很明显是有问题的! CPU 根本没有得到充分利用。 -> 但是,如果我们设置线程数量太大,大量线程可能会同时在争取 CPU 资源,这样会导致大量的上下文切换,从而增加线程的执行时间,影响了整体执行效率。 - -- **CPU 密集型任务(N+1)**: 这种任务消耗的主要是 CPU 资源,可以将线程数设置为 N(CPU 核心数)+1,比 CPU 核心数多出来的一个线程是为了防止线程偶发的缺页中断,或者其它原因导致的任务暂停而带来的影响。一旦任务暂停,CPU 就会处于空闲状态,而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间。 -- **I/O 密集型任务(2N)**: 这种任务应用起来,系统会用大部分的时间来处理 I/O 交互,而线程在处理 I/O 的时间段内不会占用 CPU 来处理,这时就可以将 CPU 交出给其它线程使用。因此在 I/O 密集型任务的应用中,我们可以多配置一些线程,具体的计算方法是 2N。 - -### java为何设计成单继承 -可以举个例子: -> 在这里有个A类,我们又编写了两个类B类和C类,并且B类和C类分别继承了A类,并且对A类的同一个方法进行了覆盖。 -> 如果此时我们再次编写了一个D类,并且D类以多继承的方式同时集成了B类和C类,那么D类也会继承B类和C类从A类中重载的方法,那么问题来了,D类也开始犯迷糊了,我到底应该哪个继承哪个类中的方法呢, -> 因为类是结构性的,这样就会造成结构上的混乱。这就是多继承的菱形继承问题。 - -可以实现对个接口: -> Java接口是行为性的,也就是说它只是定义某个行为的名称,而具体的行为的实现是集成接口的类实现的, -> 因此就算两个接口中定义了两个名称完全相同的方法,当某个类去集成这两个接口时, -> 类中也只会有一个相应的方法,这个方法的具体实现是这个类来进行编写的,所以并不会出现结构混乱的情况。 - -### 继承和组合有啥区别 -继承: -> 继承是Is a 的关系,比如说Student继承Person,则说明Student is a Person。继承的优点是子类可以重写父类的方法来方便地实现对父类的扩展。 - -- 父类的内部细节对子类是可见的。 -- 子类从父类继承的方法在编译时就确定下来了,所以无法在运行期间改变从父类继承的方法的行为。 -- 子类与父类是一种高耦合,违背了面向对象思想。 -- 继承关系最大的弱点是打破了封装,子类能够访问父类的实现细节,子类与父类之间紧密耦合,子类缺乏独立性,从而影响了子类的可维护性。 -- 不支持动态继承。在运行时,子类无法选择不同的父类。 - -组合: -- 不破坏封装,整体类与局部类之间松耦合,彼此相对独立。 -- 具有较好的可扩展性。 -- 支持动态组合。在运行时,整体对象可以选择不同类型的局部对象。 - -组合是has a的关系, 继承是is a的关系 -> 引用一句老话应该更能分清继承和组合的区别:组合可以被说成“我请了个老头在我家里干活” ,继承则是“我父亲在家里帮我干活"。 - -总结: -- 除非考虑使用多态,否则优先使用组合。 -- 要实现类似”多重继承“的设计的时候,使用组合。 -- 要考虑多态又要考虑实现“多重继承”的时候,使用组合+接口。 - - diff --git a/Interview/mianjing/jingdong/jingdong03.md b/Interview/mianjing/jingdong/jingdong03.md deleted file mode 100644 index c5c2cf8e..00000000 --- a/Interview/mianjing/jingdong/jingdong03.md +++ /dev/null @@ -1,196 +0,0 @@ -## 京东一,二面面经,许愿offer!!! -- [原文](https://www.nowcoder.com/discuss/419687) - -### hashmap数据结构 -#### put -**1.7** -直接上图: -![hashmap-put-1.7](http://media.dreamcat.ink/uPic/hashmap-put-1.7.png) -**1.8** -直接上图: -![hashmap-put-1.8](http://media.dreamcat.ink/uPic/hashmap-put-1.8.png) - -#### 100个结点,hashmap初始容量多少合适? -> Map在使用过程中不断的往里面存放数据,当数量达到了16 * 0.75 = 12就需要将当前16的容量进行扩容,而扩容这个过程涉及到rehash(重新哈希)、复制数据等操作,所有非常消耗性能。 - -#### 为什么1.8要引入红黑树,红黑树是平衡树吗? -- 当 Hash 冲突严重时,在桶上形成的链表会变的越来越长,这样在查询时的效率就会越来越低;时间复杂度为 O(N)。 -- 是 - -#### 红黑树为什么要分红节点和黑节点? -> 红黑树的查询性能略微逊色于AVL树,因为他比avl树会稍微不平衡最多一层,也就是说红黑树的查询性能只比相同内容的avl树最多多一次比较,但是,红黑树在插入和删除上完爆avl树,avl树每次插入删除会进行大量的平衡度计算,而红黑树为了维持红黑性质所做的红黑变换和旋转的开销,相较于avl树为了维持平衡的开销要小得多 - -### JVM的内存模型 -1.8之前: -![JVM内存模型-1.8之前](http://media.dreamcat.ink/uPic/JVM内存模型-1.8之前.png) - -1.8: -![JVM内存模型-1.8](http://media.dreamcat.ink/uPic/JVM内存模型-1.8.png) - -按照1.8总结: -**线程私有**: -- 程序计数器 -- 虚拟机栈 -- 本地方法栈 - -**线程共享**: -- 堆 -- 直接内存 - -**程序计数器**: -- 字节码解释器通过改变程序计数器来一次读取命令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。 -- 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程切换回来的时候能够知道线程上次运行到哪儿。 - -> 注意:程序计数器是唯一一个不会出现OutOfMemoryError的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束儿死亡。 - -**Java虚拟机栈**: -> 与程序计数器一样,Java虚拟机栈也是线程私有的,它的生命周期和线程相同,描述的Java方法执行的内存模型,每次方法调用的数据都是通过栈传递的。 - -Java内存可以粗糙的区分为堆内存(Heap)和栈内存(Stack),其中栈就是现在说的虚拟机栈,或者说是虚拟机栈中局部变量表部分: -- 局部变量表 -- 操作数栈: - - 8大基本类型 - - 对象引用:可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置 -- 动态链接 -- 方法出口 - -Java虚拟机栈会出现两种异常:`StackOverFlowError`和`OutOfMemoryError`。 -- `StackOverFlowError`:若Java虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前Java虚拟机栈的最大深度的时候,就抛出`StackOverFlowError`异常。 -- `OutOfMemoryError`:若Java虚拟机栈的内存大小允许动态扩展,且当线程请求栈时内存用完了,无法再动态扩展了,此时抛去`OutOfMemoryError`异常。 - -**本地方法栈**: -> 和虚拟机栈所发挥的作用非常相似,区别是: 虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。 其他和Java虚拟机差不多的 - -**堆**: -> Java 虚拟机所管理的内存中最大的一块,Java 堆是所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。 -> Java 堆是垃圾收集器管理的主要区域,因此也被称作GC 堆(Garbage Collected Heap).从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆还可以细分为:新生代和老年代:再细致一点有:Eden 空间、From Survivor、To Survivor 空间等。进一步划分的目的是更好地回收内存,或者更快地分配内存。 - -![堆内部分区](https://user-gold-cdn.xitu.io/2020/3/31/17130c2c0c80892e?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) -上图所示的 eden 区、s0("From") 区、s1("To") 区都属于新生代,tentired 区属于老年代。 -大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 s1("To"),并且对象的年龄还会加 1(Eden 区->Survivor 区后对象的初始年龄变为 1),当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。 -对象晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置。经过这次GC后,Eden区和"From"区已经被清空。 -这个时候,"From"和"To"会交换他们的角色,也就是新的"To"就是上次GC前的“From”,新的"From"就是上次GC前的"To"。 -不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,"To"区被填满之后,会将所有对象移动到年老代中。 - -## 二面 -### Thread和Runnable开启线程有什么区别? -- 避免了Java单继承的局限性; -- 把线程代码和任务的代码分离,解耦合(解除线程代码和任务的代码模块之间的依赖关系)。代码的扩展性非常好; - -### 线程池 -```java - /** - * 用给定的初始参数创建一个新的ThreadPoolExecutor。 - */ - public ThreadPoolExecutor(int corePoolSize,//线程池的核心线程数量 - int maximumPoolSize,//线程池的最大线程数 - long keepAliveTime,//当线程数大于核心线程数时,多余的空闲线程存活的最长时间 - TimeUnit unit,//时间单位 - BlockingQueue workQueue,//任务队列,用来储存等待执行任务的队列 - ThreadFactory threadFactory,//线程工厂,用来创建线程,一般默认即可 - RejectedExecutionHandler handler//拒绝策略,当提交的任务过多而不能及时处理时,我们可以定制策略来处理任务 - ) { - if (corePoolSize < 0 || - maximumPoolSize <= 0 || - maximumPoolSize < corePoolSize || - keepAliveTime < 0) - throw new IllegalArgumentException(); - if (workQueue == null || threadFactory == null || handler == null) - throw new NullPointerException(); - this.corePoolSize = corePoolSize; - this.maximumPoolSize = maximumPoolSize; - this.workQueue = workQueue; - this.keepAliveTime = unit.toNanos(keepAliveTime); - this.threadFactory = threadFactory; - this.handler = handler; - } -``` -![线程池参数](http://media.dreamcat.ink/uPic/线程池参数.png) - -`ThreadPoolExecutor` 3 个最重要的参数: - -- `corePoolSize` : 核心线程数线程数定义了最小可以同时运行的线程数量。 -- `maximumPoolSize` : 当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。 -- `workQueue`: 当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,信任就会被存放在队列中。 - -`ThreadPoolExecutor`其他常见参数: -- `keepAliveTime`:当线程池中的线程数量大于 `corePoolSize` 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime才会被回收销毁; -- `unit` : `keepAliveTime` 参数的时间单位。 -- `threadFactory` :`executor` 创建新线程的时候会用到。 -- `handler` :饱和策略。关于饱和策略下面单独介绍一下. - -`ThreadPoolTaskExecutor` 定义一些策略: -- `ThreadPoolExecutor.AbortPolicy`:抛出 `RejectedExecutionException来拒绝新任务的处理`。 -- `ThreadPoolExecutor.CallerRunsPolicy`:调用执行自己的线程运行任务。您不会任务请求。但是这种策略会降低对于新任务提交速度,影响程序的整体性能。另外,这个策略喜欢增加队列容量。如果您的应用程序可以承受此延迟并且你不能任务丢弃任何一个任务请求的话,你可以选择这个策略。 -- `ThreadPoolExecutor.DiscardPolicy`: 不处理新任务,直接丢弃掉。 -- `ThreadPoolExecutor.DiscardOldestPolicy`: 此策略将丢弃最早的未处理的任务请求。 - -### new对象时的过程 -![Java创建对象的过程](http://media.dreamcat.ink/uPic/Java创建对象的过程.png) - -1. 类加载检查,虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。 -2. 分配内存,在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。分配方式有 “指针碰撞” 和 “空闲列表” 两种,选择那种分配方式由 Java 堆是否规整决定,而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。 -3. 初始化零值,内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。 -4. 设置对象头,初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是那个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。 -5. 执行init方法,在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始,方法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行 方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。 - -**内存布局**: -在 Hotspot 虚拟机中,对象在内存中的布局可以分为 3 块区域: -- 对象头 -- 实例数据 -- 对齐填充 -Hotspot 虚拟机的对象头包括两部分信息 -- 第一部分用于存储对象自身的自身运行时数据(哈希码、GC 分代年龄、锁状态标志等等) -- 另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是那个类的实例。 -实例数据部分是对象真正存储的有效信息,也是在程序中所定义的各种类型的字段内容。 - -> 对齐填充部分不是必然存在的,也没有什么特别的含义,仅仅起占位作用。 因为 Hotspot 虚拟机的自动内存管理系统要求对象起始地址必须是 8 字节的整数倍,换句话说就是对象的大小必须是 8 字节的整数倍。而对象头部分正好是 8 字节的倍数(1 倍或 2 倍),因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。 - -**对象的访问方式**: -建立对象就是为了使用对象,我们的 Java 程序通过栈上的 reference 数据来操作堆上的具体对象。对象的访问方式由虚拟机实现而定,目前主流的访问方式有 -- 使用句柄 -- 直接指针 - -### mysql的优化?索引??联合索引?? -#### 索引 -- 普通索引:仅加速查询 -- 唯一索引:加速查询+列值唯一(可以有null) -- 主键索引:加速查询+列值唯一(不可以有null)+表中只有一个 -- 组合索引:多列值组成一个索引,专门用于组合搜索,其效率大于索引合并 -- 全文索引:对文本的内容进行分词,进行搜索 -- 索引合并:使用多个单列索引组合搜索 -- 覆盖索引:select的数据列只用从索引中就能够取得,不必读取数据行,换句话说查询列要被所建的索引覆盖 - -#### 优化 -**索引优化**: -- 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性 -- 可以大大加快数据的检索速度,这也是创建索引的最主要的原因 -- 帮助服务器避免排序和临时表 -- 将随机IO变为顺序IO -- 可以加速表和表之间的连接,特别是咋实现数据的参考完整性方面的特别喲意义 - -**数据库表优化** -- 范式优化:比如消除冗余 -- 反范式优化:比如适当加冗余等(减少join) -- 限定数据的范围:务必禁止不带任何限制数据范围条件的查询语句。比如:我们当用户在查询订单历史的时候,我们可以控制在一个月的范围内。 -- 读/写分离:经典的数据库拆分方案,主库负责写,从库负责读 -- 拆分表:分区将数据在物理上分隔开,不同分区的数据可以制定保存在处于不同磁盘上的数据文件里。这样,当对这个表进行查询时,只需要在表分区中进行扫描,而不必进行全表扫描,明显缩短了查询时间。 - -**联合索引** -> MySQL中的索引可以以一定顺序引用多个列,这种索引叫做联合索引一般的,一个联合索引是一个有序元组,其中各个元素均为数据表的一列。另外,单列索引可以看成联合索引元素数为1的特例。 - -索引匹配的最左原则具体是说,假如索引列分别为A,B,C,顺序也是A,B,C: -``` -- 那么查询的时候,如果查询【A】【A,B】 【A,B,C】,那么可以通过索引查询 -- 如果查询的时候,采用【A,C】,那么C这个虽然是索引,但是由于中间缺失了B,因此C这个索引是用不到的,只能用到A索引 -- 如果查询的时候,采用【B】 【B,C】 【C】,由于没有用到第一列索引,不是最左前缀,那么后面的索引也是用不到了 -- 如果查询的时候,采用范围查询,并且是最左前缀,也就是第一列索引,那么可以用到索引,但是范围后面的列无法用到索引 -``` - -### 5L,3L的杯子,如何装4L的水?? -5L,3L的杯子,如何装4L的水: -- 5L 桶装满水 -- 由 5L 桶向 3L 桶倒水 , 倒满为止 -- 把 3L 桶的水倒掉 , 由 5L 桶向 3L 桶倒水 -- 把 5L 桶装满 -- 由 5L 桶向 3L 桶倒水 , 倒满为止 diff --git a/Interview/mianjing/jingdong/jingdong04.md b/Interview/mianjing/jingdong/jingdong04.md deleted file mode 100644 index fbc667cc..00000000 --- a/Interview/mianjing/jingdong/jingdong04.md +++ /dev/null @@ -1,717 +0,0 @@ -## 京东Java春季实习 -- [原文链接](https://www.nowcoder.com/discuss/413662) - -## 一面 - -### hashcode和equals方法 -#### == -> 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象(基本数据类型==比较的是值,引用数据类型==比较的是内存地址)。 - -#### equals -> 它的作用也是判断两个对象是否相等。但它一般有两种使用情况: -- 情况1:类没有覆盖equals方法。则通过equals比较该类的两个对象时,等价于通过"=="比较这两个对象。 -- 情况2:类覆盖了equals方法。一般,我们都覆盖了equals方法来比较两个对象的内容呢是否相相等;若它们的内容相等,则返回true(即,认为这两个对象相等)。 -- String中的equals方法是被重写过的,因为object的equals方法是比较的对象的内存地址,而String的equals方法比较的是对象的值。 -- 当创建String类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前你引用。如果没有就在常量池中重新创建哪一个String对象。 - -#### hashcode -> 它的作用是获取哈希吗,也称为散列码,它实际上是返回一个int整数。这个哈希吗的作用是确定该对象在哈希表中的索引位置。hashcode定义在jdk的object.java中,这就意味着Java中的任何类都包涵hashcode函数。 - -#### 为什么要用hashcode -我们先以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode: -> 当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较, -> 如果没有相符的hashcode,HashSet会假设对象没有重复出现。 -> 但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。 -> 如果两者相同,HashSet 就不会让其加入操作成功。 -> 如果不同的话,就会重新散列到其他位置。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。 - -### 接口和抽象类的区别 -1. 接口的方法默认是public,所有方法在接口中不能有实现(除了java8),而抽象类可以有非抽象的方法。 -2. 接口中除了static、final变量,不能有其他变量,而抽象类中则不一定。 -3. 一个类可以实现多个接口,但只能实现一个抽象类。接口自己本身可以通过implement关键字扩展多个接口。 -4. 接口方法默认修饰符是public,抽象方法可以有public、protected和default这些修饰符(抽象方法就是为了被重写所以不能使用private关键字修饰) -5. 从设计层面来讲,抽象是对类的抽象,是一种能模板设计,而接口是对行为的抽象, 是一种行为的规范。 - -### 重写和重载的区别 - -#### 重载 -> 发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同。 - -#### 重写 -> 重写是子类对父类的允许访问的方法的实现过程进行重新编写,发生在子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类。 -> 另外,如果父类方法访问修饰符为private则子类就不能重写该方法。 -> 也就是说方法提供的行为改变,而方法的外貌并没有改变。 - -### 集合类有哪些,有什么区别 -> 容器主要包括 Collection 和 Map 两种,Collection 存储着对象的集合,而 Map 存储着键值对(两个对象)的映射表。 - -#### Set -- TreeSet -> 基于红黑树实现,支持有序性操作,例如根据一个范围查找元素的操作。但是查找效率不如HashSet,HashSet查找的时间复杂为O(1),TreeSet则为O(logN)。 - -- HashSet -> 基于哈希表实现,支持快速查找,但不支持有序性操作。并且失去了元素的插入顺序信息,也就是说使用Iterator遍历HashSet得到的结构是不确定的。 - -- LinkedHashSet -> 具有HashSet的查找效率,且内部使用双向链表维护元素的插入顺序。 - -#### List -- ArrayList -> 基于动态数组实现,支持随机访问。 - -- Vector -> 和ArrayList类似,但它是线程安全的。 - -- LinkedList -> 基于双向链表实现,只能顺序访问,但是可以快速地在链表中间插入和删除元素。不仅如此,LinkedList还可以用作栈、队列和双向队列。 - -#### Queue -- LinkedList -> 可以用来实现双向队列。 - -- PriorityQueue -> 基于堆结构实现,可以用它实现优先队列。 - -#### Map -- TreeMap -> 基于红黑树实现 - -- HashMap -> 基于哈希表实现 - -- HashTable - - 和 HashMap 类似,但它是线程安全的,这意味着同一时刻多个线程可以同时写入 HashTable 并且不会导致数据不一致。 - - 它是遗留类,不应该去使用它。 - - 现在可以使用 ConcurrentHashMap 来支持线程安全,并且 ConcurrentHashMap 的效率会更高,因为 ConcurrentHashMap 引入了分段锁(1.8前)。 - -- LinkedHashMap -> 使用双向链表来维护元素的顺序,顺序为插入顺序或者最近最少使用(LRU)顺序。 - -### 如何实现多线程 -- 继承Thread类创建线程 -```java -public class ThreadTest extends Thread{ - //重写run()方法,run()方法的方法体是线程执行体 - public void run(){ - for(int i=0;i<5;i++){ - //使用线程的getName()方法可以直接获取当前线程的名称 - System.out.println(this.getName() + " " + i); - } - } - - public static void main(String[] args){ - //输出Java程序运行时默认运行的主线程名称 - System.out.println(Thread.currentThread().getName()); - //创建第一个线程并开始执行 - new ThreadTest().start(); - //创建第二个线程并开始执行 - new ThreadTest().start(); - } -} -``` -- 实现Runnable接口创建线程 -```java -public class ThreadTest implements Runnable{ - private int i; - //重写run()方法,run()方法的方法体是线程执行体 - public void run(){ - //实现Runnable接口时,只能使用如下方法获取线程名 - System.out.println(Thread.currentThread().getName() + " " + i); - i++; - } - - public static void main(String[] args){ - ThreadTest tt = new ThreadTest(); - //创建第一个线程并开始执行 - //输出 新线程1 0 - new Thread(tt,"新线程1").start(); - //创建第二个线程并开始执行 - //输出 新线程2 1 - new Thread(tt,"新线程2").start(); - //使用Lambda表达式创建Runnable对象 - new Thread(()->{ - System.out.print("hello"); - System.out.println("'world"); - }).start(); - } -} -``` -- 使用Callable和Future创建线程 -```java -public class ThreadTest { - public static void main(String[] args){ - //使用FutureTask来包装Callable对象 - //使用Lambda表达式来创建Callable对象 - FutureTask task = new FutureTask<>(()->{ - System.out.println(Thread.currentThread().getName() + " " + "开始执行任务!"); - return 0; - }); - //实质还是以Callable对象来创建并启动线程 - //输出 新线程 开始执行任务! - new Thread(task,"新线程").start(); - try{ - //获取线程的返回值 - //输出 0 - System.out.print(task.get()); - }catch (Exception ex){ - ex.printStackTrace(); - } - - } -} -``` - -- 使用线程池例如用Executor框架 -```java -public class ThreadTest { - public static void main(String[] args){ - ExecutorService executorService = Executors.newFixedThreadPool(1); - executorService.submit(()->{ - System.out.println(Thread.currentThread().getName()); - }); - } -} -``` - -### JVM内存分布 -#### 1.8之前 -![JVM内存模型-1.8之前](http://media.dreamcat.ink/uPic/JVM内存模型-1.8之前.png) - -#### 1.8及之后 -![JVM内存模型-1.8](http://media.dreamcat.ink/uPic/JVM内存模型-1.8.png) - -#### 大概总结 -线程私有: -- 程序计数器 -- 虚拟机栈 -- 本地方法栈 - -线程共享: -- 堆 -- 直接内存 - -##### 程序计数器 -- 字节码解释器通过改变程序计数器来一次读取命令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。 -- 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程切换回来的时候能够知道线程上次运行到哪 -> 注意:程序计数器是唯一一个不会出现OutOfMemoryError的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。 - -##### Java虚拟机栈 -> 与程序计数器一样,Java虚拟机栈也是线程私有的,它的生命周期和线程相同,描述的Java方法执行的内存模型,每次方法调用的数据都是通过栈传递的。 - -Java内存可以粗糙的区分为堆内存和栈内存,其中栈就是现在说的虚拟机栈,或者说是虚拟机栈中的局部变量表部分: -- 局部变量表 -- 操作数栈: - - 8大基本类型 - - 对象引用:可能是一个指向对象其实地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置 -- 动态链接 -- 方法出口 - -Java虚拟机栈会出现两种异常:`StackOverFlowError`和`OutOfMemoryError`。 -- `StackOverFlowError`:若Java虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前Java虚拟机栈的祖达深度的时候,就抛出`StackOverFlowError`异常。 -- `OutOfMemoryError`:若Java虚拟机栈的内存大小允许动态扩展,且线程请求栈时内存用完了,无法再动态扩展了,此时抛出`OutOfMemoryError`异常。 - -##### 本地方法栈 -> 和虚拟机栈所发挥的作用非常相似,区别是: 虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。 其他和Java虚拟机差不多的 - -##### 堆 -> Java 虚拟机所管理的内存中最大的一块,Java 堆是所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。 - Java 堆是垃圾收集器管理的主要区域,因此也被称作GC 堆(Garbage Collected Heap).从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆还可以细分为:新生代和老年代:再细致一点有:Eden 空间、From Survivor、To Survivor 空间等。进一步划分的目的是更好地回收内存,或者更快地分配内存。 - -![堆内存分区](http://media.dreamcat.ink/uPic/堆内存分区.png) - -- 上图所示的 eden 区、s0("From") 区、s1("To") 区都属于新生代,tentired 区属于老年代。 -- 大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 s1("To"),并且对象的年龄还会加 1(Eden 区->Survivor 区后对象的初始年龄变为 1),当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。 -- 对象晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置。经过这次GC后,Eden区和"From"区已经被清空。 -- 这个时候,"From"和"To"会交换他们的角色,也就是新的"To"就是上次GC前的“From”,新的"From"就是上次GC前的"To"。 -- 不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,"To"区被填满之后,会将所有对象移动到年老代中。 - -### 讲一讲Spring原理 -谈Spring原来,不得不谈Ioc了 -> IoC(Inverse of Control:控制反转)是一种设计思想,就是将原本在程序中手动创建对象的控制权,交由Spring框架来管理。IoC在其他语言中也有应用,并非Spring持有。 -> IoC容器是Spring用来实现IoC的载体,IC容器实际上就是个Map(key,value),Map中存放的是各种对象。 - -将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。 -这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。 -IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。 -在实际项目中一个 Service 类可能有几百甚至上千个类作为它的底层,假如我们需要实例化这个 Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把人逼疯。 -如果利用 IoC 的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。 - -#### Spring中的ioc运行机制 -```java -public void refresh() throws BeansException, IllegalStateException { - // 来个锁,不然 refresh() 还没结束,你又来个启动或销毁容器的操作,那不就乱套了嘛 - synchronized (this.startupShutdownMonitor) { - - // 准备工作,记录下容器的启动时间、标记“已启动”状态、处理配置文件中的占位符 - prepareRefresh(); - - // 这步比较关键,这步完成后,配置文件就会解析成一个个 Bean 定义,注册到 BeanFactory 中, - // 当然,这里说的 Bean 还没有初始化,只是配置信息都提取出来了, - // 注册也只是将这些信息都保存到了注册中心(说到底核心是一个 beanName-> beanDefinition 的 map) - ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); - - // 设置 BeanFactory 的类加载器,添加几个 BeanPostProcessor,手动注册几个特殊的 bean - // 这块待会会展开说 - prepareBeanFactory(beanFactory); - - try { - // 【这里需要知道 BeanFactoryPostProcessor 这个知识点,Bean 如果实现了此接口, - // 那么在容器初始化以后,Spring 会负责调用里面的 postProcessBeanFactory 方法。】 - - // 这里是提供给子类的扩展点,到这里的时候,所有的 Bean 都加载、注册完成了,但是都还没有初始化 - // 具体的子类可以在这步的时候添加一些特殊的 BeanFactoryPostProcessor 的实现类或做点什么事 - postProcessBeanFactory(beanFactory); - // 调用 BeanFactoryPostProcessor 各个实现类的 postProcessBeanFactory(factory) 方法 - invokeBeanFactoryPostProcessors(beanFactory); - - // 注册 BeanPostProcessor 的实现类,注意看和 BeanFactoryPostProcessor 的区别 - // 此接口两个方法: postProcessBeforeInitialization 和 postProcessAfterInitialization - // 两个方法分别在 Bean 初始化之前和初始化之后得到执行。注意,到这里 Bean 还没初始化 - registerBeanPostProcessors(beanFactory); - - // 初始化当前 ApplicationContext 的 MessageSource,国际化这里就不展开说了,不然没完没了了 - initMessageSource(); - - // 初始化当前 ApplicationContext 的事件广播器,这里也不展开了 - initApplicationEventMulticaster(); - - // 从方法名就可以知道,典型的模板方法(钩子方法), - // 具体的子类可以在这里初始化一些特殊的 Bean(在初始化 singleton beans 之前) - onRefresh(); - - // 注册事件监听器,监听器需要实现 ApplicationListener 接口。这也不是我们的重点,过 - registerListeners(); - - // 重点,重点,重点 - // 初始化所有的 singleton beans - //(lazy-init 的除外) - finishBeanFactoryInitialization(beanFactory); - - // 最后,广播事件,ApplicationContext 初始化完成 - finishRefresh(); - } - - catch (BeansException ex) { - if (logger.isWarnEnabled()) { - logger.warn("Exception encountered during context initialization - " + - "cancelling refresh attempt: " + ex); - } - - // Destroy already created singletons to avoid dangling resources. - // 销毁已经初始化的 singleton 的 Beans,以免有些 bean 会一直占用资源 - destroyBeans(); - - // Reset 'active' flag. - cancelRefresh(ex); - - // 把异常往外抛 - throw ex; - } - - finally { - // Reset common introspection caches in Spring's core, since we - // might not ever need metadata for singleton beans anymore... - resetCommonCaches(); - } - } -} -``` -![spring-ioc](http://media.dreamcat.ink/uPic/spring-ioc.png) - -大概总结: -1. Spring容器在启动的时候,先会保存所有注册进来的Bean的定义信息; - 1. xml注册bean - 2. 注解注册bean;@Service、@Component、@Bean,xxx - 3. Spring容器会在合适时机创建这些Bean - 1. 用到这个bean的创建;利用getBean创建bean;创建好以后保存在容器中。 - 2. 统一创建剩下所有的bean的时候;finishBeanFactoryInitialization(); - 4. 后置处理器;BeanPostProcessor - 1. 每一个bean创建完成,都会使用各种后置处理器进行处理,来增强bena的功能,比如: - - AutowiredAnnotationBeanPostProcessor:处理自动注入 - - AnnotationAwareAspectJAutoProxyCreator:来做AOP功能; - 5. 事件驱动模型: - - ApplicationListener:事件监听; - - ApplicationEventMulticaster:事件派发; - - -### mysql的引擎,索引结构,索引有哪些类型? -#### 数据库引擎innodb与myisam的区别 -InnoDB: -- 是MySQL默认的事务型存储引擎,只有在需要它不支持的特性时,才考虑使用其他存储引擎。 -- 实现了四个标准的隔离级别,默认级别是可重复读。在可重复读隔离级别下,通过多版本并发控制(MVCC)+间隙锁(Next-Key Locking)防止幻影读。 -- 主索引是聚簇索引,在索引中保存了数据,从而避免直接读取磁盘,因此对查询性能有很大的提升。 -- 内部做了很多优化,包括从磁盘读取数据时采用的可预测性读、能够加快读操作并且自动创建的自适应哈希索引、能够加速插入操作的插入缓冲区等。 -- 支持真正的在线热备份。其他存储引擎不支持在线热备份,要获取一致性视图需要停止对所有表的写入,而在读写混合场景中,停止写入可能也意味着停止读取。 - -MyISAM: -- 设计简单,数据以紧密格式存储。对于只读数据,或者表比较小、可以容忍修复操作,则依然可以使用它。 -- 提供了大量的特性,包括压缩表、空间数据索引等。 -- 不支持事务。 -- 不支持行级锁,只能对整张表加锁,读取时会对需要读到的所有表加共享锁,写入时则对表加排它锁。但在表有读取操作的同时,也可以往表中插入新的记录,这被称为并发插入(CONCURRENT INSERT)。 - -#### 索引类型 -- FULLTEXT - 即为全文索引,目前只有MyISAM引擎支持。其可以在CREATE TABLE ,ALTER TABLE ,CREATE INDEX 使用,不过目前只有 CHAR、VARCHAR ,TEXT 列上可以创建全文索引。 -- HASH - 由于HASH的唯一(几乎100%的唯一)及类似键值对的形式,很适合作为索引。 - HASH索引可以一次定位,不需要像树形索引那样逐层查找,因此具有极高的效率。但是,这种高效是有条件的,即只在“=”和“in”条件下高效,对于范围查询、排序及组合索引仍然效率不高。 -- BTREE - BTREE索引就是一种将索引值按一定的算法,存入一个树形的数据结构中(二叉树),每次查询都是从树的入口root开始,依次遍历node,获取leaf。这是MySQL里默认和最常用的索引类型。 -- RTREE - RTREE在MySQL很少使用,仅支持geometry数据类型,支持该类型的存储引擎只有MyISAM、BDb、InnoDb、NDb、Archive几种。 - 相对于BTREE,RTREE的优势在于范围查找。 - -#### B+树 -B+ 树是一种树数据结构,通常用于关系型数据库(如Mysql)和操作系统的文件系统中。B+ 树的特点是能够保持数据稳定有序,其插入与修改拥有较稳定的对数时间复杂度。B+ 树元素自底向上插入,这与二叉树恰好相反。 -在B树基础上,为叶子结点增加链表指针(B树+叶子有序链表),所有关键字都在叶子结点 中出现,非叶子结点作为叶子结点的索引;B+树总是到叶子结点才命中。 -b+树的非叶子节点不保存数据,只保存子树的临界值(最大或者最小),所以同样大小的节点,b+树相对于b树能够有更多的分支,使得这棵树更加矮胖,查询时做的IO操作次数也更少。 - -### mysql 的共享锁和排它锁 - -#### 共享锁 -> 共享锁又称为读锁,简称S锁,顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。 - -#### 排它锁 -> 排他锁又称为写锁,简称X锁,顾名思义,排他锁就是不能与其他所并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据就行读取和修改。 - -### mybatis如何防止sql注入 -**首先看一下下面两个sql语句的区别:** -```mysql - - SELECT - sc.uuid , - sc.begin_date , - sc.begin_time , - sc.bus_id , - sc.bus_status, - sc.seat_status - FROM - sb_count sc - ${ew.customSqlSegment} - -``` - -### getCountDetailById -> 查询场次详情信息 -```java -// 自定义查询,涉及到联表查询 -QueryWrapper wrapper = new QueryWrapper<>(); -wrapper.eq("sc.uuid", request.getCountId()); -``` -**xml** -```xml - -``` - -### repeatSeats -> 判断座位是否已重复 -```java -public boolean repeatSeats(String seats, Long coundId) { - // 查查数据库, 找到座位字段 - boolean b = false; // false:不重复,true:重复 - try { - Count count = countMapper.selectById(coundId); - // 比如,selectedSeats 是1,2 - // dbSeats:"", - // dbSeats:"1,2,3", - // dbSeats: "4,5" - // 前端传来的selectedSeats, 前端判断是否为空,要不然后端也判断一下得了 - if (seats.equals("")) { - return true; - } - if (count.getSelectedSeats().equals("")) { - return false; - } - String[] ss = seats.split(","); - String[] cs = count.getSelectedSeats().split(","); - // 这里考虑并发问题 - HashSet hashSet = new HashSet<>(Arrays.asList(cs)); - for (String s : ss) { - if (hashSet.contains(s)) return true; - } - } catch (Exception e) { - e.printStackTrace(); - log.error("selectedSeats", e); - return true; // 异常就算是重复 - } - return b; -} -``` -### addSeats -```java -if (!StrUtil.isEmpty(selectedSeats)) { - // 这里可以优化,字符串拼接,这样的方式爆内存 - // StringBuffer - newSelectedSeats = selectedSeats + "," + newSelectedSeats; -} -``` - -### filterRepeatSeats -> 回退座位 -```java -Count count = countMapper.selectById(coundId); -String[] ss = seats.split(","); -String[] cs = count.getSelectedSeats().split(","); -// 并发问题,注意 -HashSet hashSet = new HashSet<>(Arrays.asList(cs)); -for (String s : ss) { - if (hashSet.contains(s)) { - hashSet.remove(s); - } -} -if (hashSet.isEmpty()) { - count.setSelectedSeats(""); -} -// 考虑了并发 -StringBuffer sb = new StringBuffer(); -for (String s : hashSet) { - sb.append(s); - sb.append(","); -} -// 上面的方案可以用String的replace的方法替换,遍历要回退的座位,依次替换即可 -count.setSelectedSeats(sb.toString()); -countMapper.updateById(count); -``` -看一下String的replace的源码(1.8),感觉遍历还挺多,但的确不用自己去写了 -```java -public String replace(char oldChar, char newChar) { - if (oldChar != newChar) { - int len = value.length; - int i = -1; - char[] val = value; /* avoid getfield opcode */ - - while (++i < len) { - if (val[i] == oldChar) { - break; - } - } - if (i < len) { - char buf[] = new char[len]; - for (int j = 0; j < i; j++) { - buf[j] = val[j]; - } - while (i < len) { - char c = val[i]; - buf[i] = (c == oldChar) ? newChar : c; - i++; - } - return new String(buf, true); - } - } - return this; -} -``` - -## BusSchedule定时器 -> 这里可以使用redis的延时队列或者RocketMQ的消息队列 - -### schedulChangeBusStatus - -```java -/** - * 每天上午7点到晚上21点,每隔30分钟执行一次 - */ -@Scheduled(cron = "0 0/30 7-21 * * ?") -private void schedulChangeBusStatus() { - log.warn("schedulChangeBusStatus执行"); - busService.schedulChangeBusStatus(); -} -``` -看一下业务逻辑 -```java -public void schedulChangeBusStatus() { - // 获取时间 - String currTime = DateUtil.getHours(); - // 获取日期 - String day = DateUtil.getDay(); - log.warn("schedulChangeBusStatus->目前时间:" + currTime); - log.warn("schedulChangeBusStatus->目前时间:" + day); - System.out.println("目前时间:"+ currTime); - System.out.println("目前时间:"+ day); - QueryWrapper queryWrapper = new QueryWrapper<>(); - // 先取出beingtime和now相等的表或者end_time和now相等到表 - // 1. 取出当天 - // 2. 取出开始时间或者结束时间符合当前时间 - queryWrapper - .eq("begin_date", day) // 取出当天 - .and(o -> o.eq("begin_time", currTime) // 当前时间 - .or() - .eq("end_time", currTime)); - List counts = countMapper.selectList(queryWrapper); - log.warn("schedulChangeBusStatus->查询到的:" + counts.toString()); - // 开始作妖 - for (Count count : counts) { - String busStatus = count.getBusStatus(); - String beginTime = count.getBeginTime(); - String endTime = count.getEndTime(); - if (currTime.equals(beginTime)) { - if (busStatus.equals("0")) { // 沙河空闲 - count.setBusStatus("2"); // 沙河->清水河 - } - if (busStatus.equals("1")) { // 清水河空闲 - count.setBusStatus("3"); // 清水河->沙河 - } - count.setSelectedSeats(""); // 清空座位 - } - if (currTime.equals(endTime)) { - if (busStatus.equals("2")) { // 沙河->清水河 - count.setBusStatus("1"); // 清水河空闲 - } - if (busStatus.equals("3")) { // 清水河->沙河 - count.setBusStatus("0"); // 沙河空闲 - } - } - System.out.println("修改的:" + count); - log.warn("schedulChangeBusStatus->修改的:" + count); - // 写入数据库 - countMapper.updateById(count); - } - // 删缓存,这里非常重要...不删后果很严重 - String key1 = RedisConstants.COUNTS_EXPIRE + "0"; - String key2 = RedisConstants.COUNTS_EXPIRE + "1"; - if (redisUtils.hasKey(key1)) { - redisUtils.del(key1); - } - if (redisUtils.hasKey(key2)) { - redisUtils.del(key2); - } -} -``` -> 这里每隔30分钟扫库进行一次io,这里可以有优化... 假如我使用Redis的延迟队列 -1. 在用定时器添加场次的时候,可以将这些场次存入Redis的zset的队列中,元素为场次ID,元素对应的score是出发时间,这样就有17个(定时器每天凌晨添加17个) -2. 还是用定时器轮询,采用每隔半小时轮询一次,我们取出队列中当前时间大于队列中权重的时间的场次ID,开始进行业务逻辑判断(更改场次状态) -3. 更改过后,删除或者更改该场次的score的时间(结束时间) -4. 以此类推,如果是结束时间的话,直接删除该场次id和score。 - -### addCounts -> 这个项目,没有后台,因此场次需要定时器添加即可 -```java -/** - * 每天凌晨0点2分执行 - */ -@Scheduled(cron = "0 2 0 * * ? ") -private void addCounts(){ - log.warn("addCounts执行"); - busService.addCounts(); -} -``` -具体的业务逻辑: -```java -public void addCounts() { - // 获取日期 - String day = DateUtil.getDay(); - // 获取前17个场次 - QueryWrapper wrapper = new QueryWrapper<>(); - wrapper.last("limit 17"); - List counts = countMapper.selectList(wrapper); - // 开始修改 这里可以用java8 的特性, 还不是很熟悉,后期优化一下 - for (Count count : counts) { - // 更改日期 - count.setBeginDate(day); - // 更改uuid - count.setUuid(UUIDUtils.flakesUUID()); - // 清空座位 - count.setSelectedSeats(""); - // 将走位状态清零 - count.setSeatStatus("0"); - // 插入 - countMapper.insert(count); - } - - // 删缓存,不删后果依然很严重 - String key1 = RedisConstants.COUNTS_EXPIRE + "0"; - String key2 = RedisConstants.COUNTS_EXPIRE + "1"; - if (redisUtils.hasKey(key1)) { - redisUtils.del(key1); - } - if (redisUtils.hasKey(key2)) { - redisUtils.del(key2); - } -} - -``` diff --git "a/Java/codes/bus/\347\224\250\346\210\267\346\234\215\345\212\241.md" "b/Java/codes/bus/\347\224\250\346\210\267\346\234\215\345\212\241.md" deleted file mode 100644 index be1fc864..00000000 --- "a/Java/codes/bus/\347\224\250\346\210\267\346\234\215\345\212\241.md" +++ /dev/null @@ -1,74 +0,0 @@ -# 用户服务 - - -## UserController - -### checkUsername -> 这个我就不需要再说了,这个没啥可说的 - - -### register -> 这个注意请求体的参数即可,也没什么可说的 - - -### getUserById -> 注意缓存是否存在,存在直接返回即可,没有就调用业务`getUserById`获取结果 - -### updateUserInfo -> 注意请求体的参数即可,并且若是更新数据,那么自然要删除UserInfo的缓存,保持数据一致,写不写缓存这一点都行,下次请求getUserById会写缓存。 - -### logout -> 注意redis删除缓存即可 - - -## UserServiceImpl - -### checkUsername -```java -QueryWrapper queryWrapper = new QueryWrapper<>(); -// 查询user_name列与getUsername相匹配的数据 -queryWrapper.eq("user_name", request.getUsername()); -User user = userMapper.selectOne(queryWrapper); -``` - -### regsiter -```java -// 密码采用了md5加密 -String md5Password = MD5Util.encrypt(user.getUserPwd()); -user.setUserPwd(md5Password); -``` - -### login -```java -QueryWrapper queryWrapper = new QueryWrapper<>(); -// 根据user_name查询用户是否存在 -queryWrapper.eq("user_name", request.getUsername()); -User user = userMapper.selectOne(queryWrapper); -if (user != null && user.getUuid() > 0) { - // 用户不为空,用户id > 0 - String md5Password = MD5Util.encrypt(request.getPassword()); - if (user.getUserPwd().equals(md5Password)) { - // 数据库密码和所加密的md5密码一致 - res.setUserId(user.getUuid()); - res.setCode(SbCode.SUCCESS.getCode()); - res.setMsg(SbCode.SUCCESS.getMessage()); - } -} -``` - -### getUserById -```java -//根据id查询用户 -User user = userMapper.selectById(request.getId()); -UserDto userDto = userConverter.User2Res(user); -``` - -### updateUserInfo -```java -// 这里一采用了mapstruct映射,有兴趣的可以去网上找资料学习学习 -User user = userConverter.res2User(request); -``` - -## 总结 - -用户服务,没有什么比较复杂的业务,复杂的业务在订单服务、场次服务、支付服务、 diff --git "a/Java/codes/bus/\350\256\242\345\215\225\346\234\215\345\212\241.md" "b/Java/codes/bus/\350\256\242\345\215\225\346\234\215\345\212\241.md" deleted file mode 100644 index 857d0fbe..00000000 --- "a/Java/codes/bus/\350\256\242\345\215\225\346\234\215\345\212\241.md" +++ /dev/null @@ -1,282 +0,0 @@ -# 订单服务 -## OrderController - -### getNoTakeOrdersById -> 未乘坐订单 - -```java -if (redisUtils.hasKey(key)) { - // redis是否有该缓存 - Object obj = redisUtils.get(key); - NoTakeBusResponse response = (NoTakeBusResponse) obj; - for (NoTakeDto noTakeDto : response.getNoTakeDtos()) { - // 如果场次发车时间-当前时间是大0的,那么说明已经发车了 - String beginTime = noTakeDto.getBeginTime(); - if (beginTime.compareTo(DateUtil.getHours()) <= -1) { - // 删掉当前缓存 - redisUtils.del(key); - // 重新获取最新的数据 - response = orderService.getNoTakeOrdersById(request); - // 写缓存 - redisUtils.set(key, response, RedisConstants.NO_TAKE_OREDERS_EXPIRE.getTime()); - return new ResponseUtil().setData(response); - } - } - log.warn("getNoTakeOrdersById->redis\n"); - return new ResponseUtil().setData(obj); -} -``` - -### getNoPayOrdersById -> 获取未支付订单接口 -```java -// 从redis中是否有缓存 -if (redisUtils.hasKey(key)) { - // 有就获取 - Object obj = redisUtils.get(key); - log.warn("getNoPayOrdersById->redis\n"); - return new ResponseUtil().setData(obj); -} -``` -### getEvaluateOrdersById -> 根据评价状态获取用户订单接口 -略 - -### addOrder -> 添加订单接口 - -```java -String countKey = RedisConstants.COUNT_DETAIL_EXPIRE.getKey() + request.getCountId(); -// 座位缓存失效 -if (redisUtils.hasKey(countKey)) { - redisUtils.del(countKey); -} -String noPayKey = RedisConstants.NO_PAY_ORDERS_EXPIRE.getKey() + userId; -// 未支付列表缓存失效 -if (redisUtils.hasKey(noPayKey)) { - redisUtils.del(noPayKey); -} -``` -### selectOrderById -> 根据订单id获取详情订单 -略 - -### updateOrderStatus -> 更改订单状态 -```java -// 删掉订单详情缓存 -if (redisUtils.hasKey(selectOrderKey)) { - redisUtils.del(selectOrderKey); -} -// 删除未乘坐缓存 -if (redisUtils.hasKey(noTakeKey)) { - redisUtils.del(noTakeKey); -} -// 删除未支付缓存 -if (redisUtils.hasKey(noPayKey)) { - redisUtils.del(noPayKey); -} -// 删除评价缓存 -if (redisUtils.hasKey(evaluateKey)) { - redisUtils.del(evaluateKey); -} -``` - -## OrderServiceImpl - -### getNoTakeOrdersById -```java -IPage orderIPage = new Page<>(request.getCurrentPage(), request.getPageSize()); -QueryWrapper queryWrapper = new QueryWrapper<>(); -// 获取系统年月日 -// 比如5.30 -String day = DateUtil.getDay(); -// 比如20:00 -String hours = DateUtil.getHours(); -System.out.println("当前日期:" + day); -System.out.println("当前时间:" + hours); -queryWrapper - .eq("user_id", request.getUserId()) // 用户id - .eq("order_status", "1")// 1:已经支付 - .ge("sc.begin_date", day) // 比如订单日期大于等于今天 - .ge("sc.begin_time", hours) // 订单时间大于等于当前时间 - .orderByAsc("sc.begin_time") // 排序 - .orderByDesc("so.order_time"); // 排序 -``` - -### getEvaluateOrdersById -```java -IPage orderIPage = new Page<>(request.getCurrentPage(), request.getPageSize()); -QueryWrapper queryWrapper = new QueryWrapper<>(); -// 获取系统年月日 -String day = DateUtil.getDay(); -String hours = DateUtil.getHours(); -System.out.println("当前日期:" + day); -System.out.println("当前时间:" + hours); -queryWrapper - .eq("user_id", request.getUserId()) // 用户di - .eq("order_status", "1") // 状态为1 - // 两种情况: - // 1. 符合当天日期,并且订单场次发车时间小于当前日期 - // 2. 订单场次的发车日期小于当前日期 - .and(o -> o.eq("sc.begin_date", day) - .lt("sc.begin_time", hours) - .or().lt("sc.begin_date", day)) - .eq("evaluate_status", request.getEvaluateStatus()) // 评价状态 - .orderByDesc("sc.begin_time") // 排序 - .orderByDesc("so.order_time"); // 排序 -``` - -### getNoPayOrdersById -```java -IPage noPayDtoIPage = new Page<>(request.getCurrentPage(), request.getPageSize()); -QueryWrapper queryWrapper = new QueryWrapper<>(); -// 获取系统年月日 -String day = DateUtil.getDay(); -String hours = DateUtil.getHours(); -System.out.println("当前日期:" + day); -System.out.println("当前时间:" + hours); -queryWrapper - .eq("so.user_id", request.getUserId()) // 用户id - .eq("so.order_status", "0") // 未支付状态 - .ge("sc.begin_date", day) // 比如,订单场次日期大于当前日期 - .ge("sc.begin_time", hours)// 订单场次时间大于当前日期 - .orderByDesc("sc.begin_time") // 排序 - .orderByDesc("so.order_time"); // 未支付 -``` - -### addOrder -```java -public AddOrderResponse addOrder(AddOrderRequest request) { - // 判断座位,如果重复,直接退出,否则更新场次的座位信息 - AddOrderResponse response = new AddOrderResponse(); - // 全局orderId - Long orderId = UUIDUtils.flakesUUID(); - // 1。 判断座位,如果重复,直接退出,否则下一步 - // 2。 更新座位,如果没有异常,这是写操作 - // 3。 计算总金额,如果没有异常 - // 4。 添加订单,如果异常,这是写操作 - try { - // 1。 判断座位,如果重复,直接退出,否则下一步 - tag = MqTags.ORDER_SEATS_CANCEL.getTag(); - boolean repeatSeats = busService.repeatSeats(request.getSeatsIds(), request.getCountId()); - if (repeatSeats) { - // b:true 说明重复 - response.setCode(SbCode.SELECTED_SEATS.getCode()); - response.setMsg(SbCode.SELECTED_SEATS.getMessage()); - return response; - } -// CastException.cast(SbCode.SYSTEM_ERROR); - // 2。 更新座位,如果没有异常,这是写操作 - // 用tags来过滤消息 - tag = MqTags.ORDER_ADD_SEATS_CANCLE.getTag(); - boolean addSeats = busService.addSeats(request.getSeatsIds(), request.getCountId()); - if (!addSeats) { - response.setCode(SbCode.DB_EXCEPTION.getCode()); - response.setMsg(SbCode.DB_EXCEPTION.getMessage()); - return response; - } - // 模拟系统异常 -// CastException.cast(SbCode.SYSTEM_ERROR); - // 3。 计算总金额,如果没有异常 - tag = MqTags.ORDER_CALC_MONEY_CANCLE.getTag(); - String seatIds = request.getSeatsIds(); - Integer seatNumber = seatIds.split(",").length; - Double countPrice = request.getCountPrice(); - Double totalPrice = getTotalPrice(seatNumber, countPrice); - -// CastException.cast(SbCode.SYSTEM_ERROR); - // 4。 添加订单,如果异常,这是写操作 - Order order = orderConvertver.res2Order(request); - order.setOrderPrice(totalPrice); - order.setEvaluateStatus("0"); // 未评价 - order.setOrderStatus("0"); // 未支付 - order.setUuid(orderId); // 唯一id - tag = MqTags.ORDER_ADD_CANCLE.getTag(); - int insert = orderMapper.insert(order);// 插入 不判断了 -// CastException.cast(SbCode.SYSTEM_ERROR); - // 这里就不读了,耗时 -// QueryWrapper wrapper = new QueryWrapper<>(); -// wrapper.eq("so.uuid", order.getUuid()); -// OrderDto orderDto = orderMapper.selectOrderById(wrapper); - response.setCode(SbCode.SUCCESS.getCode()); - response.setMsg(SbCode.SUCCESS.getMessage()); - response.setOrderId(orderId); -// response.setOrderDto(orderDto); - // 这里放redis 未支付缓存,时间前端给定 - redisUtils.set(RedisConstants.ORDER_CANCLE_EXPIRE.getKey() + orderId, orderId, request.getExpireTime()); - return response; - } catch (Exception e) { - // 以上操作如果程序都不发生异常的话, 是不会执行这里的代码的 - // 也就是说不会发送回退消息的。 - // 目的是在高并发的情况下,程序内部发生异常,依然高可用 -// e.printStackTrace(); - log.error("订单业务发生异常"); - // 发消息,将座位退回,将订单退回 - MQDto mqDto = new MQDto(); - mqDto.setOrderId(orderId); - mqDto.setCountId(request.getCountId()); - mqDto.setSeatsIds(request.getSeatsIds()); - try { - String key = RedisConstants.ORDER_EXCEPTION_CANCLE_EXPIRE.getKey() + Convert.toStr(orderId); - sendCancelOrder(topic,tag, key, JSON.toJSONString(mqDto)); - log.warn("订单回退消息发送成功..." + mqDto); - } catch (Exception ex) { - ex.printStackTrace(); - } - response.setCode(SbCode.SYSTEM_ERROR.getCode()); - response.setMsg(SbCode.SYSTEM_ERROR.getMessage()); - return response; - } -} -``` - -### selectOrderById -> 省略 - -### updateOrderStatus -```java -public OrderResponse updateOrderStatus(OrderRequest request) { - OrderResponse response = new OrderResponse(); - try { - // 获取orderDto - QueryWrapper wrapper = new QueryWrapper<>(); - wrapper.eq("so.uuid", request.getUuid()); - OrderDto orderDto = orderMapper.selectOrderById(wrapper); - // 1, 检查状态是否为2 - if (request.getOrderStatus().equals("2")) { - // 说明关闭订单,回退座位 - busService.filterRepeatSeats(orderDto.getSeatsIds(), orderDto.getCountId()); - redisUtils.del(RedisConstants.COUNT_DETAIL_EXPIRE.getKey() - + orderDto.getCountId()); - // 清除场次详情的缓存 - } - if (request.getOrderStatus().equals("1")) { - // 说明已经支付,删掉5分钟的订单缓存 - redisUtils.del(RedisConstants.ORDER_CANCLE_EXPIRE.getKey() + request.getUuid()); - } - Order order = orderConvertver.res2Order(request); - // 更新状态 - orderMapper.updateById(order); - // 暂时就不获取了 - response.setCode(SbCode.SUCCESS.getCode()); - response.setMsg(SbCode.SUCCESS.getMessage()); - redisUtils.del(RedisConstants.NO_PAY_ORDERS_EXPIRE.getKey()+order.getUserId()); - redisUtils.del(RedisConstants.SELECT_ORDER_EXPIRE.getKey() + request.getUuid()); - } catch (Exception e) { - log.error("updateOrderStatus", e); - response.setCode(SbCode.DB_EXCEPTION.getCode()); - response.setMsg(SbCode.DB_EXCEPTION.getMessage()); - return response; - } - return response; -} -``` - -### deleteOrderById -> 省略 - -### sendCancelOrder -> 发送订单回退消息 - -后边会单独介绍消息队列 \ No newline at end of file diff --git "a/Java/codes/collection/ArrayList\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" "b/Java/codes/collection/ArrayList\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" deleted file mode 100644 index 7a8d9fa1..00000000 --- "a/Java/codes/collection/ArrayList\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" +++ /dev/null @@ -1,123 +0,0 @@ -# ArrayList -## 一些特点 - -- ArrayList实现了List接口,是顺序容器,即元素存放的数据与放进去的顺序相同,允许放入`null`元素,底层通过**数组**实现。 -- 每个ArrayList都有一个容量(capacity),表示**底层数组的实际大小**,容器内存储元素的个数不能多于当前容量。 -- 当向容器中添加元素时,如果容量不足,**容器会自动增大底层数组的大小**。 -- Java泛型只是编译器提供的语法糖,所以这里的数组是一个Object数组,以便能够容纳任何类型的对象。 - -## 底层数据结构 -```java -private static final int DEFAULT_CAPACITY = 10; // 默认容量 -transient Object[] elementData; // Object 数组 -private int size; // 大小 -``` - -## 构造函数 - -### 有参 -```java -public ArrayList(int initialCapacity) { - if (initialCapacity > 0) { - this.elementData = new Object[initialCapacity]; - } else if (initialCapacity == 0) { - this.elementData = EMPTY_ELEMENTDATA; // 默认10 - } else { - throw new IllegalArgumentException("Illegal Capacity: "+ - initialCapacity); - } -} -``` - -### 无参 -```java -public ArrayList() { - this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; // 默认10 -} -``` - -## 扩容 -常问,看一下源码吧 - -```java -public void ensureCapacity(int minCapacity) { - int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) - // any size if not default element table - ? 0 - // larger than default for default empty table. It's already - // supposed to be at default size. - : DEFAULT_CAPACITY; - - if (minCapacity > minExpand) { - ensureExplicitCapacity(minCapacity); - } -} -``` -继续看ensureExplicitCapacity -```java -private void ensureExplicitCapacity(int minCapacity) { - modCount++; - - // overflow-conscious code - if (minCapacity - elementData.length > 0) - grow(minCapacity); // 扩容最关键的方法 -} -``` -其实可以之和看grow(jdk1.8) - -```java - private void grow(int minCapacity) { - // overflow-conscious code - int oldCapacity = elementData.length; - int newCapacity = oldCapacity + (oldCapacity >> 1); // old + old * 2 就在这... - if (newCapacity - minCapacity < 0) - newCapacity = minCapacity; - if (newCapacity - MAX_ARRAY_SIZE > 0) - newCapacity = hugeCapacity(minCapacity); - // minCapacity is usually close to size, so this is a win: - elementData = Arrays.copyOf(elementData, newCapacity); -} -``` - -## add -还是看直接看源码方便一些 -```java -public boolean add(E e) { - // 扩容判断 - ensureCapacityInternal(size + 1); // Increments modCount!! 这个参数,起到并发异常作用。 - elementData[size++] = e; // 这一步非原子性,并发容易出错,好几种情况。 下次分析 - return true; -} -``` - -## get -```java -一般获取元素,第一步都要判断索引是否越界 -public E get(int index) { - rangeCheck(index); // 判断给定索引是否越界 - return elementData(index); -} -``` - -## 聊一下并发容易出现的问题 -### 首先是数组越界,举个例子 -1. 列表大小为9,即size=9 -2. 线程A开始进入add方法,这时它获取到size的值为9,调用ensureCapacityInternal方法进行容量判断。 -3. 线程B此时也进入add方法,它和获取的size的值也为9,也开始调用ensureCapacityInternal方法。 -4. 线程A发现需求大小为10,而elementData的大小就为10,可以容纳。于是它不再扩容,返回。 -5. 线程B也发现需要大小为10,也可以容纳,返回。 -6. 好了,**问题来了哈** -7. 线程A开始进行设置值操作,elementData[size++] = e操作。此时size变为10。 -8. 线程B也开始进行设置值操作,它尝试设置elementData[10] = e, 而elementData没有进行过扩容,它的下标最大为9,于是此时会报出一个数组越界的异常`ArrayIndexOutOfBoundsException`。 - -### null值,举个例子 -1. 列表大小为10,即size=0 -2. 线程A开始添加元素,值为A。此时它执行第一条操作,将A放在了elementData下标为0的位置上。也就是说,线程挂在了`element[0] = e`上。 -3. 接着线程B刚好也要开始天极爱一个值为B的元素,且走到了第一条的操作。此时线程B获取的size的值依然为0,于是它将B也放在了elementData下标为0的位置上。 -4. **问题来了**,其实上面也是问题,覆盖了。。。 -5. 线程A将size的值增加为1 -6. 线程B开始将size的值增加为2 -7. 当你获取1索引的时候,那不就是null了? - -## Fail-Fast机制 -ArrayList也采用了快速失败的机制,**通过记录modCount参数来实现**。在面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。 \ No newline at end of file diff --git "a/Java/codes/collection/BlockingQueue\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" "b/Java/codes/collection/BlockingQueue\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" deleted file mode 100644 index d8970013..00000000 --- "a/Java/codes/collection/BlockingQueue\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" +++ /dev/null @@ -1,158 +0,0 @@ -# BlockingQueue -看一下官方1.8的官方文档 -![已知实现类](https://imgkr.cn-bj.ufileos.com/1c08311d-77a7-4f60-a4dd-2e293f43c8d0.png) - -官方文档告诉我们它们有这些特点: -- BlockingQueue实现被设计为主要用于生产者 - 消费者队列 -- BlockingQueue实现是线程安全的。 所有排队方法使用内部锁或其他形式的并发控制在原子上实现其效果。 -- BlockingQueue方法有四种形式,具有不同的操作方式,不能立即满足,但可能在将来的某个时间点满足: - - 一个抛出异常 - - 返回一个特殊值( null或false ,具体取决于操作) - - 第三个程序将无限期地阻止当前线程,直到操作成功为止 - - 在放弃之前只有给定的最大时限。 - -## 看源码之旅 - -### ArrayBlockingQueue -> 看一下官方文档的解释->一个有限的blocking queue由数组支持。 这个队列排列元素FIFO(先进先出)。 队列的头部是队列中最长的元素。 队列的尾部是队列中最短时间的元素。 新元素插入队列的尾部,队列检索操作获取队列头部的元素。这是一个经典的“有界缓冲区”,其中固定大小的数组保存由生产者插入的元素并由消费者提取。 创建后,容量无法更改。 尝试put成满的队列的元件将导致在操作阻挡; 尝试take从空队列的元件将类似地阻塞。此类支持可选的公平策略,用于订购等待的生产者和消费者线程。 默认情况下,此订单不能保证。 然而,以公平设置为true的队列以FIFO顺序授予线程访问权限。 公平性通常会降低吞吐量,但会降低变异性并避免饥饿。 - -#### 常见变量 -```java -int count; // 队列元素数量 -final ReentrantLock lock; // 内部线程安全性用了ReentrantLock -private final Condition notEmpty; // takes方法的等待条件 -private final Condition notFull; // puts方法的等待条件 -``` -#### 构造方法 -```java -public ArrayBlockingQueue(int capacity, boolean fair) { - if (capacity <= 0) - throw new IllegalArgumentException(); // 这里就不说了 - this.items = new Object[capacity]; // 初始容量 - lock = new ReentrantLock(fair); // fair参数决定是否公平锁 - notEmpty = lock.newCondition(); // 上面已提到 - notFull = lock.newCondition(); // 上面已提到 -} -``` -#### add -```java -public boolean add(E e) { - return super.add(e); // 该方法添加失败抛出异常 -} -// AbstractQueue -public boolean add(E e) { - if (offer(e)) - return true; - else - throw new IllegalStateException("Queue full"); // 在这 -} -``` -**remove就不分析了** - -#### offer -```java -public boolean offer(E e) { - checkNotNull(e); - final ReentrantLock lock = this.lock; // 上面的add其实内部也调用了offer,当时我还觉得奇怪,add没上锁?。 原来offer上了锁的 - lock.lock(); - try { - if (count == items.length) - return false; // 满了,就false - else { - enqueue(e); // 否则,添加即可 - return true; // 返回true,并不会抛出异常 - } - } finally { - lock.unlock(); - } -} -``` -**poll不分析了** - -#### put -```java -public void put(E e) throws InterruptedException { - checkNotNull(e); // 检查是否为空,为空就抛出空异常 - final ReentrantLock lock = this.lock; // 上锁哦 - lock.lockInterruptibly(); // 锁中断 - try { - while (count == items.length) // 满了,挂起阻塞 - notFull.await(); - enqueue(e); // 否则添加 - } finally { - lock.unlock(); - } -} -``` - -#### take -```java -public E take() throws InterruptedException { - final ReentrantLock lock = this.lock; // 上锁 - lock.lockInterruptibly(); // 锁中断 - try { - while (count == 0) - notEmpty.await(); // 没有元素,挂起 - return dequeue(); - } finally { - lock.unlock(); - } -} -``` - -#### 带时间的offer和poll -> 其实就是用了`long nanos = unit.toNanos(timeout);` - - -### LinkedBlockingQueue -> 四个方法和ArrayBlockingQueue的差不多,就不多分析了。它的特点之一在于,如果不指定容量,那么默认是等于Integer.MAX_VALUE。可以看一下源码 - -#### 常见的参数 -```java -/** The capacity bound, or Integer.MAX_VALUE if none */ -private final int capacity; // 一看注释,就晓得了 -// 还有一些常见的和上面的差不多 -``` - -#### 构造方法 -```java -public LinkedBlockingQueue() { - this(Integer.MAX_VALUE); // 在这里, 嘿嘿嘿。 -} -``` - -### LinkedTransferQueue -> 基于链接节点的无界TransferQueue 。 这个队列相对于任何给定的生产者订购元素FIFO(先进先出)。 队列的头部是那些已经排队的元素是一些生产者的最长时间。 队列的尾部是那些已经在队列上的元素是一些生产者的最短时间。 - -**四个方法就暂时不提了,大部分操作用了cas并且要关注transfer方法** - -先介绍几个标志参数: -```java -private static final int NOW = 0; // for untimed poll, tryTransfer -private static final int ASYNC = 1; // for offer, put, add -private static final int SYNC = 2; // for transfer, take -private static final int TIMED = 3; // for timed poll, tryTransfer -``` - -```java -// 如果可能,立即将元素转移到等待的消费者。 -public boolean tryTransfer(E e) { - return xfer(e, true, NOW, 0) == null; // xfer的now参数 -} -// 还有一个重载参数的,带时间的。 -``` - -```java -// 将元素传输到消费者,必要时等待。 -public void transfer(E e) throws InterruptedException { - if (xfer(e, true, SYNC, 0) != null) { // SYNC - Thread.interrupted(); // failure possible only due to interrupt - throw new InterruptedException(); - } -} -``` -### PriorityBlockingQueue -> 一看名字就是优先级阻塞队列,它的优先级是由堆实现的,所以该类中有很多堆的方法。源码暂时就不看了,很多都是差不多的。 - -### SynchronousQueue -> 官方文档:每个插入操作必须等待另一个线程相应的删除操作,反之亦然。 同步队列没有任何内部容量,甚至没有一个容量。 个人感觉是生产者生产一个元素,消费者必须消费,生产者才能继续生产。内部也维护了一个TransferQueue,其中部分操作是利用cas。源码就不贴了。有兴趣的可以进去看看哦。 diff --git "a/Java/codes/collection/ConcurrentHashMap\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" "b/Java/codes/collection/ConcurrentHashMap\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" deleted file mode 100644 index 608032e8..00000000 --- "a/Java/codes/collection/ConcurrentHashMap\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" +++ /dev/null @@ -1,238 +0,0 @@ -# ConcurrentHashMap -> 1.7和1.8的源码还不太一样,也就是说加锁的方式不一样。 - -## 1.7 -> 分段锁 - -![图示意图](https://i.loli.net/2019/05/08/5cd1d2c5ce95c.jpg) - -### Segment的数据结构 -```java -/** - * Segment 数组,存放数据时首先需要定位到具体的 Segment 中。 - */ -final Segment[] segments; -transient Set keySet; -transient Set> entrySet; -``` - -Segment 是 ConcurrentHashMap 的一个内部类,主要的组成如下: - -```java -static final class Segment extends ReentrantLock implements Serializable { - private static final long serialVersionUID = 2249069246763182397L; - - // 和 HashMap 中的 HashEntry 作用一样,真正存放数据的桶 - transient volatile HashEntry[] table; // volatile修饰 - transient int count; - transient int modCount; - transient int threshold; - final float loadFactor; - -} -``` -- 唯一的区别就是其中的核心数据如 value ,以及链表都是 volatile 修饰的,保证了获取时的可见性。 -- ConcurrentHashMap 采用了分段锁技术,其中 Segment 继承于 `ReentrantLock`。 -- 不会像HashTable那样不管是put还是get操作都需要做同步处理,理论上 ConcurrentHashMap 支持 CurrencyLevel (Segment 数组数量)的线程并发。 -- **每当一个线程占用锁访问一个 Segment 时,不会影响到其他的 Segment。** - -### put -```java -public V put(K key, V value) { - Segment s; - if (value == null) - throw new NullPointerException(); - int hash = hash(key); - int j = (hash >>> segmentShift) & segmentMask; - if ((s = (Segment)UNSAFE.getObject // nonvolatile; recheck - (segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment - s = ensureSegment(j); - return s.put(key, hash, value, false); -} -``` -通过key定位到Segment,之后在对应的Segment中进行具体的put -```java -final V put(K key, int hash, V value, boolean onlyIfAbsent) { - HashEntry node = tryLock() ? null : - scanAndLockForPut(key, hash, value); // 1. 加锁处理 - V oldValue; - try { - HashEntry[] tab = table; - int index = (tab.length - 1) & hash; - HashEntry first = entryAt(tab, index); - for (HashEntry e = first;;) { - if (e != null) { - K k; - if ((k = e.key) == key || // 2. 遍历该HashEntry,如果不为空则判断传入的key和当前遍历的key是否相等,相等则覆盖旧的value - (e.hash == hash && key.equals(k))) { - oldValue = e.value; - if (!onlyIfAbsent) { - e.value = value; - ++modCount; - } - break; - } - e = e.next; - } - else { // 3. 不为空则需要新建一个HashEntry并加入到Segment中,同时会先判断是否需要扩容 - if (node != null) - node.setNext(first); - else - node = new HashEntry(hash, key, value, first); - int c = count + 1; - if (c > threshold && tab.length < MAXIMUM_CAPACITY) - rehash(node); - else - setEntryAt(tab, index, node); - ++modCount; - count = c; - oldValue = null; - break; - } - } - } finally { - unlock(); // 4. 解锁 - } - return oldValue; -} -``` -- 虽然HashEntry中的value是用volatile关键字修饰的,但是并不能保证并发的原子性,所以put操作仍然需要加锁处理。 -- 首先第一步的时候会尝试获取锁,如果获取失败肯定就是其他线程存在竞争,则利用 `scanAndLockForPut()` 自旋获取锁。 - - 尝试获取自旋锁 - - 如果重试的次数达到了`MAX_SCAN_RETRIES` 则改为阻塞锁获取,保证能获取成功。 - -总的来说: -- 将当前的Segment中的table通过key的hashcode定位到HashEntry -- 遍历该HashEntry,如果不为空则判断传入的key和当前遍历的key是否相等,相等则覆盖旧的value -- 不为空则需要新建一个HashEntry并加入到Segment中,同时会先判断是否需要扩容 -- 最后会解除在1中所获取当前Segment的锁。 - -### get -```java -public V get(Object key) { - Segment s; // manually integrate access methods to reduce overhead - HashEntry[] tab; - int h = hash(key); - long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE; - if ((s = (Segment)UNSAFE.getObjectVolatile(segments, u)) != null && - (tab = s.table) != null) { - for (HashEntry e = (HashEntry) UNSAFE.getObjectVolatile - (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE); - e != null; e = e.next) { - K k; - if ((k = e.key) == key || (e.hash == h && key.equals(k))) - return e.value; - } - } - return null; -} -``` -- 只需要将 Key 通过 Hash 之后定位到具体的 Segment ,再通过一次 Hash 定位到具体的元素上。 -- 由于 HashEntry 中的 value 属性是用 volatile 关键词修饰的,保证了内存可见性,所以每次获取时都是最新值。 -- ConcurrentHashMap 的 get 方法是非常高效的,**因为整个过程都不需要加锁。** - -## 1.8 -> 1.7 查询遍历链表效率太低。其中抛弃了原有的 Segment 分段锁,而采用了 `CAS + synchronized` 来保证并发安全性 - -### put -```java - final V putVal(K key, V value, boolean onlyIfAbsent) { - if (key == null || value == null) throw new NullPointerException(); - int hash = spread(key.hashCode()); - int binCount = 0; - for (Node[] tab = table;;) { // 1. 根据key计算出hashcode - Node f; int n, i, fh; - if (tab == null || (n = tab.length) == 0) // 2. 判断是否需要进行初始化 - tab = initTable(); - else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { // 3. f即为当前key定位出的Node,如果为空表示当前位置可以写入数据,利用CAS尝试写入,失败则自旋保证成功。 - if (casTabAt(tab, i, null, - new Node(hash, key, value, null))) - break; // no lock when adding to empty bin - } - else if ((fh = f.hash) == MOVED) // 4. 如果当前位置的`hashcode == MOVED == -1`,则需要进行扩容 - tab = helpTransfer(tab, f); - else { - V oldVal = null; - synchronized (f) { // 5. 如果都不满足,则利用synchronized锁写入数据 - if (tabAt(tab, i) == f) { - if (fh >= 0) { - binCount = 1; - for (Node e = f;; ++binCount) { - K ek; - if (e.hash == hash && - ((ek = e.key) == key || - (ek != null && key.equals(ek)))) { - oldVal = e.val; - if (!onlyIfAbsent) - e.val = value; - break; - } - Node pred = e; - if ((e = e.next) == null) { - pred.next = new Node(hash, key, - value, null); - break; - } - } - } - else if (f instanceof TreeBin) { - Node p; - binCount = 2; - if ((p = ((TreeBin)f).putTreeVal(hash, key, - value)) != null) { - oldVal = p.val; - if (!onlyIfAbsent) - p.val = value; - } - } - } - } - if (binCount != 0) { - if (binCount >= TREEIFY_THRESHOLD) // 6. 如果数量大于`TREEIFY_THRESHOLD` 则要转换为红黑树。 - treeifyBin(tab, i); - if (oldVal != null) - return oldVal; - break; - } - } - } - addCount(1L, binCount); - return null; - } - -``` -- 根据key计算出hashcode -- 判断是否需要进行初始化 -- f即为当前key定位出的Node,如果为空表示当前位置可以写入数据,**利用CAS尝试写入,失败则自旋保证成功。** -- 如果当前位置的`hashcode == MOVED == -1`,则需要进行扩容 -- 如果都不满足,则利用`synchronized`锁写入数据 -- 如果数量大于TREEIFY_THRESHOLD 则要转换为红黑树。 - -### get -```java - public V get(Object key) { - Node[] tab; Node e, p; int n, eh; K ek; - int h = spread(key.hashCode()); - if ((tab = table) != null && (n = tab.length) > 0 && - (e = tabAt(tab, (n - 1) & h)) != null) { - if ((eh = e.hash) == h) { - if ((ek = e.key) == key || (ek != null && key.equals(ek))) - return e.val; - } - else if (eh < 0) - return (p = e.find(h, key)) != null ? p.val : null; - while ((e = e.next) != null) { - if (e.hash == h && - ((ek = e.key) == key || (ek != null && key.equals(ek)))) - return e.val; - } - } - return null; - } -``` -- 根据计算出来的 hashcode 寻址,如果就在桶上那么直接返回值。 -- 如果是红黑树那就按照树的方式获取值。 -- 就不满足那就按照链表的方式遍历获取值。 - -## 总结 -1.8 在 1.7 的数据结构上做了大的改动,采用红黑树之后可以保证查询效率(O(logn)),甚至取消了 ReentrantLock 改为了 synchronized,这样可以看出在新版的 JDK 中对 synchronized 优化是很到位的。 \ No newline at end of file diff --git "a/Java/codes/collection/HashMap\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" "b/Java/codes/collection/HashMap\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" deleted file mode 100644 index a1905e14..00000000 --- "a/Java/codes/collection/HashMap\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" +++ /dev/null @@ -1,173 +0,0 @@ -# HashMap -> 谈到这个,真的是面试高频呀,处处问,关键HashMap在jdk1.7和1.8的源码是我不一样的我这边就只放1.8的源码。 - -## 底层结构 - -> HashMap的底层结构是是**数组+链表**。关于为什么是链表,那是因为哈希冲突,采取链表一种方式。 - -## 常见参数 -```java -static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 初始容量 -static final float DEFAULT_LOAD_FACTOR = 0.75f; // 默认负载因子0.75 -// 给定的默认容量为16,负载因子为0.75. -// Map在使用过程中不断的往里面存放数据,当数量达到了16 * 0.75 = 12就需要将当前16的容量进行扩容, -// 而扩容这个过程涉及到rehash(重新哈希)、复制数据等操作,所有非常消耗性能。 -static final int TREEIFY_THRESHOLD = 8;// 成为红黑树的阈值,为什么是8? -static final int UNTREEIFY_THRESHOLD = 6; // 红黑树转链表阈值6 -static final int MIN_TREEIFY_CAPACITY = 64; -// 个人感觉,8是经验值,或者链表为8的时候,性能开始下降,大家知道链表查找慢,当元素过多的时候,非常影响性能的。 -// 因此采取为8的时候转成红黑树。元素过少,其实没必要直接红黑树,元素过少,链表查找的时间也不是很能影响性能。 -// 在 hash 函数设计合理的情况下,发生 hash 碰撞 8 次的几率为百万分之 6,概率说话。 -// 6是因为如果 hash 碰撞次数在 8 附近徘徊,会一直发生链表和红黑树的转化,为了预防这种情况的发生。 -``` - -## hash -```java -static final int hash(Object key) { - int h; - return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); -} -``` -hash 函数是先拿到通过 key 的 hashcode,是 32 位的 int 值,然后让 hashcode 的高 16 位和低 16 位进行异或操作。这个也叫扰动函数,这么设计有二点原因: -- 一定要尽可能降低 hash 碰撞,越分散越好; -- 算法一定要尽可能高效,因为这是高频操作, 因此采用位运算; - -## put -> 这里介绍1.8的,1.7的其实很简单,被1.8包括了 -```java -final V putVal(int hash, K key, V value, boolean onlyIfAbsent, - boolean evict) { - Node[] tab; Node p; int n, i; - if ((tab = table) == null || (n = tab.length) == 0) // 1. 判断当前桶是否为空,空的就需要初始化(resize中会判断是否进行初始化) - n = (tab = resize()).length; - if ((p = tab[i = (n - 1) & hash]) == null) // 2. 根据当前key的hashcode定位到具体的桶中并判断是否为空,为空则表明没有Hash冲突,就直接在当前位置创建一个新桶 - tab[i] = newNode(hash, key, value, null); - else { - Node e; K k; - if (p.hash == hash && // 3. 如果当前桶有值(Hash冲突),那么就要比较当前桶中的key、key的hashcode与写入的key是否相等,相等就赋值给e,在第8步的时候会统一进行赋值及返回 - ((k = p.key) == key || (key != null && key.equals(k)))) - e = p; - else if (p instanceof TreeNode) // 4. 如果当前桶为红黑树,那就要按照红黑树的方式写入数据 - e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value); - else { // 5. 如果是个链表,就需要将当前的key、value封装称一个新节点写入到当前桶的后面形成链表。 - for (int binCount = 0; ; ++binCount) { - if ((e = p.next) == null) { - p.next = newNode(hash, key, value, null); - if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st // 6. 接着判断当前链表的大小是否大于预设的阈值,大于就要转换成为红黑树 -- - treeifyBin(tab, hash); - break; - } - if (e.hash == hash && // 7. 如果在遍历过程中找到key相同时直接退出遍历。 - ((k = e.key) == key || (key != null && key.equals(k)))) - break; - p = e; - } - } - if (e != null) { // existing mapping for key 8. 如果`e != null`就相当于存在相同的key,那就需要将值覆盖。 - V oldValue = e.value; - if (!onlyIfAbsent || oldValue == null) - e.value = value; - afterNodeAccess(e); - return oldValue; - } - } - ++modCount; - if (++size > threshold) // 9. 最后判断是否需要进行扩容。 - resize(); - afterNodeInsertion(evict); - return null; -} -``` -看代码有点晕乎! - -![put过程](https://imgkr.cn-bj.ufileos.com/b2206341-08ce-4e67-87bd-3a467ccac31e.png) - -1. 判断数组是否为空,为空进行初始化; -2. 不为空,计算 k 的 hash 值,通过(n - 1) & hash计算应当存放在数组中的下标 index; -3. 查看 table[index] 是否存在数据,没有数据就构造一个 Node 节点存放在 table[index] 中; - -4. 存在数据,说明发生了 hash 冲突(存在二个节点 key 的 hash 值一样), 继续判断 key 是否相等,相等,用新的 value 替换原数据(onlyIfAbsent 为 false); - -5. 如果不相等,判断当前节点类型是不是树型节点,如果是树型节点,创造树型节点插入红黑树中; -6. 如果不是树型节点,创建普通 Node 加入链表中;判断链表长度是否大于 8, 大于的话链表转换为红黑树; - -7. 插入完成之后判断当前节点数是否大于阈值,如果大于开始扩容为原数组的二倍。 - -## get -```java -public V get(Object key) { - Node e; - return (e = getNode(hash(key), key)) == null ? null : e.value; -} -final Node getNode(int hash, Object key) { - Node[] tab; Node first, e; int n; K k; - if ((tab = table) != null && (n = tab.length) > 0 && - (first = tab[(n - 1) & hash]) != null) { - if (first.hash == hash && // always check first node - ((k = first.key) == key || (key != null && key.equals(k)))) - return first; - if ((e = first.next) != null) { - if (first instanceof TreeNode) - return ((TreeNode)first).getTreeNode(hash, key); - do { - if (e.hash == hash && - ((k = e.key) == key || (key != null && key.equals(k)))) - return e; - } while ((e = e.next) != null); - } - } - return null; -} -``` -- 首先将key hash之后取得所定位的桶 -- 如果桶为空,则直接返回null -- 否则判断桶的第一个位置(有可能是链表、红黑树)的key是否为查询的key,是就直接返回value -- 如果第一个不匹配,则判断它的下一个是红黑树还是链表 -- 红黑树就按照树的查找方式返回值 -- 不然就按照链表的方式遍历匹配返回值 - -## 1.8的优化 -1. 数组+链表改成了数组+链表或红黑树; -2. 链表的插入方式从头插法改成了尾插法,简单说就是插入时,如果数组位置上已经有元素,1.7 将新元素放到数组中,原始节点作为新节点的后继节点,1.8 遍历链表,将元素放置到链表的最后; -3. 扩容的时候 1.7 需要对原数组中的元素进行重新 hash 定位在新数组的位置,1.8 采用更简单的判断逻辑,位置不变或索引+旧容量大小; -4. 在插入时,1.7 先判断是否需要扩容,再插入,1.8 先进行插入,插入完成再判断是否需要扩容; - -## 并发问题 -- HashMap扩容的时候会调用resize()方法,就是这里的并发操作容易在一个桶上形成环形链表 -- 这样当获取一个不存在的key时,计算出的index正好是环形链表的下标就会出现死循环。 -- **但是1.7的头插法造成的问题,1.8改变了插入顺序,就解决了这个问题,但是为了内存可见性等安全性,还是需要ConCurrentHashMap** - -### Java 中有 HashTable、Collections.synchronizedMap、以及 ConcurrentHashMap 可以实现线程安全的 Map。 -- HashTable 是直接在操作方法上加 synchronized 关键字,锁住整个数组,粒度比较大 -- Collections.synchronizedMap 是使用 Collections 集合工具的内部类,通过传入 Map 封装出一个 SynchronizedMap 对象,内部定义了一个对象锁,方法内通过对象锁实现 -- ConcurrentHashMap 使用分段锁,降低了锁粒度,让并发度大大提高。(jdk1.8 CAS+ synchronized) - -[死循环分析](https://zhuanlan.zhihu.com/p/67915754) - -## 哈希遍历 - -```java -Iterator> entryIterator = map.entrySet().iterator(); - while (entryIterator.hasNext()) { - Map.Entry next = entryIterator.next(); - System.out.println("key=" + next.getKey() + " value=" + next.getValue()); - } - -Iterator iterator = map.keySet().iterator(); - while (iterator.hasNext()){ - String key = iterator.next(); - System.out.println("key=" + key + " value=" + map.get(key)); - } -``` -- 建议使用第一种,同时可以把key value取出。 -- 第二种还需要通过key取一次key,效率较低。 - -# HashSet -> HashSet中不允许有重复元素,这是因为HashSet是基于HashMap实现的,HashSet中的元素都存放在HashMap的key上面,而value中的值都是统一的一个`private static final Object PRESENT = new Object();`。 HashSet跟HashMap一样,都是一个存放链表的数组。 - -# TreeMap -> TreeMap 是按照 Key 的自然顺序或者 Comprator 的顺序进行排序,内部是通过红黑树来实现。所以要么 key 所属的类实现 Comparable 接口,或者自定义一个实现了 Comparator 接口的比较器,传给 TreeMap 用户 key 的比较。 - -# LinkedHashMap -> LinkedHashMap 内部维护了一个单链表,有头尾节点,同时 LinkedHashMap 节点 Entry 内部除了继承 HashMap 的 Node 属性,还有 before 和 after 用于标识前置节点和后置节点。可以实现按插入的顺序或访问顺序排序。 \ No newline at end of file diff --git "a/Java/codes/collection/LinkedList\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" "b/Java/codes/collection/LinkedList\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" deleted file mode 100644 index 4a9ae73a..00000000 --- "a/Java/codes/collection/LinkedList\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" +++ /dev/null @@ -1,109 +0,0 @@ -# LinkedList -> LinkedList同时实现了List接口和Deque接口,也就是说它既可以看作一个顺序容器,又可以看作一个队列(Queue),同时又可以看作一个栈(Stack)。 关于栈或队列,现在的首选是ArrayDeque,它有着比LinkedList(当作栈或队列使用时)有着更好的性能。 关于特点,想必大家都知道了。 - -## 底层数据 -> LinkedList底层通过双向链表实现。双向链表的每个节点用内部类Node表示。LinkedList通过**first和last引用分别指向链表的第一个和最后一个元素**。注意这里没有所谓的哑元,当链表为空的时候**first和last都指向null**。 -```java -transient int size = 0; -transient Node first; // 经常用 -transient Node last; // 经常用 -// Node是私有的内部类 -private static class Node { - E item; - Node next; // 这里就不多说了 - Node prev; // - - Node(Node prev, E element, Node next) { - this.item = element; - this.next = next; - this.prev = prev; - } -} -``` -构造函数就不用看了... - -## getFirst和getLast -> 这个挺简单的 -```java -public E getFirst() { - final Node f = first; // 第一个节点 - if (f == null) - throw new NoSuchElementException(); - return f.item; -} -public E getLast() { - final Node l = last; // 最后一个节点 - if (l == null) - throw new NoSuchElementException(); - return l.item; -} -``` - -**removeFirst和removeLast**和上面的源码差不多,挺简单。链表的删除,脑子里画图思考思考就明白了,添加也是一样。 - -## add调用的是`linkLast` -```java -void linkLast(E e) { - final Node l = last; // 要在尾部添加,所以要找last - final Node newNode = new Node<>(l, e, null); // last->e->null - last = newNode; - if (l == null) - first = newNode; // 如果l为空,说明没有链表没有元素,那么first和last都指向e, - else - l.next = newNode; // 否则就将l.next指向新节点 - size++; - modCount++; // 依然存在并发危险哦 -} -``` - -## 看一下remove -```java -public boolean remove(Object o) { - if (o == null) { // 将链表存在null的给移除掉 - for (Node x = first; x != null; x = x.next) { - if (x.item == null) { - unlink(x); - return true; - } - } - } else { - for (Node x = first; x != null; x = x.next) { - if (o.equals(x.item)) { // 否则直接遍历与符合当前对象,unlink即可。 - unlink(x); - return true; - } - } - } - return false; -} -``` -## unlink得看 -> 这个脑子思考一下那个图,其实就明白,很源码的话,也没问题的。 -```java -E unlink(Node x) { - // assert x != null; - final E element = x.item; // 当前值 - final Node next = x.next; // 后 - final Node prev = x.prev; // 前 - - if (prev == null) { // 如果x的前是null,说明,x是首节点,直接让first指向next - first = next; - } else { // x不是首节点,意味着pre是有值哦。 - prev.next = next; // prev.next的箭头指向x的next - x.prev = null; // 将x.prev指向null 双向链表? - } - - if (next == null) { // 同上,说明x是最后节点了 - last = prev; // 让last指向x的prev - } else { - next.prev = prev; // 否则x不是最后节点, 让next.prev 指向prev - x.next = null; // x.next 指向null , 双向链表 - } - - x.item = null; // GC回收 - size--; - modCount++; - return element; -} -``` -**其他几个方法的源码中核心都有这些...** \ No newline at end of file diff --git "a/Java/codes/collection/\346\211\213\345\206\231LRU\347\256\227\346\263\225.md" "b/Java/codes/collection/\346\211\213\345\206\231LRU\347\256\227\346\263\225.md" deleted file mode 100644 index 8995e5ea..00000000 --- "a/Java/codes/collection/\346\211\213\345\206\231LRU\347\256\227\346\263\225.md" +++ /dev/null @@ -1,73 +0,0 @@ -# LRU算法 -> LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。 - -## 一般过程 -![过程图](https://pic4.zhimg.com/80/v2-71b21233c615b1ce899cd4bd3122cbab_1440w.jpg) - -- 新数据插入到链表头部; -- 每当缓存命中(即缓存数据被访问),则将数据移到链表头部; -- 当链表满的时候,将链表尾部的数据丢弃。 - -再来来个具体的过程 -![具体过程](https://pic3.zhimg.com/80/v2-998b52e7534278b364e439bbeaf61d5e_1440w.jpg) - -1. 最开始时,内存空间是空的,因此依次进入A、B、C是没有问题的 -2. 当加入D时,就出现了问题,内存空间不够了,因此根据LRU算法,内存空间中A待的时间最为久远,选择A,将其淘汰 -3. 当再次引用B时,内存空间中的B又处于活跃状态,而C则变成了内存空间中,近段时间最久未使用的 -4. 当再次向内存空间加入E时,这时内存空间又不足了,选择在内存空间中待的最久的C将其淘汰出内存,这时的内存空间存放的对象就是E->B->D - -## 伪代码实现 - -### 思路 -> 双向链表+HashMap,Java中的LinkedHashMap就实现了该算法。 - -### 定义节点 -```java -class Node { - int key; - int value; - Node pre; - Node next; -} -``` -### 定义HashMap -`HashMap map = new HashMap<>();` - -### get -> 其实挺简单的。 -```java -public int get(int key) { - if (map.containsKey(key)) { - Node n = map.get(key); // 获取内存中存在的值,比如A - remove(n); //使用链表的方法,移除该节点 - setHead(n); //依然使用链表的方法,将该节点放入头部 - return n.value; - } - return -1; -} -``` -- 由于当一个节点通过key来访问到这个节点,那么这个节点就是刚被访问了, -- 就把这个节点删除掉,然后放到队列头,这样队列的头部都是最近访问的, -- 队列尾部是最近没有被访问的。 - -### set -```java -public void set(int key, int value) { - if (map.containsKey(key)) { - Node old = map.get(key); - old.value = value; - remove(old); // 移除旧节点 - setHead(old); // 放到队头 - } else { - Node created = new Node(key, value); - if (map.size() >= capacity) { - map.remove(end.key); // clear该key - remove(end); //链表也是依次 - setHead(created); // 将created放入队头 - } else { - setHead(created); // 如果没满,直接放入队头 - } - map.put(key,created); - } -} -``` \ No newline at end of file diff --git "a/Java/codes/collection/\351\233\206\345\220\210\346\200\273\350\247\210.md" "b/Java/codes/collection/\351\233\206\345\220\210\346\200\273\350\247\210.md" deleted file mode 100644 index b0b28954..00000000 --- "a/Java/codes/collection/\351\233\206\345\220\210\346\200\273\350\247\210.md" +++ /dev/null @@ -1,15 +0,0 @@ -# 集合总述 -通过查看JDK8的Collection的文档源码: - -![有点凶](https://imgkr.cn-bj.ufileos.com/98753cd6-dba3-4291-83c3-7964b39c5da3.png) - -再看一Map的: - - -![也挺凶](https://imgkr.cn-bj.ufileos.com/0af46521-0eb6-4666-898e-3536cc3b7c4e.png) - -不说了,来个更清晰一点的: - -![清晰uml图](https://www.pdai.tech/_images/java_collections_overview.png) - -因此,容器主要包括 Collection 和 Map 两种,Collection 存储着对象的集合,而 Map 存储着键值对(两个对象)的映射表。关于各自有什么特点,待后边带着源码详细介绍先。 \ No newline at end of file diff --git "a/Java/codes/jvm/\345\240\206\346\272\242\345\207\272\347\232\204\345\260\217\344\276\213\345\255\220.md" "b/Java/codes/jvm/\345\240\206\346\272\242\345\207\272\347\232\204\345\260\217\344\276\213\345\255\220.md" deleted file mode 100644 index 568af31e..00000000 --- "a/Java/codes/jvm/\345\240\206\346\272\242\345\207\272\347\232\204\345\260\217\344\276\213\345\255\220.md" +++ /dev/null @@ -1,26 +0,0 @@ -# 堆溢出例子 -> 启动参数:` -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError` - -```java -public class Test { - public static void main(String[] args) { - List list = new ArrayList<>(); - int i = 0; - while (true) { - list.add(new byte[5*1024*1024]); - System.out.println("分配次数:" + (++i)); - } - } -} -``` -结果: -``` -分配次数:1 -分配次数:2 -分配次数:3 -java.lang.OutOfMemoryError: Java heap space -Dumping heap to java_pid39413.hprof ... -Heap dump file created [16921133 bytes in 0.013 secs] -Exception in thread "main" java.lang.OutOfMemoryError: Java heap space - at Test.Test.main(Test.java:18) -``` \ No newline at end of file diff --git "a/Java/codes/jvm/\345\276\252\347\216\257\345\274\225\347\224\250\347\232\204\345\260\217\344\276\213\345\255\220.md" "b/Java/codes/jvm/\345\276\252\347\216\257\345\274\225\347\224\250\347\232\204\345\260\217\344\276\213\345\255\220.md" deleted file mode 100644 index 90dbf83d..00000000 --- "a/Java/codes/jvm/\345\276\252\347\216\257\345\274\225\347\224\250\347\232\204\345\260\217\344\276\213\345\255\220.md" +++ /dev/null @@ -1,14 +0,0 @@ -# 引用计数循环引用小例子 - -```java -public class Test { - public static void main(String[] args) { - Object object1 = new Object(); - Object object2 = new Object(); - object1 = object2; // obj1 引用obj2 - object2 = object1; // obj2 引用obj1 循环引用 - object1 = null; - object2 = null; - } -} -``` \ No newline at end of file diff --git "a/Java/codes/jvm/\346\237\245\347\234\213\345\217\215\347\274\226\350\257\221\346\272\220\347\240\201.md" "b/Java/codes/jvm/\346\237\245\347\234\213\345\217\215\347\274\226\350\257\221\346\272\220\347\240\201.md" deleted file mode 100644 index 10e93658..00000000 --- "a/Java/codes/jvm/\346\237\245\347\234\213\345\217\215\347\274\226\350\257\221\346\272\220\347\240\201.md" +++ /dev/null @@ -1,68 +0,0 @@ -# 查看反编译源码 - -## 例子 -JavaBean.java -```java -public class JavaBean { - int value = 3; - final int value2 = 5; - static int value3 = 8; - static int value4 = -1; - String s = "买"; - - void getValue() { - int temp = 4; - } -} -``` -- 终端输入`javac JavaBean.java`得到JavaBean.class文件 -- 终端继续输入`javap -c JavaBean > JavaBean.txt` - -``` -Compiled from "JavaBean.java" -public class JavaBean { - int value; - - final int value2; - - static int value3; - - static int value4; - - java.lang.String s; - - public JavaBean(); - Code: - 0: aload_0 - 1: invokespecial #1 // Method java/lang/Object."":()V - 4: aload_0 - 5: iconst_3 - 6: putfield #2 // Field value:I - 9: aload_0 - 10: iconst_5 - 11: putfield #3 // Field value2:I - 14: aload_0 - 15: ldc #4 // String 买 - 17: putfield #5 // Field s:Ljava/lang/String; - 20: return - - void getValue(); - Code: - 0: iconst_4 - 1: istore_1 - 2: return - - static {}; - Code: - 0: bipush 8 - 2: putstatic #6 // Field value3:I - 5: iconst_m1 - 6: putstatic #7 // Field value4:I - 9: return -} -``` -暂时就不详细解释了。 - -当然如果查看更信息反编译文件,终端可以输入`javap -p JavaBean > JavaBean.txt` -内容我就不放了,它可以查看更多的字面量等,内容比较多。 - diff --git "a/Java/codes/jvm/\346\240\210\346\272\242\345\207\272\347\232\204\345\260\217\344\276\213\345\255\220.md" "b/Java/codes/jvm/\346\240\210\346\272\242\345\207\272\347\232\204\345\260\217\344\276\213\345\255\220.md" deleted file mode 100644 index 829e98fa..00000000 --- "a/Java/codes/jvm/\346\240\210\346\272\242\345\207\272\347\232\204\345\260\217\344\276\213\345\255\220.md" +++ /dev/null @@ -1,15 +0,0 @@ -# 模拟栈溢出的小例子 -> 启动参数: `-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError` -```java -public class Test { - - public void m() { - System.out.println("stack test overflow..."); - m(); - } - - public static void main(String[] args) { - new T1().m(); - } -} -``` \ No newline at end of file diff --git "a/Java/codes/modes/\344\273\243\347\220\206\346\250\241\345\274\217.md" "b/Java/codes/modes/\344\273\243\347\220\206\346\250\241\345\274\217.md" deleted file mode 100644 index afaa720e..00000000 --- "a/Java/codes/modes/\344\273\243\347\220\206\346\250\241\345\274\217.md" +++ /dev/null @@ -1,86 +0,0 @@ -## 代理模式 - -> 所谓代理模式是指客户端并不直接调用实际的对象,而是通过调用代理,来间接的调用实际的对象。 - 一般是因为客户端不想直接访问实际的对象,或者访问实际的对象存在困难,因此通过一个代理对象来完成间接的访问。 - -### 静态代理 - -> 定义主题 - -```java -interface Subject { - void visit(); -} -``` - -> 实现subject的两个类 - -```java -class RealSubject implements Subject { - private String name = "dreamcat"; - @Override - public void visit() { - System.out.println(name); - } -} -``` -> 代理类 -```java -class ProxySubject implements Subject { - - private Subject subject; - - public ProxySubject(Subject subject) { - this.subject = subject; - } - - @Override - public void visit() { - subject.visit(); - } -} -``` -缺点: -> 代理类接受一个Subject接口的对象,任何实现该接口的对象,都可以通过代理类进行代理,增加了通用性。 - 每一个代理类都必须实现一遍委托类(也就是realsubject)的接口,如果接口增加方法,则代理类也必须跟着修改。 - 代理类每一个接口对象对应一个委托对象,如果委托对象非常多,则静态代理类就非常臃肿,难以胜任。 - -### 动态代理 -> 动态代理是实现方式,是通过反射来实现的,借助Java自带的java.lang.reflect.Proxy,通过固定的规则生成。 - -> 动态代理 - -```java -class DynamicProxy implements InvocationHandler { - - private Object object; - - public DynamicProxy(Object object) { - this.object = object; - } - - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - Object result = method.invoke(object, args); - return result; - } -} -``` - -### 测试 -```java -public class ProxyMode { - public static void main(String[] args) { - // 静态代理 - ProxySubject proxySubject = new ProxySubject(new RealSubject()); - proxySubject.visit(); - - // 动态代理 - RealSubject realSubject = new RealSubject(); - DynamicProxy dynamicProxy = new DynamicProxy(realSubject); - ClassLoader classLoader = realSubject.getClass().getClassLoader(); - Subject subject = (Subject) Proxy.newProxyInstance(classLoader, new Class[]{Subject.class}, dynamicProxy); - subject.visit(); - } -} -``` diff --git "a/Java/codes/modes/\345\215\225\344\276\213\346\250\241\345\274\217.md" "b/Java/codes/modes/\345\215\225\344\276\213\346\250\241\345\274\217.md" deleted file mode 100644 index d74b210b..00000000 --- "a/Java/codes/modes/\345\215\225\344\276\213\346\250\241\345\274\217.md" +++ /dev/null @@ -1,87 +0,0 @@ -## 单例模式 -> 单例就不多说了 - -### 饿汉 - -```java -public class Singleton { - private static Singleton instance = new Singleton(); - private Singleton(){} - - public static Singleton getInstance() { - return instance; - } -} -``` -### 饿汉变种 - -```java -class Singleton { - private static Singleton instance = null; - static { - instance = new Singleton(); - } - private Singleton() {} - - public static Singleton getInstance() { - return instance; - } -} -``` - -### 懒汉(线程不安全) - -```java -class Singleton { - private static Singleton instance = null; - private Singleton(){} - - public static Singleton getInstance() { - if (instance == null){ - instance = new Singleton(); - } - return instance; - - } -} -``` - -### 懒汉(线程安全,但消耗资源较为严重) - -```java -class Singleton { - private static Singleton instance = null; - - private Singleton() { - } - - public static synchronized Singleton getInstance() { - if (instance == null) { - instance = new Singleton(); - } - return instance; - } -} -``` - -### 懒汉(线程安全,双重校验) - -```java -class Singleton { - private static volatile Singleton instance = null; - - private Singleton() { - } - - public static Singleton getInstance() { - if (instance == null) { - synchronized (Singleton.class) { - if (instance == null) { - instance = new Singleton(); - } - } - } - return instance; - } -} -``` \ No newline at end of file diff --git "a/Java/codes/modes/\345\267\245\345\216\202\346\250\241\345\274\217.md" "b/Java/codes/modes/\345\267\245\345\216\202\346\250\241\345\274\217.md" deleted file mode 100644 index 3ef26dba..00000000 --- "a/Java/codes/modes/\345\267\245\345\216\202\346\250\241\345\274\217.md" +++ /dev/null @@ -1,81 +0,0 @@ -## 工厂模式 -> 大概意思就不要说了,直接举个例子,看例子讲解就知道是什么意思了。 - -### 举例子 - -> 定义一个面条抽象类 - -```java -abstract class INoodles { - /** - * 描述每种面条长什么样的... - */ - public abstract void desc(); -} -``` - -> 定义一份兰州拉面(具体产品) - -```java -class LzNoodles extends INoodles { - - @Override - public void desc() { - System.out.println("兰州拉面,成都的好贵 家里的才5-6块钱一碗"); - } -} -``` - -> 定义一份泡面(程序员挺喜欢的) - -```java -class PaoNoodles extends INoodles { - - @Override - public void desc() { - System.out.println("泡面可还行..."); - } -} -``` - -> 不得不说家乡的杂酱面了,好吃得不得了 - -```java -class ZaNoodles extends INoodles { - - @Override - public void desc() { - System.out.println("杂酱面,嗯? 真香..."); - } -} -``` - -> 重头戏,开面条馆了。(工厂) - -```java -class SimpleNoodlesFactory { - public static final int TYPE_LZ = 1; // 兰州拉面 - public static final int TYPE_PAO = 2; // 泡面撒 - public static final int TYPE_ZA = 3; // 杂酱面 - // 提供静态方法 - public static INoodles createNoodles(int type) { - switch (type) { - case TYPE_LZ:return new LzNoodles(); - case TYPE_PAO:return new PaoNoodles(); - case TYPE_ZA:return new ZaNoodles(); - default:return new ZaNoodles(); - } - } -} -``` - -> 测试 - -```java -public class FactoryMode { - public static void main(String[] args) { - INoodles noodles = SimpleNoodlesFactory.createNoodles(SimpleNoodlesFactory.TYPE_ZA); - noodles.desc(); - } -} -``` \ No newline at end of file diff --git "a/Java/codes/modes/\346\250\241\346\235\277\346\226\271\346\263\225\346\250\241\345\274\217.md" "b/Java/codes/modes/\346\250\241\346\235\277\346\226\271\346\263\225\346\250\241\345\274\217.md" deleted file mode 100644 index cb19467b..00000000 --- "a/Java/codes/modes/\346\250\241\346\235\277\346\226\271\346\263\225\346\250\241\345\274\217.md" +++ /dev/null @@ -1,63 +0,0 @@ -## 模板方法模式 - -> 定义一个操作中的算法骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的 - -例如: -> 去银行办业务,银行给我们提供了一个模板就是:先取号,排对,办理业务(核心部分我们子类完成),给客服人员评分,完毕。 - 这里办理业务是属于子类来完成的,其他的取号,排队,评分则是一个模板。 - -再例如: -> 去餐厅吃饭,餐厅给提供的一套模板就是:先点餐,等待,吃饭(核心部分我们子类完成),买单 - 这里吃饭是属于子类来完成的,其他的点餐,买单则是餐厅提供给我们客户的一个模板。 - -所以: -> 实现一些操作时,整体步骤很固定,但是呢。就是其中一小部分容易变,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。 - - -### 举例子 -> 银行办理业务抽象类 - -```java -abstract class BankTemplateMethod { - // 1. 取号排队 - public void takeNumber() { - System.out.println("取号排队..."); - } - - // 2. 每个子类不同的业务实现,各由子类来实现 - abstract void transact(); - - // 3. 评价 - public void evaluate() { - System.out.println("反馈评价..."); - } - - public void process() { - takeNumber(); - transact(); - evaluate(); - } -} -``` - -> 具体的业务,比如取钱 -```java -class DrawMoney extends BankTemplateMethod { - - @Override - void transact() { - System.out.println("我要取款..."); - } -} -``` - -### 测试 - -```java -public class TemplateMode { - public static void main(String[] args) { - BankTemplateMethod drawMoney = new DrawMoney(); - drawMoney.process(); - } -} -``` \ No newline at end of file diff --git "a/Java/codes/modes/\350\243\205\351\245\260\345\231\250\346\250\241\345\274\217.md" "b/Java/codes/modes/\350\243\205\351\245\260\345\231\250\346\250\241\345\274\217.md" deleted file mode 100644 index 1513ee9d..00000000 --- "a/Java/codes/modes/\350\243\205\351\245\260\345\231\250\346\250\241\345\274\217.md" +++ /dev/null @@ -1,136 +0,0 @@ -## 装饰器模式 - -> 按照单一职责原则,某一个对象只专注于干一件事,而如果要扩展其职能的话,不如想办法分离出一个类来“包装”这个对象,而这个扩展出的类则专注于实现扩展功能。 - 装饰器模式就可以将新功能动态地附加于现有对象而不改变现有对象的功能。 - -> 代理模式专注于对被代理对象的访问; - 装饰器模式专注于对被装饰对象附加额外功能。 - -### 举例子 -> 假设我去买咖啡,首先服务员给我冲了一杯原味咖啡,我希望服务员给我加些牛奶和白糖混合入原味咖啡中。使用装饰器模式就可以解决这个问题。 - -> 咖啡接口 - -```java -interface Coffee { - // 获取价格 - double getCost(); - - // 获取配料 - String getIngredients(); -} -``` - -> 原味咖啡 - -```java -class SimpleCoffee implements Coffee { - - @Override - public double getCost() { - return 1; - } - - @Override - public String getIngredients() { - return "Coffee"; - } -} -``` -> 咖啡对象的装饰器类 -```java -abstract class CoffeeDecorator implements Coffee { - protected final Coffee decoratedCoffee; - - /** - * 在构造方法中,初始化咖啡对象的引用 - */ - - protected CoffeeDecorator(Coffee coffee) { - this.decoratedCoffee = coffee; - } - - /** - * 装饰器父类中直接转发"请求"至引用对象 - */ - public double getCost() { - return decoratedCoffee.getCost(); - } - - public String getIngredients() { - return decoratedCoffee.getIngredients(); - } -} -``` - -注意: -> 具体的装饰器类,负责往咖啡中“添加”牛奶,注意看getCost()方法和getIngredients()方法,可以在转发请求之前或者之后,增加功能。 - 如果是代理模式,这里的结构就有所不同,通常代理模式根据运行时的条件来判断是否转发请求。 - - -> 混合牛奶 - -```java -class WithMilk extends CoffeeDecorator { - - public WithMilk(Coffee coffee) { - super(coffee); - } - - @Override - public double getCost() { - double additionalCost = 0.5; - return super.getCost() + additionalCost; - } - - @Override - public String getIngredients() { - String additionalIngredient = "milk"; - return super.getIngredients() + ", " + additionalIngredient; - } -} -``` - -```java -class WithSugar extends CoffeeDecorator { - - public WithSugar(Coffee coffee) { - super(coffee); - } - - @Override - public double getCost() { - return super.getCost() + 1; - } - - @Override - public String getIngredients() { - return super.getIngredients() + ", Sugar"; - } -} -``` - -### 测试 -```java -public class DecoratorMode { - static void print(Coffee c) { - System.out.println("花费了: " + c.getCost()); - System.out.println("配料: " + c.getIngredients()); - System.out.println("============"); - } - - public static void main(String[] args) { - //原味咖啡 - Coffee c = new SimpleCoffee(); - print(c); - - //增加牛奶的咖啡 - c = new WithMilk(c); - print(c); - - //再加一点糖 - c = new WithSugar(c); - print(c); - } -} -``` \ No newline at end of file diff --git "a/Java/codes/modes/\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217.md" "b/Java/codes/modes/\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217.md" deleted file mode 100644 index 4e209295..00000000 --- "a/Java/codes/modes/\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217.md" +++ /dev/null @@ -1,113 +0,0 @@ -## 观察者模式 -> 在对象之间定义了一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象会收到通知并自动更新。 - 其实就是发布订阅模式,发布者发布信息,订阅者获取信息,订阅了就能收到信息,没订阅就收不到信息。 -> 四个角色 -> - 抽象被观察者角色 -> - 抽象观察者角色 -> - 具体被观察者角色 -> - 具体观察者角色 - -### 举例子(如:微信公众号) - -> 定义一个抽象被观察者接口,声明了添加、删除、通知观察者方法。 - -```java -interface Observerable { - void registerObserver(Observer o); - void removeObserver(Observer o); - void notifyObserver(); -} -``` - -> 定义一个抽象观察者接口,定义了一个update()方法,当被观察者调用notifyObservers()方法时,观察者的update()方法会被回调。 - -```java -interface Observer { - void update(String message); -} -``` - -> 定义被观察者,实现三个接口,并且list保存注册的Observer - -```java -class WechatObserver implements Observerable { - - private List list; - private String message; - - public WechatObserver() { - list = new ArrayList(); - } - - @Override - public void registerObserver(Observer o) { - list.add(o); - } - - @Override - public void removeObserver(Observer o) { - list.remove(o); - } - - @Override - public void notifyObserver() { - for (Observer observer : list) { - observer.update(message); - } - } - - public void setMessage(String message) { - this.message = message; - System.out.println("微信服务更新消息:" + message); - // 消息更新,通知所有观察者 - notifyObserver(); - } -} -``` -> 定义具体观察者,User - -```java -class User implements Observer { - - private String name; - private String message; - - public User(String name) { - this.name = name; - } - - - @Override - public void update(String message) { - this.message = message; - read(); - } - - void read() { - System.out.println(name+ " 收到推送消息:" + message); - } -} -``` - -### 测试 - -```java -public class ObserverMode { - public static void main(String[] args) { - WechatObserver wechatObserver = new WechatObserver(); - User maifeng = new User("Maifeng"); - User xiaofeng = new User("Xiaofeng"); - User fengfeng = new User("Fengfeng"); - - wechatObserver.registerObserver(maifeng); - wechatObserver.registerObserver(xiaofeng); - wechatObserver.registerObserver(fengfeng); - wechatObserver.setMessage("PHP是世界上最好的语言..."); - - System.out.println("---------------"); - - wechatObserver.removeObserver(fengfeng); - wechatObserver.setMessage("JAVA是世界上最好的语言..."); - } -} -``` \ No newline at end of file diff --git "a/Java/codes/mysql/InnoDB\345\222\214MyISAM.md" "b/Java/codes/mysql/InnoDB\345\222\214MyISAM.md" deleted file mode 100644 index 2e0e9b92..00000000 --- "a/Java/codes/mysql/InnoDB\345\222\214MyISAM.md" +++ /dev/null @@ -1,22 +0,0 @@ -# InnoDB和MyISAM - -## InnoDB -- 是 MySQL 默认的**事务型存储引擎**,只有在需要它不支持的特性时,才考虑使用其它存储引擎。 -- 实现了四个标准的隔离级别,默认级别是**可重复读(REPEATABLE READ)**。在可重复读隔离级别下,通过**多版本并发控制**(MVCC)+ **间隙锁**(Next-Key Locking)**防止幻影读**。 -- 主索引是**聚簇索引**,在**索引中保存了数据**,从而避免直接读取磁盘,因此对查询性能有很大的提升。 -- 内部做了很多优化,包括从磁盘读取数据时采用的**可预测性读**、能够加快读操作并且自动创建的**自适应哈希索引**、能够加速插入操作的**插入缓冲区**等。 -- 支持真正的在**线热备份**。其它存储引擎不支持在线热备份,要获取一致性视图需要停止对所有表的写入,而在读写混合场景中,停止写入可能也意味着停止读取。 - -## MyISAM -- 设计简单,数据以**紧密格式存储**。对于只读数据,或者表比较小、可以容忍修复操作,则依然可以使用它。 -- 提供了大量的特性,包括**压缩表、空间数据索引**等。 -- **不支持事务**。 -- **不支持行级锁,只能对整张表加锁**,读取时会对需要读到的所有表加共享锁,写入时则对表加排它锁。但在表有读取操作的同时,也可以往表中插入新的记录,这被称为并发插入(CONCURRENT INSERT)。 - -## 简单对比 -- **事务**: InnoDB 是事务型的,可以使用 `Commit` 和 `Rollback` 语句。 -- **并发**: MyISAM 只支持表级锁,而 InnoDB 还支持行级锁。 -- **外键**: InnoDB 支持外键。 -- **备份**: InnoDB 支持在线热备份。 -- **崩溃恢复**: MyISAM 崩溃后发生损坏的概率比 InnoDB 高很多,而且恢复的速度也更慢。 -- **其它特性**: MyISAM 支持压缩表和空间数据索引。 \ No newline at end of file diff --git "a/Java/codes/mysql/MVCC\347\232\204\347\274\272\347\202\271.md" "b/Java/codes/mysql/MVCC\347\232\204\347\274\272\347\202\271.md" deleted file mode 100644 index 672aea39..00000000 --- "a/Java/codes/mysql/MVCC\347\232\204\347\274\272\347\202\271.md" +++ /dev/null @@ -1,8 +0,0 @@ -# MVCC的缺点 -> MVCC在大多数情况下代替了行锁,实现了对读的非阻塞,读不加锁,读写不冲突。缺点是每行记录都需要额外的存储空间,需要做更多的行维护和检查工作。 要知道的,MVCC机制下,会在更新前建立undo log,根据各种策略读取时非阻塞就是MVCC,undo log中的行就是MVCC中的多版本。 而undo log这个关键的东西,记载的内容是串行化的结果,记录了多个事务的过程,不属于多版本共存。 这么一看,似乎mysql的mvcc也并没有所谓的多版本共存 - -## InnoDB的实现 -1. 事务以**排他锁**的形式修改原始数据 -2. 把修改前的数据存放于**undo log**,通过回滚指针与主数据关联 -3. 修改成功(commit),数据放到**redo log**中,失败则恢复**undo log**中的数据(rollback) - diff --git "a/Java/codes/mysql/MySQL\346\230\257\345\246\202\344\275\225\346\211\247\350\241\214\344\270\200\346\235\241SQL\350\257\255\345\217\245\347\232\204.md" "b/Java/codes/mysql/MySQL\346\230\257\345\246\202\344\275\225\346\211\247\350\241\214\344\270\200\346\235\241SQL\350\257\255\345\217\245\347\232\204.md" deleted file mode 100644 index 92c0c654..00000000 --- "a/Java/codes/mysql/MySQL\346\230\257\345\246\202\344\275\225\346\211\247\350\241\214\344\270\200\346\235\241SQL\350\257\255\345\217\245\347\232\204.md" +++ /dev/null @@ -1,20 +0,0 @@ -# MySQL是如何执行一条SQL的 - -先附上一张图: - -![执行过程图](http://media.dreamcat.ink/uPic/SQL%E6%89%A7%E8%A1%8C%E7%9A%84%E5%85%A8%E9%83%A8%E8%BF%87%E7%A8%8B.png) - -**MySQL内部可以分为服务层和存储引擎层两部分:** -1. **服务层包括连接器、查询缓存、分析器、优化器、执行器等**,涵盖MySQL的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函数等),所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。 -2. **存储引擎层负责数据的存储和提取**,其架构模式是插件式的,支持InnoDB、MyISAM、Memory等多个存储引擎。现在最常用的存储引擎是InnoDB,它从MySQL 5.5.5版本开始成为了默认的存储引擎。 - -**Server层按顺序执行sql的步骤为**: -客户端请求->**连接器**(验证用户身份,给予权限) -> **查询缓存**(存在缓存则直接返回,不存在则执行后续操作)->**分析器**(对SQL进行词法分析和语法分析操作) -> **优化器**(主要对执行的sql优化选择最优的执行方案方法) -> **执行器**(执行时会先看用户是否有执行权限,有才去使用这个引擎提供的接口)->**去引擎层获取数据返回**(如果开启查询缓存则会缓存查询结果) - -简单概括: -- **连接器**:管理连接、权限验证; -- **查询缓存**:命中缓存则直接返回结果; -- **分析器**:对SQL进行词法分析、语法分析;(判断查询的SQL字段是否存在也是在这步) -- **优化器**:执行计划生成、选择索引; -- **执行器**:操作引擎、返回结果; -- **存储引擎**:存储数据、提供读写接口。 \ No newline at end of file diff --git "a/Java/codes/mysql/MySQL\347\232\204ACID\345\216\237\347\220\206.md" "b/Java/codes/mysql/MySQL\347\232\204ACID\345\216\237\347\220\206.md" deleted file mode 100644 index db848b0d..00000000 --- "a/Java/codes/mysql/MySQL\347\232\204ACID\345\216\237\347\220\206.md" +++ /dev/null @@ -1,74 +0,0 @@ -# MySQL的ACID原理 - -> ACID嘛,原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)! - -## 举例子 -我们以从A账户转账50元到B账户为例进行说明一下ACID,四大特性。 - -### 原子性 -> 根据定义,原子性是指一个事务是一个不可分割的工作单位,**其中的操作要么都做,要么都不做**。即要么转账成功,要么转账失败,是不存在中间的状态! - -**如果无法保证原子性会怎么样?** - -OK,就会出现数据不一致的情形,A账户减去50元,而B账户增加50元操作失败。系统将无故丢失50元~ - -### 隔离性 -> 根据定义,隔离性是指**多个事务并发执行的时候,事务内部的操作与其他事务是隔离的**,并发执行的各个事务之间不能互相干扰。 - -**如果无法保证隔离性会怎么样?** - -OK,假设A账户有200元,B账户0元。A账户往B账户转账两次,金额为50元,分别在两个事务中执行。如果无法保证隔离性,会出现下面的情形 - - -![事务隔离](https://imgkr.cn-bj.ufileos.com/b3d04d1e-6bf1-4d49-b670-7e8a4801b385.png) - -如图所示,如果不保证隔离性,A扣款两次,而B只加款一次,凭空消失了50元,依然出现了数据不一致的情形! - -### 持久性 -> 根据定义,**持久性是指事务一旦提交,它对数据库的改变就应该是永久性的**。接下来的其他操作或故障不应该对其有任何影响。 - -**如果无法保证持久性会怎么样?** - -在MySQL中,为了解决CPU和磁盘速度不一致问题,MySQL是将磁盘上的数据加载到内存,对内存进行操作,然后再回写磁盘。好,假设此时宕机了,在内存中修改的数据全部丢失了,持久性就无法保证。 - -设想一下,系统提示你转账成功。但是你发现金额没有发生任何改变,此时数据出现了不合法的数据状态,我们将这种状态认为是**数据不一致**的情形。 - -### 一致性 -> 根据定义,一致性是指事务执行前后,数据处于一种合法的状态,这种状态是语义上的而不是语法上的。 那什么是合法的数据状态呢? oK,这个状态是满足预定的约束就叫做合法的状态,再通俗一点,这状态是由你自己来定义的。**满足这个状态,数据就是一致的,不满足这个状态,数据就是不一致的!** - -**如果无法保证一致性会怎么样?** - -- 例一:A账户有200元,转账300元出去,此时A账户余额为-100元。你自然就发现了此时数据是不一致的,为什么呢?因为你定义了一个状态,余额这列必须大于0。 -- 例二:A账户200元,转账50元给B账户,A账户的钱扣了,但是B账户因为各种意外,余额并没有增加。你也知道此时数据是不一致的,为什么呢?因为你定义了一个状态,要求A+B的余额必须不变。 - -## 如何保证 - -### 保证一致性 -OK,这个问题分为两个层面来说。 - -- **从数据库层面**,数据库通过原子性、隔离性、持久性来保证一致性。也就是说ACID四大特性之中,C(一致性)是目的,A(原子性)、I(隔离性)、D(持久性)是手段,是为了保证一致性,数据库提供的手段。**数据库必须要实现AID三大特性,才有可能实现一致性**。例如,原子性无法保证,显然一致性也无法保证。 -- **从应用层面**,通过代码判断数据库数据是否有效,然后决定回滚还是提交数据! - -### 保证原子性 -OK,是利用Innodb的**undo log**。 **undo log**名为回滚日志,是实现原子性的关键,当事务回滚时能够撤销所有已经成功执行的sql语句,他需要记录你要回滚的相应日志信息。 例如 -- 当你delete一条数据的时候,就需要记录这条数据的信息,回滚的时候,insert这条旧数据 -- 当你update一条数据的时候,就需要记录之前的旧值,回滚的时候,根据旧值执行update操作 -- 当年insert一条数据的时候,就需要这条记录的主键,回滚的时候,根据主键执行delete操作 - -**undo log**记录了这些回滚需要的信息,当事务执行失败或调用了**rollback**,导致事务需要回滚,便可以利用**undo log**中的信息将数据回滚到修改之前的样子。 - -### 保证持久性 -OK,是利用Innodb的**redo log**。 正如之前说的,MySQL是先把磁盘上的数据加载到内存中,在内存中对数据进行修改,再刷回磁盘上。如果此时突然宕机,内存中的数据就会丢失。 怎么解决这个问题? 简单啊,事务提交前直接把数据写入磁盘就行啊。 这么做有什么问题? -- 只修改一个页面里的一个字节,就要将整个页面刷入磁盘,太浪费资源了。毕竟一个页面16kb大小,你只改其中一点点东西,就要将16kb的内容刷入磁盘,听着也不合理。 -- 毕竟一个事务里的SQL可能牵涉到多个数据页的修改,而这些数据页可能不是相邻的,也就是属于随机IO。显然操作随机IO,速度会比较慢。 - -于是,决定采用**redo log**解决上面的问题。当做数据修改的时候,不仅在内存中操作,还会在**redo log**中记录这次操作。当事务提交的时候,会将**redo log**日志进行刷盘(**redo log**一部分在内存中,一部分在磁盘上)。当数据库宕机重启的时候,会将redo log中的内容恢复到数据库中,再根据**undo log**和**binlog**内容决定回滚数据还是提交数据。 - -**采用redo log的好处?** - -其实好处就是将**redo log**进行刷盘比对数据页刷盘效率高,具体表现如下: -- **redo log**体积小,毕竟只记录了哪一页修改了啥,因此体积小,刷盘快。 -- **redo log**是一直往末尾进行追加,属于顺序IO。效率显然比随机IO来的快。 - -### 保证隔离 -**利用的是锁和MVCC机制。** \ No newline at end of file diff --git "a/Java/codes/mysql/MySQL\350\257\273\345\206\231\345\210\206\347\246\273\344\270\273\344\273\216\345\244\215\345\210\266\345\216\237\347\220\206.md" "b/Java/codes/mysql/MySQL\350\257\273\345\206\231\345\210\206\347\246\273\344\270\273\344\273\216\345\244\215\345\210\266\345\216\237\347\220\206.md" deleted file mode 100644 index acd6d144..00000000 --- "a/Java/codes/mysql/MySQL\350\257\273\345\206\231\345\210\206\347\246\273\344\270\273\344\273\216\345\244\215\345\210\266\345\216\237\347\220\206.md" +++ /dev/null @@ -1,11 +0,0 @@ -# MySQL读写分离主从复制原理 - -主库(master)将变更写**binlog**日志,然后从库(slave)连接到主库之后,从库有一个**IO线程**,将主库的binlog日志**拷贝到自己本地**,写入一个中继日志中。接着从库中有一个SQL线程会从中继日志读取binlog,然后执行binlog日志中的内容,也就是在自己本地再次执行一遍SQL,这样就可以保证自己跟主库的数据是一样的。 - -这里有一个非常重要的一点,就是从库同步主库数据的过程是**串行化**的,也就是说**主库上并行**的操作,在从库上会串行执行。所以这就是一个非常重要的点了,由于从库从主库拷贝日志以及串行执行SQL的特点,在高并发场景下,从库的数据一定会比主库慢一些,是有延时的。所以经常出现,刚写入主库的数据可能是读不到的,要过几十毫秒,甚至几百毫秒才能读取到。 - -而且这里还有另外一个问题,就是如果主库突然宕机,然后恰好数据还没同步到从库,那么有些数据可能在从库上是没有的,有些数据可能就丢失了。 - -所以mysql实际上在这一块有两个机制,一个是**半同步复制**,用来解决主库数据丢失问题;一个是**并行复制**,用来解决主从同步延时问题。 - -所谓并行复制,指的是从库**开启多个线程,并行读取relay log中不同库的日志**,然后并行重放不同库的日志,这是库级别的并行。 \ No newline at end of file diff --git "a/Java/codes/mysql/\345\271\266\345\217\221\344\272\213\345\212\241\345\270\246\346\235\245\347\232\204\351\227\256\351\242\230.md" "b/Java/codes/mysql/\345\271\266\345\217\221\344\272\213\345\212\241\345\270\246\346\235\245\347\232\204\351\227\256\351\242\230.md" deleted file mode 100644 index 7f910eab..00000000 --- "a/Java/codes/mysql/\345\271\266\345\217\221\344\272\213\345\212\241\345\270\246\346\235\245\347\232\204\351\227\256\351\242\230.md" +++ /dev/null @@ -1,51 +0,0 @@ -# 并发事务带来的问题 - -## 脏读 - -![脏读](https://www.pdai.tech/_images/pics/dd782132-d830-4c55-9884-cfac0a541b8e.png) - -第一个事务首先读取var变量为50,接着准备更新为100的时,并未提交,第二个事务已经读取var为100,此时第一个事务做了回滚。最终第二个事务读取的var和数据库的var不一样。 - -## 丢弃修改 -![丢弃修改](https://www.pdai.tech/_images/pics/88ff46b3-028a-4dbb-a572-1f062b8b96d3.png) - -T1 和 T2 两个事务都对一个数据进行修改,T1 先修改,T2 随后修改,T2 的修改覆盖了 T1 的修改。例如:事务1读取某表中的数据A=50,事务2也读取A=50,事务1修改A=A+50,事务2也修改A=A+50,最终结果A=100,事务1的修改被丢失。 - -## 不可重复读 - -![不可重复读](https://www.pdai.tech/_images/pics/c8d18ca9-0b09-441a-9a0c-fb063630d708.png) - -T2 读取一个数据,T1 对该数据做了修改。如果 T2 再次读取这个数据,此时读取的结果和第一次读取的结果不同。 - -## 幻读 - -![幻读](https://www.pdai.tech/_images/pics/72fe492e-f1cb-4cfc-92f8-412fb3ae6fec.png) - -T1 读取某个范围的数据,T2 在这个范围内插入新的数据,T1 再次读取这个范围的数据,此时读取的结果和和第一次读取的结果不同。 - -## 不可重复读和幻读区别 -**不可重复读的重点是修改,幻读的重点在于新增或者删除。** - -- 例1(同样的条件, 你读取过的数据, 再次读取出来发现值不一样了 ):事务1中的A先生读取自己的工资为 1000的操作还没完成,事务2中的B先生就修改了A的工资为2000,导致A再读自己的工资时工资变为 2000;这就是不可重复读。 -- 例2(同样的条件, 第1次和第2次读出来的记录数不一样 ):假某工资单表中工资大于3000的有4人,事务1读取了所有工资大于3000的人,共查到4条记录,这时事务2 又插入了一条工资大于3000的记录,事务1再次读取时查到的记 录就变为了5条,这样就导致了幻读。 - -## 隔离级别 -- **未提交读**,事务中发生了修改,即使没有提交,其他事务也是可见的,比如对于一个数A原来50修改为100,但是我还没有提交修改,另一个事务看到这个修改,而这个时候原事务发生了回滚,这时候A还是50,但是另一个事务看到的A是100.**可能会导致脏读、幻读或不可重复读** -- **提交读**,对于一个事务从开始直到提交之前,所做的任何修改是其他事务不可见的,举例就是对于一个数A原来是50,然后提交修改成100,这个时候另一个事务在A提交修改之前,读取的A是50,刚读取完,A就被修改成100,这个时候另一个事务再进行读取发现A就突然变成100了;**可以阻止脏读,但是幻读或不可重复读仍有可能发生** -- **重复读**,就是对一个记录读取多次的记录是相同的,比如对于一个数A读取的话一直是A,前后两次读取的A是一致的;**可以阻止脏读和不可重复读,但幻读仍有可能发生** -- **可串行化读**,在并发情况下,和串行化的读取的结果是一致的,没有什么不同,比如不会发生脏读和幻读;**该级别可以防止脏读、不可重复读以及幻读** - -| 隔离级别 | 脏读 | 不可重复读 | 幻影读 | -| ---------------- | ---- | ---------- | ------ | -| READ-UNCOMMITTED | √ | √ | √ | -| READ-COMMITTED | × | √ | √ | -| REPEATABLE-READ | × | × | √ | -| SERIALIZABLE | × | × | × | - -MySQL InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ**(可重读) - -> **这里需要注意的是**:与 SQL 标准不同的地方在于InnoDB 存储引擎在 REPEATABLE-READ(可重读)事务隔离级别 下使用的是**Next-Key Lock 锁**算法,因此可以避免幻读的产生,这与其他数据库系统(如 SQL Server)是不同的。所以 说InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读) 已经可以完全保证事务的隔离性要 求,即达到了 SQL标准的SERIALIZABLE(可串行化)隔离级别。 - -因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是READ-COMMITTED(读取提交内 容):,但是你要知道的是InnoDB 存储引擎默认使用 **REPEATABLE-READ(可重读)并不会有任何性能损失**。 - -InnoDB 存储引擎在分布式事务 的情况下一般会用到SERIALIZABLE(可串行化)隔离级别。 \ No newline at end of file diff --git "a/Java/codes/mysql/\346\225\260\346\215\256\345\272\223\347\273\223\346\236\204\344\274\230\345\214\226.md" "b/Java/codes/mysql/\346\225\260\346\215\256\345\272\223\347\273\223\346\236\204\344\274\230\345\214\226.md" deleted file mode 100644 index 0649118a..00000000 --- "a/Java/codes/mysql/\346\225\260\346\215\256\345\272\223\347\273\223\346\236\204\344\274\230\345\214\226.md" +++ /dev/null @@ -1,26 +0,0 @@ -# 数据库结构优化 -- **范式优化**: 比如消除冗余(节省空间。。) -- **反范式优化**:比如适当加冗余等(减少join) -- **限定数据的范围**: 务必禁止不带任何限制数据范围条件的查询语句。比如:我们当用户在查询订单历史的时候,我们可以控制在一个月的范围内。 -- **读/写分离**: 经典的数据库拆分方案,主库负责写,从库负责读; -- **拆分表**:分区将数据在物理上分隔开,不同分区的数据可以制定保存在处于不同磁盘上的数据文件里。这样,当对这个表进行查询时,只需要在表分区中进行扫描,而不必进行全表扫描,明显缩短了查询时间,另外处于不同磁盘的分区也将对这个表的数据传输分散在不同的磁盘I/O,一个精心设置的分区可以将数据传输对磁盘I/O竞争均匀地分散开。对数据量大的时时表可采取此方法。可按月自动建表分区。 - -## 拆分表 -**拆分其实又分垂直拆分和水平拆分**: - -举例子: - -简单购物系统暂设涉及如下表: -1. 产品表(数据量10w,稳定) -2. 订单表(数据量200w,且有增长趋势) -3. 用户表 (数据量100w,且有增长趋势) - -### 垂直拆分 -- 解决问题:表与表之间的io竞争 -- 不解决问题:单表中数据量增长出现的压力 -- 方案: 把产品表和用户表放到一个server上 订单表单独放到一个server上 - -### 水平拆分 -- 解决问题:单表中数据量增长出现的压力 -- 不解决问题:表与表之间的io争夺 -- 方案:**用户表**通过性别拆分为**男用户表**和**女用户表**,**订单表**通过已完成和完成中拆分为**已完成订单**和**未完成订单**,**产品表未完成订单放一个server上**,**已完成订单表盒男用户表放一个server上**,**女用户表放一个server上**(女的爱购物 哈哈)。 \ No newline at end of file diff --git "a/Java/codes/mysql/\346\225\260\346\215\256\345\272\223\350\214\203\345\274\217.md" "b/Java/codes/mysql/\346\225\260\346\215\256\345\272\223\350\214\203\345\274\217.md" deleted file mode 100644 index 6a3b7eb6..00000000 --- "a/Java/codes/mysql/\346\225\260\346\215\256\345\272\223\350\214\203\345\274\217.md" +++ /dev/null @@ -1,10 +0,0 @@ -# 数据库范式 - -## 第一范式 -在任何一个关系数据库中,第一范式(1NF)是对关系模式的基本要求,不满足第一范式(1NF)的数据库就不是关系数据库。 所谓第一范式(1NF)是指数据库表的每一列都是不可分割的基本数据项,同一列中不能有多个值,即实体中的某个属性不能有多个值或者不能有重复的属性。如果出现重复的属性,就可能需要定义一个新的实体,新的实体由重复的属性构成,新实体与原实体之间为一对多关系。在第一范式(1NF)中表的每一行只包含一个实例的信息。简而言之,**第一范式就是无重复的列**。 - -## 第二范式 -第二范式(2NF)是在第一范式(1NF)的基础上建立起来的,即满足第二范式(2NF)必须先满足第一范式(1NF)。第二范式(2NF)要求数据库表中的每个实例或行必须可以被惟一地区分。为实现区分通常需要为表加上一个列,以存储各个实例的惟一标识。这个惟一属性列被称为主关键字或主键、主码。 第二范式(2NF)要求实体的属性完全依赖于主关键字。所谓完全依赖是指不能存在仅依赖主关键字一部分的属性,如果存在,那么这个属性和主关键字的这一部分应该分离出来形成一个新的实体,新实体与原实体之间是一对多的关系。为实现区分通常需要为表加上一个列,以存储各个实例的惟一标识。简而言之,第二范式就是**非主属性非部分依赖于主关键字**。 - -## 第三范式 -满足第三范式(3NF)必须先满足第二范式(2NF)。简而言之,第三范式(3NF)要求一个数据库表中不包含已在其它表中已包含的非主关键字信息。例如,存在一个部门信息表,其中每个部门有部门编号(dept_id)、部门名称、部门简介等信息。那么在员工信息表中列出部门编号后就不能再将部门名称、部门简介等与部门有关的信息再加入员工信息表中。如果不存在部门信息表,则根据第三范式(3NF)也应该构建它,否则就会有大量的数据冗余。简而言之,第三范式就是属性不依赖于其它非主属性。(我的理解是消除冗余)**外键** \ No newline at end of file diff --git "a/Java/codes/mysql/\347\264\242\345\274\225\347\247\215\347\261\273.md" "b/Java/codes/mysql/\347\264\242\345\274\225\347\247\215\347\261\273.md" deleted file mode 100644 index f03a8639..00000000 --- "a/Java/codes/mysql/\347\264\242\345\274\225\347\247\215\347\261\273.md" +++ /dev/null @@ -1,30 +0,0 @@ -# 索引种类 - -## 索引类型 -- **FULLTEXT** :即为全文索引,目前只有MyISAM引擎支持。其可以在CREATE TABLE ,ALTER TABLE ,CREATE INDEX 使用,不过目前只有 CHAR、VARCHAR ,TEXT 列上可以创建全文索引。 -- **HASH** :由于HASH的唯一(几乎100%的唯一)及类似键值对的形式,很适合作为索引。 HASH索引可以一次定位,不需要像树形索引那样逐层查找,因此具有极高的效率。但是,这种高效是有条件的,即只在“=”和“in”条件下高效,对于范围查询、排序及组合索引仍然效率不高。 -- **BTREE** :BTREE索引就是一种将索引值按一定的算法,存入一个树形的数据结构中(二叉树),每次查询都是从树的入口root开始,依次遍历node,获取leaf。这是MySQL里默认和最常用的索引类型。 -- **RTREE** :RTREE在MySQL很少使用,仅支持geometry数据类型,支持该类型的存储引擎只有MyISAM、BDb、InnoDb、NDb、Archive几种。 相对于BTREE,RTREE的优势在于范围查找。 - -## 索引种类 -- **普通索引**:仅加速查询 -- **唯一索引**:加速查询 + 列值唯一(可以有null) -- **主键索引**:加速查询 + 列值唯一(不可以有null)+ 表中只有一个 -- **组合索引**:多列值组成一个索引,专门用于组合搜索,其效率大于索引合并 -- **全文索引**:对文本的内容进行分词,进行搜索 -- **索引合并**:使用多个单列索引组合搜索 -- **覆盖索引**:select的数据列只用从索引中就能够取得,不必读取数据行,换句话说查询列要被所建的索引覆盖 -- **聚簇索引**:表数据是和主键一起存储的,主键索引的叶结点存储行数据(包含了主键值),二级索引的叶结点存储行的主键值。使用的是B+树作为索引的存储结构,非叶子节点都是索引关键字,但非叶子节点中的关键字中不存储对应记录的具体内容或内容地址。叶子节点上的数据是主键与具体记录(数据内容) - - -## 为什么使用索引 -- 通过创建唯一性索引,可以保证数据库表中每一行数据的**唯一性**。 -- 可以大大加快数据的**检索速度**,这也是创建索引的最主要的原因。 -- 帮助服务器**避免排序和临时表**。 -- 将**随机IO变为顺序IO**。 -- 可以**加速表和表之间的连接**,特别是在实现数据的参考完整性方面特别有意义。 - -## 索引缺点 -- 当对表中的数据进行增加、删除和修改的时候,**索引也要动态的维护**,这样就降低了数据的维护速度。 -- 索引需要**占物理空间**,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立簇索引,那么需要的空间就会更大。 -- **创建索引和维护索引要耗费时间**,这种时间随着数据量的增加而增加 \ No newline at end of file diff --git "a/Java/codes/mysql/\347\272\242\351\273\221\346\240\221\343\200\201B\346\240\221\345\222\214B+\346\240\221.md" "b/Java/codes/mysql/\347\272\242\351\273\221\346\240\221\343\200\201B\346\240\221\345\222\214B+\346\240\221.md" deleted file mode 100644 index 4282598a..00000000 --- "a/Java/codes/mysql/\347\272\242\351\273\221\346\240\221\343\200\201B\346\240\221\345\222\214B+\346\240\221.md" +++ /dev/null @@ -1,43 +0,0 @@ -# 红黑树、B树和B+树 - -## 红黑树 -> 红黑树也是一种自平衡的二叉查找树。 - -![红黑树](https://www.pdai.tech/_images/alg/alg-tree-14.png) - -### 特点 -- 每个结点要么是红的要么是黑的。(红或黑) -- 根结点是黑的。 (根黑) -- 每个叶结点(叶结点即指树尾端NIL指针或NULL结点)都是黑的。 (叶黑) -- 如果一个结点是红的,那么它的两个儿子都是黑的。 (红子黑) -- 对于任意结点而言,其到叶结点树尾端NIL指针的每条路径都包含相同数目的黑结点。(路径下黑相同) - -### 适用场景 -- Java `ConcurrentHashMap` & `TreeMap` -- C++ STL: map & set -- linux进程调度Completely Fair Scheduler,用红黑树管理进程控制块 -- epoll在内核中的实现,用红黑树管理事件块 -- nginx中,用红黑树管理timer等 - -## B树 -> B树(英语: B-tree)是一种自平衡的树,能够保持数据有序。这种数据结构能够让**查找数据、顺序访问、插入数据及删除的动作,都在对数时间内完成**。B树,概括来说是一个**一般化的二叉查找树**(binary search tree),可以拥有最多2个子节点。与自平衡二叉查找树不同,B树适用于读写相对大的数据块的存储系统,例如磁盘。其中,概念较为复杂,给个简单的图理解: - - -![简单B树](https://imgkr.cn-bj.ufileos.com/e30bb7ee-f0ec-4fd6-a4f2-8f82405dfe9a.png) - - - -## B+树 -- 在B树基础上,为**叶子结点增加链表指针**(B树+叶子有序链表),所有关键字都在叶子结点中出现,非叶子结点作为叶子结点的索引;B+树总是到叶子结点才命中。 -- B+树的**非叶子节点不保存数据**,只保存**子树的临界值**(最大或者最小),所以同样大小的节点,**B+树相对于B树能够有更多的分支,使得这棵树更加矮胖,查询时做的IO操作次数也更少**。 - -给个图: - - -![B+](https://imgkr.cn-bj.ufileos.com/0dd56365-0cc0-4f95-973b-5465c9664f71.png) - - -## 为什么用B+树而不用hash和B-Tree -- 利用Hash需要把数据全部**加载到内存中**,如果数据量大,是一件很**消耗内存**的事,而采用B+树,是基于**按照节点分段加载,由此减少内存消耗**。 -- 和业务场景有段,**对于唯一查找**(查找一个值),Hash确实更快,**但数据库中经常查询多条数据**,这时候由于B+数据的有序性,与叶子节点又有链表相连,他的查询效率会比Hash快的多。 -- b+树的**非叶子节点不保存数据**,**只保存子树的临界值**(最大或者最小),所以同样大小的节点,**b+树相对于b树能够有更多的分支,使得这棵树更加矮胖,查询时做的IO操作次数也更少**。 \ No newline at end of file diff --git "a/Java/codes/other/Sentinel\347\233\270\345\205\263\346\246\202\345\277\265.md" "b/Java/codes/other/Sentinel\347\233\270\345\205\263\346\246\202\345\277\265.md" deleted file mode 100644 index a6437463..00000000 --- "a/Java/codes/other/Sentinel\347\233\270\345\205\263\346\246\202\345\277\265.md" +++ /dev/null @@ -1,24 +0,0 @@ -# Sentinel? -> Sentinel是一个面试分布式架构的轻量级服务保护框架,主要以流量控制、熔断降级、系统负载保护等多个维度。 - -## 服务限流 -> 当系统资源不够,不足以应对大量请求,对系统按照预设的规则进行流量限制或功能限制 - -## 服务熔断 -> 当调用目标服务的请求和调用大量超时或失败,服务调用方为避免造成长时间的阻塞造成影响其他服务,后续对该服务接口的调用不再经过进行请求,直接执行本地的默认方法 - -## 服务降级 -> 为了保证核心业务在大量请求下能正常运行,根据实际业务情况及流量,对部分服务降低优先级,有策略的不处理或用简单的方式处理 - -## 为什么熔断降级 -> 系统承载的访问量是有限的,如果不做流量控制,会导致系统资源占满,服务超时,从而所有用户无法使用,通过服务限流控制请求的量,服务降级省掉非核心业务对系统资源的占用,最大化利用系统资源,尽可能服务更多用户 - -## 工作原理 -![](https://www.javazhiyin.com/wp-content/uploads/2019/10/java6-1572258892.png) - -## 对比 -![](https://imgkr.cn-bj.ufileos.com/8f2cf909-4c41-47c3-89d9-d6eb2676a519.png) - -**值得补充的是**:相比 Hystrix 基于线程池隔离进行限流,这种方案虽然隔离性比较好,但是代价就是线程数目太多,线程上下文切换的 overhead 比较大,特别是对低延时的调用有比较大的影响。 - -Sentinel 并发线程数限流不负责创建和管理线程池,而是简单统计当前请求上下文的线程数目,如果超出阈值,新的请求会被立即拒绝,效果类似于信号量隔离 diff --git "a/Java/codes/redis/Redis\345\201\232\345\273\266\350\277\237\351\230\237\345\210\227.md" "b/Java/codes/redis/Redis\345\201\232\345\273\266\350\277\237\351\230\237\345\210\227.md" deleted file mode 100644 index c8a69ffd..00000000 --- "a/Java/codes/redis/Redis\345\201\232\345\273\266\350\277\237\351\230\237\345\210\227.md" +++ /dev/null @@ -1,218 +0,0 @@ -# Redis做延迟队列 - -## 引言 -> 班车预约平台,订单自动取消任务,采用的是监听Redis的key键消失的策略,属于被动轮询。而用Redis做延迟队列,需要主动轮询。班车平台的github:[https://github.com/DreamCats/school-bus](https://github.com/DreamCats/school-bus) - -## 实现思路 -1. 将整个Redis当做消息池,以kv形式存储消息,key为id,value为具体的消息body -2. 使用ZSET做优先队列,按照score维持优先级(用当前时间+需要延时的时间作为score) -3. 轮询ZSET,拿出score比当前时间戳大的数据(已过期的) -4. 根据id拿到消息池的具体消息进行消费 -5. 消费成功,删除改队列和消息 -6. 消费失败,让该消息重新回到队列 - - -## 代码实现 - -### RedisMessage - -```java -public class RedisMessage { - - /** - * 消息id - */ - private String id; - - /** - * 消息延迟/毫秒 - */ - private long delay; - - /** - * 消息存活时间 - */ - private int ttl; - - /** - * 消息体,对应业务内容 - */ - private String body; - - /** - * 创建时间,如果只有优先级没有延迟,可以设置创建时间为0 - * 用来消除时间的影响 - */ - private long createTime; - -} -``` - -### RedisMQ队列 -```java -@Component -public class RedisMQ { - - /** - * 消息池前缀,以此前缀加上传递的消息id作为key,以消息{@link RedisMessage} - * 的消息体body作为值存储 - */ - public static final String MSG_POOL = "Message:Pool:"; - /** - * zset队列 名称 queue - */ - public static final String QUEUE_NAME = "Message:Queue:"; - - private static final int SEMIH = 30*60; - - @Autowired - private RedisUtils redisUtils; - - /** - * 存入消息池 - * @param message - * @return - */ - public boolean addMsgPool(RedisMessage message) { - - if (null != message) { - return redisUtils.set(MSG_POOL + message.getId(), message.getBody(), Long.valueOf(message.getTtl() + SEMIH)); - } - return false; - } - - /** - * 从消息池中删除消息 - * @param id - */ - public void deMsgPool(String id) { - redisUtils.del(MSG_POOL + id); - } - - /** - * 向队列中添加消息 - * @param key - * @param score - * @param val - */ - public void enMessage(String key, long score, String val) { - redisUtils.zsset(key, val, score); - } - - /** - * 从队列额删除消息 - * @param key - * @param id - * @return - */ - public boolean deMessage(String key, String id) { - return redisUtils.zdel(key, id); - } -} -``` - -### Redis的工具类 -可以去[github地址](https://github.com/DreamCats/school-bus/blob/master/school-bus/guns-order/src/test/java/com/stylefeng/guns/rest/RedisUtils.java) - -### 生产者(用户) -```java -@Component -public class MessageProvider { - - private static int delay = 30;//30秒,可自己动态传入 - - @Resource - private RedisMQ redisMQ; - - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - - //改造成redis - public void sendMessage(String messageContent) { - try { - if (messageContent != null){ - String seqId = UUID.randomUUID().toString(); - // 将有效信息放入消息队列和消息池中 - RedisMessage message = new RedisMessage(); - // 可以添加延迟配置 - message.setDelay(delay*1000); - message.setCreateTime(System.currentTimeMillis()); - message.setBody(messageContent); - message.setId(seqId); - // 设置消息池ttl,防止长期占用 - message.setTtl(delay + 360); - redisMQ.addMsgPool(message); - //当前时间加上延时的时间,作为score - Long delayTime = message.getCreateTime() + message.getDelay(); - String d = sdf.format(message.getCreateTime()); - System.out.println("当前时间:" + d+",消费的时间:" + sdf.format(delayTime)); - redisMQ.enMessage(RedisMQ.QUEUE_NAME,delayTime, message.getId()); - }else { - System.out.println("消息内容为空"); - } - }catch (Exception e){ - e.printStackTrace(); - } - } -} -``` - -### 消费者 -```java -@Component -public class RedisMQConsumer { - - @Resource - private RedisMQ redisMQ; - - @Autowired - private RedisUtils redisUtils; - - @Autowired - private MessageProvider provider; - - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - - - /** - * 消息队列监听器 - * 也可以开个线程池 - * 主动轮询 - */ - @Scheduled(cron = "*/1 * * * * *") - public void monitor() { - // 取出0到当前时间的权重值 - Set set = redisUtils.rangeByScore(RedisMQ.QUEUE_NAME, 0, System.currentTimeMillis()); - if (null != set) { - // 如果不为空 - // 获取当前时间 - long current = System.currentTimeMillis(); - for (Object id : set) { - long score = redisUtils.getScore(RedisMQ.QUEUE_NAME, (String) id).longValue(); - if (current >= score) { - // 已超时的消息拿出来消费 - String str = ""; - try { - // 根据id取出消息 - str = (String) redisUtils.get(RedisMQ.MSG_POOL + id); - System.out.println("消费了:" + str+ ",消费的时间:" + sdf.format(System.currentTimeMillis())); - } catch (Exception e) { - e.printStackTrace(); - //如果取消息发生了异常,则将消息重新放回队列 - System.out.println("消费异常,重新回到队列"); - provider.sendMessage(str); - } finally { - // 不管消费成功与非,都要删除当前消息 - redisMQ.deMessage(RedisMQ.QUEUE_NAME, (String) id); - redisMQ.deMsgPool((String) id); - } - } - } - } - } -} -``` - -### 结合班车预约平台思路 -1. 当前端发一个带延迟时间戳的请求,我们后端可以在zset当做延迟队列,订单ID为元素的key,时间戳为当前时间+延迟时间戳当做score -2. 可以使用Springboot定时器或者线程池延迟主动轮询拉取队列中符合条件的score,根据符合条件score的订单id -3. 根据符合条件的订单id,进行回退座位。 \ No newline at end of file diff --git "a/Java/codes/redis/\346\267\261\347\251\266Redis\347\232\204\345\272\225\345\261\202\347\273\223\346\236\204.md" "b/Java/codes/redis/\346\267\261\347\251\266Redis\347\232\204\345\272\225\345\261\202\347\273\223\346\236\204.md" deleted file mode 100644 index dec79def..00000000 --- "a/Java/codes/redis/\346\267\261\347\251\266Redis\347\232\204\345\272\225\345\261\202\347\273\223\346\236\204.md" +++ /dev/null @@ -1,185 +0,0 @@ -# 深究Redis的底层结构 - -## 简单动态字符串(SDS) -> Redis没有直接使用C语言传统的字符串(以空字符结尾的字符数组,以下简称C字符串),而是自己构建了一种名为简单动态字符串(Simple dynamic string,SDS)的抽象类型,并将SDS用作Redis的默认字符串表示。 - -### 看一下SDS的定义 -```c -struct sdshdr { - // 记录buf数组中已使用字节的数量 - // 等于sds所保存字符串的长度 - int len; - - // 记录buf数组中未使用字节的数量 - int free; - - // 字节数组,用于保存字符串 - char buf[]; -} -``` - -### 有何优点 -1. 获取字符串长度的复杂度为O(1)。 -2. 杜绝缓冲区溢出。 -3. 减少修改字符串长度时所需要的内存重分配次数。 -4. 二进制安全。 -5. 兼容部分C字符串函数。 - -## 链表 -> 毫无疑问,Redis也不会不适用链表的,毕竟链表这么好的结构,用起来很香的。当有一个列表键包含了数量比较多的元素,又或者列表中包含的元素都是比较长的额字符串时,Redis就会使用链表作为列表建的底层实现。 - -### 节点底层结构 -```c -typedef struct listNode { - // 前置节点 - struct listNode *prev; - // 后置节点 - struct listNode *next; - // 节点的值 - void *value; -} listNode; -``` -和其他的节点结构没啥区别... - -### list底层结构 -```c -typedef struct list { - // 表头节点 - listNode *head; - // 表尾节点 - listNode *tail; - // 链表所包含的节点数量 - unsigned long len; - // 节点值复制函数 - void *(*dup)(void *ptr); - // 节点值是放过函数 - void (*free)(void *ptr); - // 节点值对比函数 - int(*match)(void *ptr, void *key); -} list; -``` - -### 特性 -1. 链表被广泛用于实现Redis的各种功能,比如列表建、发布与订阅、慢查询、监视器等。 -2. 每个链表节点由一个listNode结构来表示,每个节点都有一个指向前置节点和后置节点的指针,所以Redis的链表实现是双端链表。 -3. 每个链表使用一个list结构表示,这个结构带有表头节点指针、表尾节点指针,以及链表长度等信息。 -4. 因为链表表头的前置节点和表尾节点的后置节点都指向NULL,所以Redis的链表实现是无环链表。 -5. 通过为链表设置不同的类型特定函数,Redis的链表可以用于保存各种不同类型的值。 - -## 字典 -> 说白了, 字典就是键值对。Redis的字典的底层就是哈希表而已。 - -### 哈希表 -```c -typedef struct dictht { - // 哈希表数组 - dictEntry **table; - // 哈希表大小 - unsigned long size; - // 哈希表大小掩码,用于计算索引值 - // 总是等于size-1 - unsigned long sizemark; - // 该哈希表已有节点的数量 - unsigned long used; -} dichht; -``` - -那么,哈希表数组的结构table呢? - -### dictEntry的结构 -```c -typedef struct dictEntry { - // 键 - void *key; - // 值 - union { - void *val; - uint64_t u64; - int64_t s64; - } v; - // 指向下个哈希表节点,形成链表 - struct dictEntry *nect; -} dictEntry; -``` -dict的底层结构省略。 - -### 哈希算法 -当字典被用作数据库的底层实现,或者哈希键的底层实现时,Redis使用MurmurHash算法。这种算法的优点在于即使输入的键是规律的,算法仍能给出一个个很好的随机分布性,并且算法的计算速度非常快。 - -### 哈希冲突 -Redis的哈希表使用链地址法来解决键冲突,每个哈希表节点都有一个next指针,多个哈希表节点可以用这个单向链表连接起来,这就解决了键冲突的问题。 - -### 特性 -1. 字典被广泛用于实现Redis的各种功能,其中包括数据库和哈希键。 -2. Redis中的字典使用哈希表作为底层结构实现,每个字典带有两个哈希表,一个平时使用,另一个仅在进行rehash时使用。 -3. Redis使用MurmurHash2算法来计算键的哈希值。 -4. 哈希表使用链地址法来解决键冲突。 - -## 跳跃表 - -先看这样一张图: -![](https://user-gold-cdn.xitu.io/2019/4/18/16a30ca4a17c6280?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) - -如上图,我们要查找一个元素,就需要从头节点开始遍历,直到找到对应的节点或者是第一个大于要查找的元素的节点(没找到)。时间复杂度为O(N)。 - -这个查找效率是比较低的,但如果我们把列表的某些节点拔高一层,如下图,例如把每两个节点中有一个节点变成两层。那么第二层的节点只有第一层的一半,查找效率也就会提高。 - -![](https://user-gold-cdn.xitu.io/2019/4/18/16a30ca4aa58e4bb?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) - -查找的步骤是从头节点的顶层开始,查到第一个大于指定元素的节点时,退回上一节点,在下一层继续查找。 - -比如我们要查找16: -1. 从头节点的最顶层开始,先到节点7。 -2. 7的下一个节点是39,大于16,因此我们退回到7 -3. 从7开始,在下一层继续查找,就可以找到16。 - -这个例子中遍历的节点不比一维列表少,但是当节点更多,查找的数字更大时,这种做法的优势就体现出来了。还是上面的例子,如果我们要**查找的是39**,那么只需要访问两个节点(7、39)就可以找到了。这比一维列表要减少一半的数量。 - -为了避免插入操作的时间复杂度是O(N),skiplist每层的数量不会严格按照2:1的比例,而是对每个要插入的元素随机一个层数。 - -随机层数的计算过程如下: -1. 每个节点都有第一层 -2. 那么它有第二层的概率是p,有第三层的概率是p*p -3. 不能超过最大层数 - -### zskiplistNode -```c -typedef struct zskiplistNode { - // 后退指针 - struct zskiplistNode *backward; - // 分值 权重 - double score; - // 成员对象 - robj *obj; - // 层 - struct zskiplistLevel { - // 前进指针 - struct zskiplistNode *forward; - // 跨度 - unsigned int span; - } leval[]; -} zskiplistNode; -``` -一般来说,层的数量越多,访问其他节点的速度越快。 - -### zskipList -```c -typedef struct zskiplist { - // 表头节点和表尾节点 - struct zskiplistNode *header, *tail; - // 表中节点的数量 - unsigned long length; - // 表中层数最大的节点的层数 - int leval; -} zskiplist; -``` - -### 特性 -1. 跳跃表是有序集合的底层实现之一 -2. Redis的跳跃表实现由zskiplist和zskiplistNode两个结构组成,其中zskiplist用于保存跳跃表信息(比如表头节点、表尾节点、长度),而zskiplistNode则用于表示跳跃表节点 -3. 每个跳跃表节点的层高都是1至32之间的随机数 -4. 在同一个跳跃表中,多个节点可以包含相同的分值,但每个节点的成员对象必须是唯一的。 -5. 跳跃表中的节点按照分值大小进行排序,当分值相同时,节点按照成员对象的大小进行排序。 - -## 压缩列表 -> 一看名字,就是为了节省内存造的列表结构。压缩列表(ziplist)是列表键和哈希键的底层实现之一。当一个列表键只包含少量列表项,并且每个列表项要么就是小整数值,要么就是长度比较短的字符串,那么Redis就会使用压缩列表来做列表键的底层实现。 \ No newline at end of file diff --git "a/Java/codes/spring/\346\211\213\345\206\231SpringAOP\344\273\243\347\240\201.md" "b/Java/codes/spring/\346\211\213\345\206\231SpringAOP\344\273\243\347\240\201.md" deleted file mode 100644 index 284133a7..00000000 --- "a/Java/codes/spring/\346\211\213\345\206\231SpringAOP\344\273\243\347\240\201.md" +++ /dev/null @@ -1,116 +0,0 @@ -# 手写SpringAOP - -## 定义advice接口 - -```java -public interface Advice extends InvocationHandler { -} -``` - -## 定义方法回调接口 -```java -public interface MethodInvocation { - void invoke(); -} -``` - -## 定义前置通知 -```java -public class BeforeAdvice implements Advice { - - private Object bean; // bean对象 - - private MethodInvocation methodInvocation; // 方法调用 - - public BeforeAdvice(Object bean, MethodInvocation methodInvocation) { - this.bean = bean; - this.methodInvocation = methodInvocation; - } - - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - // 在目标方法前调用,前置 - methodInvocation.invoke(); - return method.invoke(bean, args); - } -} -``` - -## 定义后置通知 -```java -public class AfterAdvice implements Advice { - - private Object bean; - - private MethodInvocation methodInvocation; - - public AfterAdvice(Object bean, MethodInvocation methodInvocation) { - this.bean = bean; - this.methodInvocation = methodInvocation; - } - - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - // 业务逻辑调用 - Object invoke = method.invoke(bean, args); - // 后置通知调用 - methodInvocation.invoke(); - return invoke; - } -} -``` - -## 定义目标方法接口 -```java -public interface HelloService { - void sayHelloWorld(); -} -``` - -## 实现目标方法 -```java -public class HelloServiceImpl implements HelloService { - - @Override - public void sayHelloWorld() { - System.out.println("hello world..."); - } -} -``` - -## SpringAOP的实现 -```java -public class SimpleAOP { - // 反射代理 - public static Object getProxy(Object bean, Advice advice) { - return Proxy.newProxyInstance(SimpleAOP.class.getClassLoader() - , bean.getClass().getInterfaces(), advice); - } -} -``` - -## 简单测试 -```java -public class SimpleAOPTest { - public static void main(String[] args) { - // 1. 创建一个 MethodInvocation 实现类 切面逻辑类 - MethodInvocation logTask = () -> System.out.println("log task start"); - MethodInvocation logTaskEnd = () -> System.out.println("log task end"); - - // 业务逻辑类 - HelloServiceImpl helloService = new HelloServiceImpl(); - - // 2. 创建一个Advice 切入点 - BeforeAdvice beforeAdvice = new BeforeAdvice(helloService, logTask); - AfterAdvice afterAdvice = new AfterAdvice(helloService, logTaskEnd); - - // 3. 为目标对象生成代理 - HelloService helloServiceImplProxy = (HelloService) SimpleAOP.getProxy(helloService, beforeAdvice); - HelloService helloServiceImplProxyAfter = (HelloService) SimpleAOP.getProxy(helloService, afterAdvice); - - helloServiceImplProxy.sayHelloWorld(); - helloServiceImplProxyAfter.sayHelloWorld(); - - } -} -``` diff --git "a/Java/codes/spring/\346\211\213\345\206\231SpringIoc\345\256\271\345\231\250.md" "b/Java/codes/spring/\346\211\213\345\206\231SpringIoc\345\256\271\345\231\250.md" deleted file mode 100644 index 95863271..00000000 --- "a/Java/codes/spring/\346\211\213\345\206\231SpringIoc\345\256\271\345\231\250.md" +++ /dev/null @@ -1,188 +0,0 @@ -# 手写SpringIoc代码 - -## 举个例子 - -> 容器注册bean(Car和Wheel) - -### Wheel -```java -public class Wheel { - - private String brand; - - private String specification; - - public String getBrand() { - return brand; - } - - public void setBrand(String brand) { - this.brand = brand; - } - - public String getSpecification() { - return specification; - } - - public void setSpecification(String specification) { - this.specification = specification; - } - - @Override - public String toString() { - return "Wheel{" + - "brand='" + brand + '\'' + - ", specification='" + specification + '\'' + - '}'; - } -} -``` - -### Car -```java -public class Car { - - private String name; - - private Wheel wheel; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public Wheel getWheel() { - return wheel; - } - - public void setWheel(Wheel wheel) { - this.wheel = wheel; - } - - @Override - public String toString() { - return "Car{" + - "name='" + name + '\'' + - ", wheel=" + wheel + - '}'; - } -} -``` - -### xml注册 -```xml - - - - - - - - - - - -``` - -### SimpleIOC -```java -public class SimpleIOC { - //需要hashmap - private Map beanMap = new HashMap<>(); - - public SimpleIOC(String location) throws Exception { - loadBean(location); - } - - public Object getBean(String name) { - Object bean = beanMap.get(name); - if (bean == null) { - throw new IllegalArgumentException("there is no bean with name" + name); - } - return bean; - } - - private void loadBean(String location) throws Exception { - // 加载xml配置文件 - InputStream inputStream = new FileInputStream(location); - // docbuilder factory - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - // doc builder - DocumentBuilder documentBuilder = factory.newDocumentBuilder(); - // doc - Document document = documentBuilder.parse(inputStream); - // element - Element element = document.getDocumentElement(); - // nodes - NodeList nodes = element.getChildNodes(); - // 遍历标签获取bean节点信息 - for (int i = 0; i < nodes.getLength(); i++) { - Node node = nodes.item(i); - if (node instanceof Element) { - Element ele = (Element) node; - String id = ele.getAttribute("id"); - String className = ele.getAttribute("class"); - // 加载beanClass - Class beanClass = null; - try { - beanClass = Class.forName(className);// 反射 - } catch (ClassNotFoundException e) { - e.printStackTrace(); - return; - } - // 创建bean - Object bean = beanClass.newInstance(); // new实例 - // 遍历标签 - NodeList propertyNodes = ele.getElementsByTagName("property"); - for (int j = 0; j < propertyNodes.getLength(); j++) { - Node propertyNode = propertyNodes.item(j); - if (propertyNode instanceof Element) { - Element propertyElement = (Element) propertyNode; - String name = propertyElement.getAttribute("name"); - String value = propertyElement.getAttribute("value"); - // 利用反射将 bean 相关字段访问权限设为可访问 // 暴力访问 - Field declaredField = bean.getClass().getDeclaredField(name); - declaredField.setAccessible(true); - - if (value != null && value.length() > 0) { - // 将属性值填充到相关字段中 - declaredField.set(bean, value); - } else { - String ref = propertyElement.getAttribute("ref"); - if (ref == null || ref.length() == 0) { - throw new IllegalArgumentException("ref config error"); - } - // 将引用填充到相关字段中 - declaredField.set(bean, getBean(ref)); - } - } - // 将 bean 注册到 bean 容器中 - registerBean(id, bean); - } - } - } - } - - private void registerBean(String id, Object bean) { - beanMap.put(id, bean); - } -} -``` - -### SimpleIOCTest -```java -public class SimpleIOCTest { - public static void main(String[] args) throws Exception { - System.out.println(SimpleIOC.class.getClassLoader().getResource("ioc.xml").getFile()); - String location = SimpleIOC.class.getClassLoader().getResource("ioc.xml").getFile(); - SimpleIOC simpleIOC = new SimpleIOC(location); - Wheel wheel = (Wheel) simpleIOC.getBean("wheel"); // 获取wheel bean - System.out.println(wheel); - Car car = (Car) simpleIOC.getBean("car"); // 获取 bean - System.out.println(car); - } -} -``` \ No newline at end of file diff --git "a/Java/codes/thread/AQS\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" "b/Java/codes/thread/AQS\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" deleted file mode 100644 index a731c1bd..00000000 --- "a/Java/codes/thread/AQS\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" +++ /dev/null @@ -1,318 +0,0 @@ -# AQS -> Java的`AbstractQueuedSynchronizer`抽象类,抽象同队队列 - - -## 原理 -> AQS 使用一个 int 成员变量来表示同步状态,通过内置的 FIFO 队列来完成获取资源线程的排队工作。AQS 使用 CAS 对该同步状态进行原子操作实现对其值的修改。 -```java -private volatile int state;//共享变量,使用volatile修饰保证线程可见性 -``` -状态信息通过 protected 类型的`getState`,`setState`,`compareAndSetState`进行操作 -```java -//返回同步状态的当前值 -protected final int getState() { - return state; -} - // 设置同步状态的值 -protected final void setState(int newState) { - state = newState; -} -//原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值) -protected final boolean compareAndSetState(int expect, int update) { - return unsafe.compareAndSwapInt(this, stateOffset, expect, update); -} -``` - -**AQS 定义两种资源共享方式** - -1. **Exclusive**(独占)只有一个线程能执行,如 ReentrantLock。又可分为公平锁和非公平锁,ReentrantLock 同时支持两种锁。 - - 总结:公平锁和非公平锁只有两处不同: - - 1. 非公平锁在调用 lock 后,首先就会调用 CAS 进行一次抢锁,如果这个时候恰巧锁没有被占用,那么直接就获取到锁返回了。 - 2. 非公平锁在 CAS 失败后,和公平锁一样都会进入到 tryAcquire 方法,在 tryAcquire 方法中,如果发现锁这个时候被释放了(state == 0),非公平锁会直接 CAS 抢锁,但是公平锁会判断等待队列是否有线程处于等待状态,如果有则不去抢锁,乖乖排到后面。 - -2. **Share**(共享)多个线程可同时执行,如 Semaphore/CountDownLatch。Semaphore、 CyclicBarrier、ReadWriteLock 。 - -**AQS 使用了模板方法模式,自定义同步器时需要重写下面几个 AQS 提供的模板方法:** -```java -isHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它。 -tryAcquire(int)//独占方式。尝试获取资源,成功则返回true,失败则返回false。 -tryRelease(int)//独占方式。尝试释放资源,成功则返回true,失败则返回false。 -tryAcquireShared(int)//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。 -tryReleaseShared(int)//共享方式。尝试释放资源,成功则返回true,失败则返回false。 -``` - -## ReentrantLock -> 我们知道,我们需要在哪里上锁,一般都是调用`xxx.lock()`,在finally的代码块中释放锁`xxx.unlock()`,那么我们来看一下源码。 - -### 构造方法 -```java -// 默认是非公平锁 -public ReentrantLock() { - sync = new NonfairSync(); -} -// 可选参数,是否公平 -public ReentrantLock(boolean fair) { - sync = fair ? new FairSync() : new NonfairSync(); -} -``` - -#### 公平锁的实现 -```java -protected final boolean tryAcquire(int acquires) { - final Thread current = Thread.currentThread(); - int c = getState(); - if (c == 0) { - if (!hasQueuedPredecessors() && // 判断队列是否轮到该线程 - compareAndSetState(0, acquires)) { // 利用cas更换状态 - setExclusiveOwnerThread(current); // 如果都ok,就设置独占锁 - return true; - } - } - else if (current == getExclusiveOwnerThread()) {// 判断当前独占锁是否还是当前线程 - int nextc = c + acquires;// 状态累加 - if (nextc < 0) - throw new Error("Maximum lock count exceeded"); - setState(nextc); // 设置状态 - return true; - } // 否则false - return false; -} -``` -#### 非公平锁的实现 -> 比公平锁少了队列的判断而已。 -```java -final boolean nonfairTryAcquire(int acquires) { - final Thread current = Thread.currentThread(); - int c = getState(); - if (c == 0) { - if (compareAndSetState(0, acquires)) {// 在这里... - setExclusiveOwnerThread(current); - return true; - } - } - else if (current == getExclusiveOwnerThread()) { - int nextc = c + acquires; - if (nextc < 0) // overflow - throw new Error("Maximum lock count exceeded"); - setState(nextc); - return true; - } - return false; -} -``` - -## CountDownLatch -CountDownLatch是共享锁的一种实现,它默认构造 `AQS` 的 `state` 值为 `count`。当线程使用`countDown`方法时,其实使用了`tryReleaseShared`方法以CAS的操作来减少`state`,直至`state`为0就代表所有的线程都调用了`countDown`方法。当调用`await`方法的时候,如果`state`不为0,就代表仍然有线程没有调用countDown方法,那么就把已经调用过countDown的线程都放入**阻塞队列Park,并自旋CAS判断state == 0**,直至最后一个线程调用了countDown,使得state == 0,于是阻塞的线程便判断成功,全部往下执行。 - -### 构造方法 -```java -public CountDownLatch(int count) { - if (count < 0) throw new IllegalArgumentException("count < 0"); - this.sync = new Sync(count); // 这里设置state的次数 -} -``` - -### countDown -```java -public void countDown() { - sync.releaseShared(1); -} -``` -### tryReleaseShared -```java -protected boolean tryReleaseShared(int releases) { - // Decrement count; signal when transition to zero - for (;;) { - int c = getState(); - if (c == 0) - return false; - int nextc = c-1; // 每执行一次该方法,状态减一 - if (compareAndSetState(c, nextc)) - return nextc == 0; - } -} -``` - -### 三种用法: - -1. 某一线程在开始运行前等待 n 个线程执行完毕。将 CountDownLatch 的计数器初始化为 n :`new CountDownLatch(n)`,每当一个任务线程执行完毕,就将计数器减 1 `countdownlatch.countDown()`,当计数器的值变为 0 时,在`CountDownLatch上 await()` 的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。 -2. 实现多个线程开始执行任务的最大并行性。注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。类似于赛跑,将多个线程放到起点,等待发令枪响,然后同时开跑。做法是初始化一个共享的 `CountDownLatch` 对象,将其计数器初始化为 1 :`new CountDownLatch(1)`,多个线程在开始执行任务前首先 `coundownlatch.await()`,当主线程调用 countDown() 时,计数器变为 0,多个线程同时被唤醒。 -3. 死锁检测:一个非常方便的使用场景是,你可以使用 n 个线程访问共享资源,在每次测试阶段的线程数目是不同的,并尝试产生死锁。 - -### 举个例子 -> 主线程等待多个子线程 -```java -public class CountDownLatchDemo { - - public static void main(String[] args) throws InterruptedException { - countDownLatchTest(); -// general(); - } - - public static void general() { - for (int i = 0; i < 6; i++) { - new Thread(() -> { - System.out.println(Thread.currentThread().getName() + " 上完自习,离开教师"); - }, "Thread --> " + i).start(); - } - while (Thread.activeCount() > 2) { // 一般方式 - try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } - System.out.println(Thread.currentThread().getName() + " ====班长最后走人"); - } - } - - public static void countDownLatchTest() throws InterruptedException { - CountDownLatch countDownLatch = new CountDownLatch(6); - for (int i = 0; i < 6; i++) { - new Thread(() -> { - System.out.println(Thread.currentThread().getName() + " 上完自习,离开教师"); - countDownLatch.countDown(); // 每个线程执行完都会调用countDown - }, "Thread --> " + i).start(); - } - countDownLatch.await(); // 阻塞主线程 - System.out.println(Thread.currentThread().getName() + " ====班长最后走人"); - } -} -``` - -## CyclicBarrier -> CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。CyclicBarrier 默认的构造方法是 `CyclicBarrier(int parties)`,其参数表示屏障拦截的线程数量,每个线程调用`await`方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。 - -### 举个例子 -```java -public class CyclicBarrierDemo { - public static void main(String[] args) { - cyclicBarrierTest(); - } - - public static void cyclicBarrierTest() { - CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> { - System.out.println("====召唤神龙===="); // 拦截到一定数量,执行该代码 - }); - for (int i = 0; i < 7; i++) { - final int tempInt = i; - new Thread(() -> { - System.out.println(Thread.currentThread().getName() + " 收集到第" + tempInt + "颗龙珠"); - try { - cyclicBarrier.await(); // 拦截 - } catch (InterruptedException e) { - e.printStackTrace(); - } catch (BrokenBarrierException e) { - e.printStackTrace(); - } - }, "" + i).start(); - } - } -} -``` -当调用 `CyclicBarrier` 对象调用 `await()` 方法时,实际上调用的是`dowait(false, 0L)`方法。 `await()` 方法就像树立起一个栅栏的行为一样,将线程挡住了,当拦住的线程数量达到 parties 的值时,栅栏才会打开,线程才得以通过执行。 -看一下源码: -```java - // 当线程数量或者请求数量达到 count 时 await 之后的方法才会被执行。上面的示例中 count 的值就为 7。 - private int count; - /** - * Main barrier code, covering the various policies. - */ - private int dowait(boolean timed, long nanos) - throws InterruptedException, BrokenBarrierException, - TimeoutException { - final ReentrantLock lock = this.lock; - // 锁住 - lock.lock(); - try { - final Generation g = generation; - - if (g.broken) - throw new BrokenBarrierException(); - - // 如果线程中断了,抛出异常 - if (Thread.interrupted()) { - breakBarrier(); - throw new InterruptedException(); - } - // cout减1 - int index = --count; - // 当 count 数量减为 0 之后说明最后一个线程已经到达栅栏了,也就是达到了可以执行await 方法之后的条件 - if (index == 0) { // tripped - boolean ranAction = false; - try { - final Runnable command = barrierCommand; - if (command != null) - command.run(); - ranAction = true; - // 将 count 重置为 parties 属性的初始化值 - // 唤醒之前等待的线程 - // 下一波执行开始 - nextGeneration(); - return 0; - } finally { - if (!ranAction) - breakBarrier(); - } - } - - // loop until tripped, broken, interrupted, or timed out - for (;;) { - try { - if (!timed) - trip.await(); - else if (nanos > 0L) - nanos = trip.awaitNanos(nanos); - } catch (InterruptedException ie) { - if (g == generation && ! g.broken) { - breakBarrier(); - throw ie; - } else { - // We're about to finish waiting even if we had not - // been interrupted, so this interrupt is deemed to - // "belong" to subsequent execution. - Thread.currentThread().interrupt(); - } - } - - if (g.broken) - throw new BrokenBarrierException(); - - if (g != generation) - return index; - - if (timed && nanos <= 0L) { - breakBarrier(); - throw new TimeoutException(); - } - } - } finally { - lock.unlock(); - } - } -``` -总结:`CyclicBarrier` 内部通过一个 count 变量作为计数器,count 的初始值为 parties 属性的初始化值,每当一个线程到了栅栏这里了,那么就将计数器减一。如果 count 值为 0 了,表示这是这一代最后一个线程到达栅栏,就尝试执行我们构造方法中输入的任务。 - -## Semaphore -> **synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源,Semaphore(信号量)可以指定多个线程同时访问某个资源。** - -### 举个例子 -```java -public class SemaphoreDemo { - public static void main(String[] args) { - Semaphore semaphore = new Semaphore(3);// 模拟三个停车位 - for (int i = 0; i < 6; i++) { // 模拟6部汽车 - new Thread(() -> { - try { - semaphore.acquire(); - System.out.println(Thread.currentThread().getName() + " 抢到车位"); - // 停车3s - try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } - System.out.println(Thread.currentThread().getName() + " 停车3s后离开车位"); - } catch (Exception e) { - e.printStackTrace(); - } finally { - semaphore.release(); - } - }, "Car " + i).start(); - } - } -} -``` diff --git "a/Java/codes/thread/CAS\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" "b/Java/codes/thread/CAS\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" deleted file mode 100644 index c01f3e17..00000000 --- "a/Java/codes/thread/CAS\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" +++ /dev/null @@ -1,89 +0,0 @@ -# CAS -> **我们在读Concurrent包下的类的源码时,发现无论是**ReenterLock内部的AQS,还是各种Atomic开头的原子类,内部都应用到了`CAS` - -```java -public class Test { - - public AtomicInteger i; - - public void add() { - i.getAndIncrement(); - } -} -``` -**我们来看`getAndIncrement`的内部:** -```java -public final int getAndIncrement() { - return unsafe.getAndAddInt(this, valueOffset, 1); -} -``` -**再深入到`getAndAddInt`():** -```java -public 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; -} -``` -**现在重点来了,`compareAndSwapInt(var1, var2, var5, var5 + var4)`其实换成`compareAndSwapInt(obj, offset, expect, update)`比较清楚,意思就是如果`obj`内的`value`和`expect`相等,就证明没有其他线程改变过这个变量,那么就更新它为`update`,如果这一步的`CAS`没有成功,那就采用自旋的方式继续进行`CAS`操作,取出乍一看这也是两个步骤了啊,其实在`JNI`里是借助于一个`CPU`指令完成的。所以还是原子操作。** - -```c -UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x)) - UnsafeWrapper("Unsafe_CompareAndSwapInt"); - oop p = JNIHandles::resolve(obj); - jint* addr = (jint *) index_oop_from_field_offset_long(p, offset); - return (jint)(Atomic::cmpxchg(x, addr, e)) == e; -UNSAFE_END -``` -**p是取出的对象,addr是p中offset处的地址,最后调用了`Atomic::cmpxchg(x, addr, e)`, 其中参数x是即将更新的值,参数e是原内存的值。代码中能看到cmpxchg有基于各个平台的实现。** - -## ABA问题 -> 描述: 第一个线程取到了变量 x 的值 A,然后巴拉巴拉干别的事,总之就是只拿到了变量 x 的值 A。这段时间内第二个线程也取到了变量 x 的值 A,然后把变量 x 的值改为 B,然后巴拉巴拉干别的事,最后又把变量 x 的值变为 A (相当于还原了)。在这之后第一个线程终于进行了变量 x 的操作,但是此时变量 x 的值还是 A,所以 compareAndSet 操作是成功。 - -**目前在JDK的atomic包里提供了一个类`AtomicStampedReference`来解决ABA问题。** - -```java -public class ABADemo { - static AtomicInteger atomicInteger = new AtomicInteger(100); - static AtomicStampedReference atomicStampedReference = new AtomicStampedReference<>(100, 1); - - public static void main(String[] args) { - System.out.println("=====ABA的问题产生====="); - new Thread(() -> { - atomicInteger.compareAndSet(100, 101); - atomicInteger.compareAndSet(101, 100); - }, "t1").start(); - - new Thread(() -> { - // 保证线程1完成一次ABA问题 - try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } - System.out.println(atomicInteger.compareAndSet(100, 2020) + " " + atomicInteger.get()); - }, "t2").start(); - try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } - - System.out.println("=====解决ABA的问题====="); - new Thread(() -> { - int stamp = atomicStampedReference.getStamp(); // 第一次获取版本号 - System.out.println(Thread.currentThread().getName() + " 第1次版本号" + stamp); - try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } - atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1); - System.out.println(Thread.currentThread().getName() + "\t第2次版本号" + atomicStampedReference.getStamp()); - atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1); - System.out.println(Thread.currentThread().getName() + "\t第3次版本号" + atomicStampedReference.getStamp()); - }, "t3").start(); - - new Thread(() -> { - int stamp = atomicStampedReference.getStamp(); - System.out.println(Thread.currentThread().getName() + "\t第1次版本号" + stamp); - try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); } - boolean result = atomicStampedReference.compareAndSet(100, 2020, stamp, stamp + 1); - System.out.println(Thread.currentThread().getName() + "\t修改是否成功" + result + "\t当前最新实际版本号:" + atomicStampedReference.getStamp()); - System.out.println(Thread.currentThread().getName() + "\t当前最新实际值:" + atomicStampedReference.getReference()); - }, "t4").start(); - - } -} -``` \ No newline at end of file diff --git "a/Java/codes/thread/Java\351\224\201\347\232\204\344\273\213\347\273\215.md" "b/Java/codes/thread/Java\351\224\201\347\232\204\344\273\213\347\273\215.md" deleted file mode 100644 index e8c3f0a3..00000000 --- "a/Java/codes/thread/Java\351\224\201\347\232\204\344\273\213\347\273\215.md" +++ /dev/null @@ -1,98 +0,0 @@ -# 各种锁 -## 公平锁/非公平锁 -**公平锁指多个线程按照申请锁的顺序来获取锁。非公平锁指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象(很长时间都没获取到锁-非洲人...),ReentrantLock,了解一下。** - -## 可重入锁 -**可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁,典型的synchronized,了解一下** -```java -synchronized void setA() throws Exception { - Thread.sleep(1000); - setB(); // 因为获取了setA()的锁,此时调用setB()将会自动获取setB()的锁,如果不自动获取的话方法B将不会执行 -} -synchronized void setB() throws Exception { - Thread.sleep(1000); -} -``` - -## 独享锁/共享锁 -- 独享锁:是指该锁一次只能被一个线程所持有。 -- 共享锁:是该锁可被多个线程所持有。 - -## 互斥锁/读写锁 -**上面讲的独享锁/共享锁就是一种广义的说法,互斥锁/读写锁就是其具体的实现** - -## 乐观锁/悲观锁 - -1. **乐观锁与悲观锁不是指具体的什么类型的锁,而是指看待兵法同步的角度。** -2. **悲观锁认为对于同一个人数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改。因此对于同一个数据的并发操作,悲观锁采取加锁的形式。悲观的认为,不加锁的并发操作一定会出现问题。** -3. **乐观锁则认为对于同一个数据的并发操作,是不会发生修改的。在更新数据的时候,会采用尝试更新,不断重新的方式更新数据。乐观的认为,不加锁的并发操作时没有事情的。** -4. **悲观锁适合写操作非常多的场景,乐观锁适合读操作非常多的场景,不加锁带来大量的性能提升。** -5. **悲观锁在Java中的使用,就是利用各种锁。乐观锁在Java中的使用,是无锁编程,常常采用的是CAS算法,典型的例子就是原子类,通过CAS自旋实现原子类操作的更新。重量级锁是悲观锁的一种,自旋锁、轻量级锁与偏向锁属于乐观锁** - -## 分段锁 - -1. **分段锁其实是一种锁的设计,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来哦实现高效的并发操作。** -2. **以ConcurrentHashMap来说一下分段锁的含义以及设计思想,ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap(JDK7与JDK8中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是ReentrantLock(Segment继承了ReentrantLock)** -3. **当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道他要放在那一个分段中,然后对这个分段进行加锁,所以当多线程put的时候,只要不是放在一个分段中,就实现了真正的并行的插入。但是,在统计size的时候,可就是获取hashmap全局信息的时候,就需要获取所有的分段锁才能统计。** -4. **分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。** - -## 偏向锁/轻量级锁/重量级锁 - -1. **这三种锁是锁的状态,并且是针对Synchronized。在Java5通过引入锁升级的机制来实现高效Synchronized。这三种锁的状态是通过对象监视器在对象头中的字段来表明的。偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。** -2. **偏向锁的适用场景:始终只有一个线程在执行代码块,在它没有执行完释放锁之前,没有其它线程去执行同步快,在锁无竞争的情况下使用,一旦有了竞争就升级为轻量级锁,升级为轻量级锁的时候需要撤销偏向锁,撤销偏向锁的时候会导致stop the word操作;在有锁竞争时,偏向锁会多做很多额外操作,尤其是撤销偏向锁的时候会导致进入安全点,安全点会导致stw,导致性能下降,这种情况下应当禁用。** -3. **轻量级锁是指当锁是偏向锁的时候,被另一个线程锁访问,偏向锁就会升级为轻量级锁,其他线程会通过自选的形式尝试获取锁,不会阻塞,提高性能。** -4. **重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。** - -## 自旋锁 - -1. **在Java中,自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。** -2. **自旋锁原理非常简单,如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。** -3. **自旋锁尽可能的减少线程的阻塞,适用于锁的竞争不激烈,且占用锁时间非常短的代码来说性能能大幅度的提升,因为自旋的消耗会小于线程阻塞挂起再唤醒的操作的消耗。** -4. **但是如果锁的竞争激烈,或者持有锁的线程需要长时间占用锁执行同步块,这时候就不适用使用自旋锁了,因为自旋锁在获取锁前一直都是占用cpu做无用功,同时有大量线程在竞争一个锁,会导致获取锁的时间很长,线程自旋的消耗大于线程阻塞挂起操作的消耗,其它需要cpu的线程又不能获取到cpu,造成cpu的浪费。** - -### 手写自旋锁的例子 -```java -public class SpinLock { - - // 原子引用线程 - AtomicReference atomicReference = new AtomicReference<>(); - - public void mylock() { - Thread thread = Thread.currentThread(); - System.out.println(Thread.currentThread().getName() + " como in..."); - while (!atomicReference.compareAndSet(null, thread)) { -// System.out.println("不爽,重新获取一次值瞧瞧..."); - } - } - - public void myUnlock() { - Thread thread = Thread.currentThread(); - atomicReference.compareAndSet(thread, null); - System.out.println(Thread.currentThread().getName() + " invoke myUnLock..."); - } - - public static void main(String[] args) { - SpinLock spinLock = new SpinLock(); - new Thread(() -> { - spinLock.mylock(); - try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } - spinLock.myUnlock(); - }, "t1").start(); - try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } - - new Thread(() -> { - spinLock.mylock(); - spinLock.myUnlock(); - }, "t2").start(); - } - -} -``` - -## Java锁总结 - -**Java锁机制可归为Sychornized锁和Lock锁两类。Synchronized是基于JVM来保证数据同步的,而Lock则是硬件层面,依赖特殊的CPU指令来实现数据同步的。** - -- Synchronized是一个非公平、悲观、独享、互斥、可重入的重量级锁。 -- ReentrantLock是一个默认非公平但可实现公平的、悲观、独享、互斥、可重入、重量级锁。 -- ReentrantReadWriteLock是一个默认非公平但可实现公平的、悲观、写独享、读共享、读写、可重入、重量级锁。 \ No newline at end of file diff --git "a/Java/codes/thread/Runnable\343\200\201Callable\345\222\214FutureTask.md" "b/Java/codes/thread/Runnable\343\200\201Callable\345\222\214FutureTask.md" deleted file mode 100644 index d140c65c..00000000 --- "a/Java/codes/thread/Runnable\343\200\201Callable\345\222\214FutureTask.md" +++ /dev/null @@ -1,61 +0,0 @@ -# Runnable、Callable和FutureTask -> Runnable自Java 1.0以来一直存在,但Callable仅在Java 1.5中引入,目的就是为了来处理**Runnable不支持的用例**。**Runnable 接口不会返回结果或抛出检查异常,但是Callable 接口可以**。所以,如果任务不需要返回结果或抛出异常推荐使用 Runnable 接口,这样代码看起来会更加简洁。 - -## Runnable源码 -```java -@FunctionalInterface -public interface Runnable { - /** - * 被线程执行,没有返回值也无法抛出异常 - */ - public abstract void run(); -} -``` - -## Callable源码 -```java -@FunctionalInterface -public interface Callable { - /** - * 计算结果,或在无法这样做时抛出异常。 - * @return 计算得出的结果 - * @throws 如果无法计算结果,则抛出异常 - */ - V call() throws Exception; -} -``` - -## 线程池的submit - -![看图说话](https://imgkr.cn-bj.ufileos.com/16a8713e-b2d1-408d-82e2-ac411a51b6e0.png) - -## 再瞧Thread - - -![](https://imgkr.cn-bj.ufileos.com/ff6bdf55-1616-4887-b320-1b21576f8211.png) - -有个疑问,没有FutureTask的参数哇? - -再看FutureTask: - -![](https://imgkr.cn-bj.ufileos.com/6f63d5a6-50a5-4412-b301-1bab8ce91b4d.png) - -它的参数有Callable和Runnable。 - -`public class FutureTask implements RunnableFuture` -源码实现了RunnableFuture,那我们跟进再看: - -```java -public interface RunnableFuture extends Runnable, Future { - /** - * Sets this Future to the result of its computation - * unless it has been cancelled. - */ - void run(); -} -``` -RunnableFuture继承了Runnable和Future。意味着FutureTask既有Runnable和Future的特性,Future的方法: - -![](https://imgkr.cn-bj.ufileos.com/5c856df9-feb9-43c0-9329-97db80239de0.png) - -可看出,Future的特性的get方法了。 diff --git "a/Java/codes/thread/ThreadLocal\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" "b/Java/codes/thread/ThreadLocal\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" deleted file mode 100644 index 6ca02b79..00000000 --- "a/Java/codes/thread/ThreadLocal\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" +++ /dev/null @@ -1,38 +0,0 @@ -# ThreadLocal -**如果想实现每一个线程都有自己的专属本地变量该如何解决呢?** JDK中提供的`ThreadLocal`类正是为了解决这样的问题。 **`ThreadLocal`类主要解决的就是让每个线程绑定自己的值,可以将`ThreadLocal`类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。****如果你创建了一个`ThreadLocal`变量,那么访问这个变量的每个线程都会有这个变量的本地副本,这也是`ThreadLocal`变量名的由来。他们可以使用 `get()` 和 `set()` 方法来获取默认值或将其值更改为当前线程所存的副本的值,从而避免了线程安全问题。** - -## 原理 -```java -public class Thread implements Runnable { - ...... -//与此线程有关的ThreadLocal值。由ThreadLocal类维护 -ThreadLocal.ThreadLocalMap threadLocals = null; - -//与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护 -ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; - ...... -} -``` -从上面`Thread`类 源代码可以看出`Thread` 类中有一个 `threadLocals` 和 一个 `inheritableThreadLocals` 变量,它们都是 `ThreadLocalMap` 类型的变量,我们可以把 `ThreadLocalMap` 理解为`ThreadLocal` 类实现的定制化的 `HashMap`。默认情况下这两个变量都是null,只有当前线程调用 `ThreadLocal` 类的 `set`或`get`方法时才创建它们,实际上调用这两个方法的时候,我们调用的是`ThreadLocalMap`类对应的 `get()`、`set() `方法。 - -`ThreadLocal`类的`set()`方法 - -```java -public void set(T value) { - Thread t = Thread.currentThread(); - ThreadLocalMap map = getMap(t); - if (map != null) - map.set(this, value); - else - createMap(t, value); -} -ThreadLocalMap getMap(Thread t) { - return t.threadLocals; -} -``` -**最终的变量是放在了当前线程的 `ThreadLocalMap` 中,并不是存在 `ThreadLocal` 上,ThreadLocal 可以理解为只是ThreadLocalMap的封装,传递了变量值。** - -**每个Thread中都具备一个ThreadLocalMap,而ThreadLocalMap可以存储以ThreadLocal为key的键值对。** 比如我们在同一个线程中声明了两个 `ThreadLocal` 对象的话,会使用 `Thread`内部都是使用仅有那个`ThreadLocalMap` 存放数据的,`ThreadLocalMap`的 key 就是 `ThreadLocal`对象,value 就是 `ThreadLocal` 对象调用`set`方法设置的值。`ThreadLocal` 是 map结构是为了让每个线程可以关联多个 `ThreadLocal`变量。这也就解释了ThreadLocal声明的变量为什么在每一个线程都有自己的专属本地变量。 - -## 内存泄露 -`ThreadLocalMap` 中使用的 key 为 `ThreadLocal` 的弱引用,而 value 是强引用。所以,如果 `ThreadLocal` 没有被外部强引用的情况下,在垃圾回收的时候会 key 会被清理掉,而 value 不会被清理掉。这样一来,`ThreadLocalMap` 中就会出现key为null的Entry。假如我们不做任何措施的话,value 永远无法被GC 回收,这个时候就可能会产生内存泄露。ThreadLocalMap实现中已经考虑了这种情况,在调用 `set()`、`get()`、`remove()` 方法的时候,会清理掉 key 为 null 的记录。使用完 `ThreadLocal`方法后 最好手动调用`remove()`方法。 \ No newline at end of file diff --git "a/Java/codes/thread/synchronized\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" "b/Java/codes/thread/synchronized\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" deleted file mode 100644 index e6d70f2c..00000000 --- "a/Java/codes/thread/synchronized\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" +++ /dev/null @@ -1,97 +0,0 @@ -# synchronized - -## 修饰范围 -先看例子: -```java -public class Test { - private final Object lock = new Object(); - - private static int money = 0; - - // 非静态方法 - public synchronized void noStaticMethod() { - money++; - } - // 静态方法 - public static synchronized void staticMethod() { - money++; - } - public void codeBlock() { - // 代码块 - synchronized(lock) { - money++; - } - } -} -``` -- **修饰实例方法,作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁** -- **修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁** -- **修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁** - -## synchronized和ReentrantLock对比 -1. **两者都是可重入锁**:两者都是可重入锁。“可重入锁”概念是:**自己可以再次获取自己的内部锁**。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。 -2. **Synchronized 依赖于 JVM 而 ReenTrantLock 依赖于 API**:synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。ReenTrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。 -3. **ReenTrantLock 比 Synchronized 增加了一些高级功能** - 1. **等待可中断**:过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。 - 2. **可实现公平锁** - 3. **可实现选择性通知(锁可以绑定多个条件)**:线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。 在使用notify/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的,用ReentrantLock类结合Condition实例可以实现“选择性通知” - 4. **性能已不是选择标准**:在jdk1.6之前synchronized 关键字吞吐量随线程数的增加,下降得非常严重。1.6之后,**synchronized 和 ReenTrantLock 的性能基本是持平了。** - -## 底层原理 -> **synchronized 关键字底层原理属于 JVM 层面。** - -1. **synchronized 同步语句块的情况** - -**synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。** 当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor(monitor对象存在于每个Java对象的对象头中,synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因) 的持有权.当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执行 monitorexit 指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。 - -1. **synchronized 修饰方法的的情况** - -synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。 - -## 1.6版本的优化 -> 在 Java 早期版本中,`synchronized` 属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的 **Mutex Lock** 来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要**操作系统**帮忙完成,而操作系统实现线程之间的切换时需要从**用户态转换到内核态**,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的 synchronized 效率低的原因。庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对synchronized 较大优化,所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引入了大量的优化,如**自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等**技术来减少锁操作的开销。 - -锁主要存在四中状态,依次是:**无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态**,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。 - -1. **偏向锁** - **引入偏向锁的目的和引入轻量级锁的目的很像,他们都是为了没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。但是不同是:轻量级锁在无竞争的情况下使用 CAS 操作去代替使用互斥量。而偏向锁在无竞争的情况下会把整个同步都消除掉**。 - 偏向锁的“偏”就是偏心的偏,它的意思是会偏向于第一个获得它的线程,如果在接下来的执行中,该锁没有被其他线程获取,那么持有偏向锁的线程就不需要进行同步! - 但是对于锁竞争比较激烈的场合,偏向锁就失效了,因为这样场合极有可能每次申请锁的线程都是不相同的,因此这种场合下不应该使用偏向锁,否则会得不偿失,需要注意的是,偏向锁失败后,并不会立即膨胀为重量级锁,而是先升级为轻量级锁。**升级过程:** - 1. 访问Mark Word中**偏向锁的标识是否设置成1**,**锁标识位是否为01**,确认偏向状态 - 2. 如果为可偏向状态,则判断**当前线程ID是否为偏向线程** - 3. 如果偏向线程未当前线程,则通过**cas操作竞争锁**,如果竞争成功则操作Mark Word中线程ID设置为当前线程ID - 4. 如果cas偏向锁获取失败,则挂起当前偏向锁线程,偏向锁升级为**轻量级锁** - -3. **轻量级锁** - 倘若偏向锁失败,虚拟机并不会立即升级为重量级锁,它还会尝试使用一种称为轻量级锁的优化手段(1.6之后加入的)。**轻量级锁不是为了代替重量级锁,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗,因为使用轻量级锁时,不需要申请互斥量。另外,轻量级锁的加锁和解锁都用到了CAS操作。** - **轻量级锁能够提升程序同步性能的依据是“对于绝大部分锁,在整个同步周期内都是不存在竞争的”,这是一个经验数据。如果没有竞争,轻量级锁使用 CAS 操作避免了使用互斥操作的开销。但如果存在锁竞争,除了互斥量开销外,还会额外发生CAS操作,因此在有锁竞争的情况下,轻量级锁比传统的重量级锁更慢!如果锁竞争激烈,那么轻量级将很快膨胀为重量级锁!升级过程** : - 1. 线程由偏向锁升级为轻量级锁时,会先把**锁的对象头MarkWord复制一份到线程的栈帧中,建立一个名为锁记录空间(Lock Record),用于存储当前Mark Word的拷贝**。 - 2. 虚拟机使用cas操作尝试将**对象的Mark Word指向Lock Record的指针,并将Lock record里的owner指针指对象的Mark Word**。 - 3. 如果cas操作成功,则该线程拥有了对象的轻量级锁。第二个线程cas自旋锁等待锁线程释放锁。 - 4. 如果多个线程竞争锁,轻量级锁要膨胀为**重量级锁**,**Mark Word中存储的就是指向重量级锁(互斥量)的指针**。其他等待线程进入阻塞状态。 - -5. **自旋锁和自适应自旋** - 轻量级锁失败后,虚拟机为了避免线程真实地在操作系统层面挂起,还会进行一项称为自旋锁的优化手段。 - 互斥同步对性能最大的影响就是阻塞的实现,因为挂起线程/恢复线程的操作都需要转入内核态中完成(用户态转换到内核态会耗费时间)。 - - **一般线程持有锁的时间都不是太长,所以仅仅为了这一点时间去挂起线程/恢复线程是得不偿失的。** 所以,虚拟机的开发团队就这样去考虑:“我们能不能让后面来的请求获取锁的线程等待一会而不被挂起呢?看看持有锁的线程是否很快就会释放锁”。**为了让一个线程等待,我们只需要让线程执行一个忙循环(自旋),这项技术就叫做自旋**。 - - **在 JDK1.6 中引入了自适应的自旋锁。自适应的自旋锁带来的改进就是:自旋的时间不在固定了,而是和前一次同一个锁上的自旋时间以及锁的拥有者的状态来决定,虚拟机变得越来越“聪明”了**。 - -6. **锁消除** - - 锁消除理解起来很简单,它指的就是虚拟机即使编译器在运行时,如果检测到那些共享数据不可能存在竞争,那么就执行锁消除。锁消除可以节省毫无意义的请求锁的时间。 - -7. **锁粗化** - - 原则上,我们在编写代码的时候,总是推荐将同步块的作用范围限制得尽量小,——直在共享数据的实际作用域才进行同步,这样是为了使得需要同步的操作数量尽可能变小,如果存在锁竞争,那等待线程也能尽快拿到锁。 - -8. **总结升级过程**: - - 1. 检测Mark Word里面是不是当前线程的ID,如果是,表示当前线程处于偏向锁 - 2. 如果不是,则使用CAS将当前线程的ID替换Mard Word,如果成功则表示当前线程获得偏向锁,置偏向标志位1 - 3. 如果失败,则说明发生竞争,撤销偏向锁,进而升级为轻量级锁。 - 4. 当前线程使用CAS将对象头的Mark Word替换为锁记录指针,如果成功,当前线程获得锁 - 5. 如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。 - 6. 如果自旋成功则依然处于轻量级状态。 - 7. 如果自旋失败,则升级为重量级锁。 diff --git "a/Java/codes/thread/volatile\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" "b/Java/codes/thread/volatile\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" deleted file mode 100644 index fbba865f..00000000 --- "a/Java/codes/thread/volatile\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" +++ /dev/null @@ -1,164 +0,0 @@ -# volatile -> 那肯定介绍的它的两大特性:内存可见性和可序性。当然,还会介绍它的底层原理 - -## 内存可见性 -```java -public class Test { - private volatile boolean isStart = true; - void m() { - System.out.println(Thread.currentThread().getName() + " start..."); - while (isStart) {} - System.out.println(Thread.currentThread().getName() + " end..."); - } - - public static void main(String[] args) { - Test t1 = new Test(); - new Thread(t1::m, "t1").start(); - try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } - t1.isStart = false; - } -} -``` - -分析:一开始isReady为true,m方法中的while会一直循环,而主线程开启开线程之后会延迟1s将isReady赋值为false,若不加volatile修饰,则程序一直在运行,若加了volatile修饰,则程序最后会输出t1 m end... - -## 可序性 -> 有序性是指程序代码的执行是按照代码的实现顺序来按序执行的;volatile的有序性特性则是指禁止JVM指令重排优化。 -```java -public class Test { - private volatile static Test instance = null; - private Test(){} - - private static Test getInstance() { - if (instance != null) { - synchronized (Test.class) { - if (instance != null) { - instance = new Test(); - } - } - } - return instance; - } -} -``` -上面的代码是一个很常见的单例模式实现方式,但是上述代码在多线程环境下是有问题的。为什么呢,问题出在instance对象的初始化上,因为`instance = new Singleton();`这个初始化操作并不是原子的,在JVM上会对应下面的几条指令: -``` -memory =allocate(); //1. 分配对象的内存空间 -ctorInstance(memory); //2. 初始化对象 -instance =memory; //3. 设置instance指向刚分配的内存地址 -``` -上面三个指令中,步骤2依赖步骤1,但是步骤3不依赖步骤2,所以JVM可能针对他们进行指令重拍序优化,重排后的指令如下: -``` -memory =allocate(); //1. 分配对象的内存空间 -instance =memory; //3. 设置instance指向刚分配的内存地址 -ctorInstance(memory); //2. 初始化对象 -``` -这样优化之后,内存的初始化被放到了instance分配内存地址的后面,这样的话当线程1执行步骤3这段赋值指令后,刚好有另外一个线程2进入getInstance方法判断instance不为null,这个时候线程2拿到的instance对应的内存其实还未初始化,这个时候拿去使用就会导致出错。 - -所以我们在用这种方式实现单例模式时,会使用volatile关键字修饰instance变量,这是因为volatile关键字除了可以保证变量可见性之外,还具有防止指令重排序的作用。当用volatile修饰instance之后,JVM执行时就不会对上面提到的初始化指令进行重排序优化,这样也就不会出现多线程安全问题了。 - -## 不能保证原子性 -> 20个线程,每个线程count累加10000次,最后count理论上是200000 -```java -public class Test { - volatile int count = 0; - private CountDownLatch latch; - - public Test(CountDownLatch latch) { - this.latch = latch; - } - - void m() { - for (int i = 0; i < 10000; i++) { - count++; - } - latch.countDown(); - } - - public static void main(String[] args) throws InterruptedException { - CountDownLatch latch = new CountDownLatch(20); - Test t1 = new Test(latch); - for (int i = 0; i < 20; i++) { - new Thread(t1::m, "Thread " + i).start(); - } - latch.await(); - System.out.println(t1.count); - } -// 85121 -``` -分析图:[https://www.processon.com/view/link/5e130e51e4b07db4cfac9d2c](https://www.processon.com/view/link/5e130e51e4b07db4cfac9d2c) - -## 整一波内存屏障 -> **Java的Volatile的特征是任何读都能读到最新值,本质上是JVM通过内存屏障来实现的;为了实现volatile内存语义,JMM会分别限制重排序类型。下面是JMM针对编译器制定的volatile重排序规则表:** - - | 是否能重排序 | 第二个操作 | | | -| :----------: | :--------: | :--------: | :--------: | - | 第一个操作 | 普通读/写 | volatile读 | volatile写 | - | 普通读/写 | | | no | - | volatile读 | no | no | no | - | volatile写 | | no | no | - -**从上表我们可以看出**: -- 当第二个操作是volatile写时,不管第一个操作是什么,都不能重排序。这个规则确保volatile写之前的操作不会被编译器重排序到volatile写之后。 -- 当第一个操作是volatile读时,不管第二个操作是什么,都不能重排序。这个规则确保volatile读之后的操作不会被编译器重排序到volatile读之前。 -- 当第一个操作是volatile写,第二个操作是volatile读时,不能重排序。 -**为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。对于编译器来说,发现一个最优布置来最小化插入屏障的总数几乎不可能,为此,JMM采取保守策略。下面是基于保守策略的JMM内存屏障插入策略:** -- 在每个volatile写操作的前面插入一个StoreStore屏障。 -- 在每个volatile写操作的后面插入一个StoreLoad屏障。 -- 在每个volatile读操作的后面插入一个LoadLoad屏障。 -- 在每个volatile读操作的后面插入一个LoadStore屏障。 - -**volatile写插入内存指令图:** -![volatile写插入](https://image-static.segmentfault.com/416/041/416041851-5ac871370fec9_articlex) - -**上图中的StoreStore屏障可以保证在volatile写之前,其前面的所有普通写操作已经对任意处理器可见了。这是因为StoreStore屏障将保障上面所有的普通写在volatile写之前刷新到主内存。** - -> **这里比较有意思的是volatile写后面的StoreLoad屏障。这个屏障的作用是避免volatile写与后面可能有的volatile读/写操作重排序。因为编译器常常无法准确判断在一个volatile写的后面,是否需要插入一个StoreLoad屏障(比如,一个volatile写之后方法立即return)。为了保证能正确实现volatile的内存语义,JMM在这里采取了保守策略:在每个volatile写的后面或在每个volatile读的前面插入一个StoreLoad屏障。从整体执行效率的角度考虑,JMM选择了在每个volatile写的后面插入一个StoreLoad屏障。因为volatile写-读内存语义的常见使用模式是:一个写线程写volatile变量,多个读线程读同一个volatile变量。当读线程的数量大大超过写线程时,选择在volatile写之后插入StoreLoad屏障将带来可观的执行效率的提升。从这里我们可以看到JMM在实现上的一个特点:首先确保正确性,然后再去追求执行效率。** - -**volatile读插入内存指令图:** -![volatile读](https://image-static.segmentfault.com/288/764/2887649856-5ac871c442f52_articlex) - -**上图中的LoadLoad屏障用来禁止处理器把上面的volatile读与下面的普通读重排序。LoadStore屏障用来禁止处理器把上面的volatile读与下面的普通写重排序。** - -**上述volatile写和volatile读的内存屏障插入策略非常保守。在实际执行时,只要不改变volatile写-读的内存语义,编译器可以根据具体情况省略不必要的屏障。下面我们通过具体的示例代码来说明:** - -```java -class VolatileBarrierExample { - int a; - volatile int v1 = 1; - volatile int v2 = 2; - - void readAndWrite() { - int i = v1; //第一个volatile读 - int j = v2; // 第二个volatile读 - a = i + j; //普通写 - v1 = i + 1; // 第一个volatile写 - v2 = j * 2; //第二个 volatile写 - } -} -``` -针对readAndWrite()方法,编译器在生成字节码时可以做如下的优化: -![readAndWrite](https://image-static.segmentfault.com/178/456/1784565222-5ac871e5e6dec_articlex) - -**注意,最后的StoreLoad屏障不能省略。因为第二个volatile写之后,方法立即return。此时编译器可能无法准确断定后面是否会有volatile读或写,为了安全起见,编译器常常会在这里插入一个StoreLoad屏障。** - -## volatile的汇编 -```c -0x000000011214bb49: mov %rdi,%rax -0x000000011214bb4c: dec %eax -0x000000011214bb4e: mov %eax,0x10(%rsi) -0x000000011214bb51: lock addl $0x0,(%rsp) ;*putfield v1 - ; - com.earnfish.VolatileBarrierExample::readAndWrite@21 (line 35) - -0x000000011214bb56: imul %edi,%ebx -0x000000011214bb59: mov %ebx,0x14(%rsi) -0x000000011214bb5c: lock addl $0x0,(%rsp) ;*putfield v2 - ; - com.earnfish.VolatileBarrierExample::readAndWrite@28 (line 36) -``` -**可见其本质是通过一个lock指令来实现的。那么lock是什么意思呢?** - -**它的作用是使得本CPU的Cache写入了内存,该写入动作也会引起别的CPU invalidate其Cache。所以通过这样一个空操作,可让前面volatile变量的修改对其他CPU立即可见。** - -- 锁住内存 -- 任何读必须在写完成之后再执行 -- 使其它线程这个值的栈缓存失效 diff --git "a/Java/codes/thread/\344\270\273\347\272\277\347\250\213\347\255\211\345\276\205\345\244\232\344\270\252\345\255\220\347\272\277\347\250\213\347\232\204\344\276\213\345\255\220.md" "b/Java/codes/thread/\344\270\273\347\272\277\347\250\213\347\255\211\345\276\205\345\244\232\344\270\252\345\255\220\347\272\277\347\250\213\347\232\204\344\276\213\345\255\220.md" deleted file mode 100644 index 41239eb7..00000000 --- "a/Java/codes/thread/\344\270\273\347\272\277\347\250\213\347\255\211\345\276\205\345\244\232\344\270\252\345\255\220\347\272\277\347\250\213\347\232\204\344\276\213\345\255\220.md" +++ /dev/null @@ -1,73 +0,0 @@ -# 主线程等待多个子线程的例子 - -## sleep -> 这个不常用,但是简单一些 -```java -public class Test { - void m() { - System.out.println(Thread.currentThread().getName()); - } - - public static void main(String[] args) { - Test t1 = new Test(); - for (int i = 0; i < 5; i++) { - new Thread(t1::m, "Thread " + i).start(); - } - try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } - System.out.println("main thread"); - } -} -``` - -## join - -```java -public class Test { - void m() { - System.out.println(Thread.currentThread().getName()); - } - - public static void main(String[] args) { - Test t1 = new Test(); - ArrayList threads = new ArrayList<>(); - for (int i = 0; i < 5; i++) { - threads.add(new Thread(t1::m, "Thread " + i)); - } - threads.forEach(o -> o.start()); - threads.forEach(o -> { - try { - o.join(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - }); - System.out.println("main thread"); - } -} -``` - -## CountDownLatch -```java -public class Test { - private CountDownLatch latch; - - public Test(CountDownLatch latch) { - this.latch = latch; - } - - void m() { - System.out.println(Thread.currentThread().getName()); - latch.countDown(); - } - - public static void main(String[] args) throws InterruptedException { - CountDownLatch countDownLatch = new CountDownLatch(5); - Test t1 = new Test(countDownLatch); - for (int i = 0; i < 5; i++) { - new Thread(t1::m, "Thread " + i).start(); - } - countDownLatch.await(); - System.out.println("main thread"); - } -} -``` \ No newline at end of file diff --git "a/Java/codes/thread/\344\270\276\344\270\252\345\244\232\347\272\277\347\250\213\347\232\204\345\260\217\344\276\213\345\255\220.md" "b/Java/codes/thread/\344\270\276\344\270\252\345\244\232\347\272\277\347\250\213\347\232\204\345\260\217\344\276\213\345\255\220.md" deleted file mode 100644 index eed0016c..00000000 --- "a/Java/codes/thread/\344\270\276\344\270\252\345\244\232\347\272\277\347\250\213\347\232\204\345\260\217\344\276\213\345\255\220.md" +++ /dev/null @@ -1,94 +0,0 @@ -# 举个多线程的小例子 -> 开启10个线程,每个线程对同一个变量进行1000次加1操作。 - -## V1 -```java -public class Test { - int count; - void m() { - for(int i = 0; i < 1000; i++) { - count++; - } - } - - public static void main(String[] args) { - Test t1 = new Test(); - for (int i = 0; i < 10; i++){ - new Thread(t1::m, "Thread " + i).start(); - } - // 等待完成,这里有多种方式... - try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } - System.out.println(t1.count); - } -} -// 运行结果 -// 9378 -// 想知道原因,得需要知道Java内存模型... -``` -## V2(volatile) -```java -public class Test { - volatile int count; - void m() { - for(int i = 0; i < 1000; i++) { - count++; - } - } - - public static void main(String[] args) { - Test t1 = new Test(); - for (int i = 0; i < 10; i++){ - new Thread(t1::m, "Thread " + i).start(); - } - // 等待完成,这里有多种方式... - try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } - System.out.println(t1.count); - } -} -``` -count++是非原子性操作,即使使用volatile保证内存可见性,但是无法保证原子性,因此,还是凉凉 - -## V3(synchronized) -```java -public class Test { - int count; - synchronized void m() { - for(int i = 0; i < 1000; i++) { - count++; - } - } - - public static void main(String[] args) { - Test t1 = new Test(); - for (int i = 0; i < 10; i++){ - new Thread(t1::m, "Thread " + i).start(); - } - // 等待完成,这里有多种方式... - try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } - System.out.println(t1.count); - } -} -``` -这里可以得到线程安全同步,但是效率似乎有点慢,毕竟这个操作是自增。 - -## V4(CAS) -```java -public class Test { - AtomicInteger count = new AtomicInteger(0); - void m() { - count.incrementAndGet(); - } - - public static void main(String[] args) { - Test t1 = new Test(); - for (int i = 0; i < 10; i++){ - new Thread(t1::m, "Thread " + i).start(); - } - // 等待完成,这里有多种方式... - try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } - System.out.println(t1.count); - } -} -``` - -像Volatile、synchronized、CAS稍后再说... \ No newline at end of file diff --git "a/Java/codes/thread/\346\255\273\351\224\201\344\276\213\345\255\220.md" "b/Java/codes/thread/\346\255\273\351\224\201\344\276\213\345\255\220.md" deleted file mode 100644 index f63c7eed..00000000 --- "a/Java/codes/thread/\346\255\273\351\224\201\344\276\213\345\255\220.md" +++ /dev/null @@ -1,38 +0,0 @@ -# 死锁例子 - -## 死锁的四个条件 -- 互斥条件:该资源任意一个时刻只由一个线程占用。(同一时刻,这个碗是我的,你不能碰) -- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。(我拿着这个碗一直不放) -- 不剥夺条件:线程已获得的资源在末使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。(我碗中的饭没吃完,你不能抢,释放权是我自己的,我想什么时候放就什么时候放) -- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。(我拿了A碗,你拿了B碗,但是我还想要你的B碗,你还想我的A碗) - -## 例子 -```java -public class Test { - private static Object res1 = new Object(); - private static Object res2 = new Object(); - - public static void main(String[] args) { - new Thread(() -> { - synchronized (res1) { - System.out.println(Thread.currentThread().getName() + " res1"); - // 延迟一下, 确保B拿到了res2 - try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } - synchronized (res2) { - System.out.println(Thread.currentThread().getName() + " res2"); - } - } - }, "ThreadA").start(); - - new Thread(() -> { - synchronized (res2) { - System.out.println(Thread.currentThread().getName() + " res2"); - // 延迟一下,确保A拿到了res1 - synchronized (res1) { - System.out.println(Thread.currentThread().getName() + " res1"); - } - } - }, "ThreadB").start(); - } -} -``` \ No newline at end of file diff --git "a/Java/codes/thread/\347\224\237\344\272\247\350\200\205\345\222\214\346\266\210\350\264\271\350\200\205\347\232\204\344\270\211\347\247\215\344\276\213\345\255\220.md" "b/Java/codes/thread/\347\224\237\344\272\247\350\200\205\345\222\214\346\266\210\350\264\271\350\200\205\347\232\204\344\270\211\347\247\215\344\276\213\345\255\220.md" deleted file mode 100644 index d68976cc..00000000 --- "a/Java/codes/thread/\347\224\237\344\272\247\350\200\205\345\222\214\346\266\210\350\264\271\350\200\205\347\232\204\344\270\211\347\247\215\344\276\213\345\255\220.md" +++ /dev/null @@ -1,192 +0,0 @@ -# 生产者和消费者 -> 在这里有三种方式实现:synchronized、ReentrantLock和BlockingQueue - -## synchronized -```java -public class ProdConsumerSynchronized { - - private final LinkedList lists = new LinkedList<>(); - - public synchronized void put(String s) { - while (lists.size() != 0) { // 用while怕有存在虚拟唤醒线程 - // 满了, 不生产了 - try { - this.wait(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - lists.add(s); - System.out.println(Thread.currentThread().getName() + " " + lists.peekFirst()); - this.notifyAll(); // 这里可是通知所有被挂起的线程,包括其他的生产者线程 - } - - public synchronized void get() { - while (lists.size() == 0) { - try { - this.wait(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - System.out.println(Thread.currentThread().getName() + " " + lists.removeFirst()); - this.notifyAll(); // 通知所有被wait挂起的线程 用notify可能就死锁了。 - } - - public static void main(String[] args) { - ProdConsumerSynchronized prodConsumerSynchronized = new ProdConsumerSynchronized(); - - // 启动消费者线程 - for (int i = 0; i < 5; i++) { - new Thread(prodConsumerSynchronized::get, "ConsA" + i).start(); - } - - // 启动生产者线程 - for (int i = 0; i < 5; i++) { - int tempI = i; - new Thread(() -> { - prodConsumerSynchronized.put("" + tempI); - }, "ProdA" + i).start(); - } - } -} -``` - -## ReentrantLock -```java -public class ProdConsumerReentrantLock { - - private LinkedList lists = new LinkedList<>(); - - private Lock lock = new ReentrantLock(); - - private Condition prod = lock.newCondition(); - - private Condition cons = lock.newCondition(); - - public void put(String s) { - lock.lock(); - try { - // 1. 判断 - while (lists.size() != 0) { - // 等待不能生产 - prod.await(); - } - // 2.干活 - lists.add(s); - System.out.println(Thread.currentThread().getName() + " " + lists.peekFirst()); - // 3. 通知 - cons.signalAll(); - } catch (Exception e) { - e.printStackTrace(); - } finally { - lock.unlock(); - } - } - - public void get() { - lock.lock(); - try { - // 1. 判断 - while (lists.size() == 0) { - // 等待不能消费 - cons.await(); - } - // 2.干活 - System.out.println(Thread.currentThread().getName() + " " + lists.removeFirst()); - // 3. 通知 - prod.signalAll(); - } catch (Exception e) { - e.printStackTrace(); - } finally { - lock.unlock(); - } - } - - public static void main(String[] args) { - ProdConsumerReentrantLock prodConsumerReentrantLock = new ProdConsumerReentrantLock(); - for (int i = 0; i < 5; i++) { - int tempI = i; - new Thread(() -> { - prodConsumerReentrantLock.put(tempI + ""); - }, "ProdA" + i).start(); - } - for (int i = 0; i < 5; i++) { - new Thread(prodConsumerReentrantLock::get, "ConsA" + i).start(); - } - } -} -``` -## BlockingQueue -```java -public class ProdConsumerBlockingQueue { - - private volatile boolean flag = true; - - private AtomicInteger atomicInteger = new AtomicInteger(); - - BlockingQueue blockingQueue = null; - - public ProdConsumerBlockingQueue(BlockingQueue blockingQueue) { - this.blockingQueue = blockingQueue; - } - - public void myProd() throws Exception { - String data = null; - boolean retValue; - while (flag) { - data = atomicInteger.incrementAndGet() + ""; - retValue = blockingQueue.offer(data, 2, TimeUnit.SECONDS); - if (retValue) { - System.out.println(Thread.currentThread().getName() + " 插入队列" + data + " 成功"); - } else { - System.out.println(Thread.currentThread().getName() + " 插入队列" + data + " 失败"); - } - TimeUnit.SECONDS.sleep(1); - } - System.out.println(Thread.currentThread().getName() + " 大老板叫停了,flag=false,生产结束"); - } - - public void myConsumer() throws Exception { - String result = null; - while (flag) { - result = blockingQueue.poll(2, TimeUnit.SECONDS); - if (null == result || result.equalsIgnoreCase("")) { - flag = false; - System.out.println(Thread.currentThread().getName() + " 超过2s没有取到蛋糕,消费退出"); - return; - } - System.out.println(Thread.currentThread().getName() + " 消费队列" + result + "成功"); - } - } - - public void stop() { - flag = false; - } - - public static void main(String[] args) { - ProdConsumerBlockingQueue prodConsumerBlockingQueue = new ProdConsumerBlockingQueue(new ArrayBlockingQueue<>(10)); - new Thread(() -> { - System.out.println(Thread.currentThread().getName() + " 生产线程启动"); - try { - prodConsumerBlockingQueue.myProd(); - } catch (Exception e) { - e.printStackTrace(); - } - }, "Prod").start(); - - new Thread(() -> { - System.out.println(Thread.currentThread().getName() + " 消费线程启动"); - try { - prodConsumerBlockingQueue.myConsumer(); - } catch (Exception e) { - e.printStackTrace(); - } - }, "Consumer").start(); - - try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } - System.out.println("5s后main叫停,线程结束"); - prodConsumerBlockingQueue.stop(); - } -} -``` \ No newline at end of file diff --git "a/Java/codes/thread/\347\272\277\347\250\213\346\261\240\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" "b/Java/codes/thread/\347\272\277\347\250\213\346\261\240\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" deleted file mode 100644 index b5afec2a..00000000 --- "a/Java/codes/thread/\347\272\277\347\250\213\346\261\240\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" +++ /dev/null @@ -1,136 +0,0 @@ -# 线程池 -线程池的好处 -- **降低资源消耗**。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。 -- **提高响应速度**。当任务到达时,任务可以不需要的等到线程创建就能立即执行。 -- **提高线程的可管理性**。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。 - -## FixedThreadPool -`FixedThreadPool` 被称为可重用固定线程数的线程池。通过 Executors 类中的相关源代码来看一下相关实现: -```java -/** - * 创建一个可重用固定数量线程的线程池 - */ -public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) { - return new ThreadPoolExecutor(nThreads, nThreads, - 0L, TimeUnit.MILLISECONDS, - new LinkedBlockingQueue(), - threadFactory); -} -``` -**从上面源代码可以看出新创建的 `FixedThreadPool` 的 `corePoolSize` 和 `maximumPoolSize` 都被设置为 nThreads,这个 nThreads 参数是我们使用的时候自己传递的。** -1. 如果当前运行的线程数小于 corePoolSize, 如果再来新任务的话,就创建新的线程来执行任务; -2. 当前运行的线程数等于 corePoolSize 后, 如果再来新任务的话,会将任务加入 `LinkedBlockingQueue`; -3. 线程池中的线程执行完 手头的任务后,会在循环中反复从 `LinkedBlockingQueue` 中获取任务来执行; - -不推荐使用 - -**`FixedThreadPool` 使用无界队列 `LinkedBlockingQueue`(队列的容量为 Intger.MAX_VALUE)作为线程池的工作队列会对线程池带来如下影响 :** - -1. 当线程池中的线程数达到 `corePoolSize` 后,新任务将在无界队列中等待,因此线程池中的线程数不会超过 corePoolSize; -2. 由于使用无界队列时 `maximumPoolSize` 将是一个无效参数,因为不可能存在任务队列满的情况。所以,通过创建 `FixedThreadPool`的源码可以看出创建的 `FixedThreadPool` 的 `corePoolSize` 和 `maximumPoolSize` 被设置为同一个值。 -3. 由于 1 和 2,使用无界队列时 `keepAliveTime` 将是一个无效参数; -4. 运行中的 `FixedThreadPool`(未执行 `shutdown()`或 `shutdownNow()`)不会拒绝任务,在任务比较多的时候会导致 OOM(内存溢出)。 - -## SingleThreadExecutor -```java -/** - *返回只有一个线程的线程池 - */ -public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) { - return new FinalizableDelegatedExecutorService - (new ThreadPoolExecutor(1, 1, - 0L, TimeUnit.MILLISECONDS, - new LinkedBlockingQueue(), - threadFactory)); -} -``` -和上面一个差不多,只不过core和max都被设置为1 - -## CachedThreadPool -```java -/** - * 创建一个线程池,根据需要创建新线程,但会在先前构建的线程可用时重用它。 - */ -public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) { - return new ThreadPoolExecutor(0, Integer.MAX_VALUE, - 60L, TimeUnit.SECONDS, - new SynchronousQueue(), - threadFactory); -} -``` -`CachedThreadPool` 的` corePoolSize` 被设置为空(0),`maximumPoolSize `被设置为 Integer.MAX.VALUE,即它是无界的,这也就意味着如果主线程提交任务的速度高于 `maximumPool` 中线程处理任务的速度时,`CachedThreadPool` 会不断创建新的线程。极端情况下,这样会导致耗尽 cpu 和内存资源。 - -1. 首先执行 `SynchronousQueue.offer(Runnable task)` 提交任务到任务队列。如果当前 `maximumPool` 中有闲线程正在执行 `SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)`,那么主线程执行 offer 操作与空闲线程执行的 `poll` 操作配对成功,主线程把任务交给空闲线程执行,`execute()`方法执行完成,否则执行下面的步骤 2; -2. 当初始 `maximumPool` 为空,或者 `maximumPool` 中没有空闲线程时,将没有线程执行 `SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)`。这种情况下,步骤 1 将失败,此时 `CachedThreadPool` 会创建新线程执行任务,execute 方法执行完成; - -## ThreadPoolExecutor(重点) -```java -/** - * 用给定的初始参数创建一个新的ThreadPoolExecutor。 - */ -public ThreadPoolExecutor(int corePoolSize,//线程池的核心线程数量 - int maximumPoolSize,//线程池的最大线程数 - long keepAliveTime,//当线程数大于核心线程数时,多余的空闲线程存活的最长时间 - TimeUnit unit,//时间单位 - BlockingQueue workQueue,//任务队列,用来储存等待执行任务的队列 - ThreadFactory threadFactory,//线程工厂,用来创建线程,一般默认即可 - RejectedExecutionHandler handler//拒绝策略,当提交的任务过多而不能及时处理时,我们可以定制策略来处理任务 - ) -``` - -**`ThreadPoolExecutor` 3 个最重要的参数:** - -- **`corePoolSize` :** 核心线程数线程数定义了最小可以同时运行的线程数量。 -- **`maximumPoolSize` :** 当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。 -- **`workQueue`:** 当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,信任就会被存放在队列中。 - -`ThreadPoolExecutor`其他常见参数: - -- **`keepAliveTime`**:当线程池中的线程数量大于 `corePoolSize` 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 `keepAliveTime`才会被回收销毁; -- **`unit`** : `keepAliveTime` 参数的时间单位。 -- **`threadFactory`** :executor 创建新线程的时候会用到。 -- **`handler`** :饱和策略。关于饱和策略下面单独介绍一下. - -![线程池各个参数的关系](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-7/线程池各个参数的关系.jpg) - -- **`ThreadPoolExecutor.AbortPolicy`**:抛出 `RejectedExecutionException`来拒绝新任务的处理。 -- **`ThreadPoolExecutor.CallerRunsPolicy`**:调用执行自己的线程运行任务。您不会任务请求。但是这种策略会降低对于新任务提交速度,影响程序的整体性能。另外,这个策略喜欢增加队列容量。如果您的应用程序可以承受此延迟并且你不能任务丢弃任何一个任务请求的话,你可以选择这个策略。 -- **`ThreadPoolExecutor.DiscardPolicy`:** 不处理新任务,直接丢弃掉。 -- **`ThreadPoolExecutor.DiscardOldestPolicy`:** 此策略将丢弃最早的未处理的任务请求。 - -> Spring 通过 `ThreadPoolTaskExecutor` 或者我们直接通过 `ThreadPoolExecutor` 的构造函数创建线程池的时候,当我们不指定 `RejectedExecutionHandler` 饱和策略的话来配置线程池的时候默认使用的是 `ThreadPoolExecutor.AbortPolicy`。在默认情况下,`ThreadPoolExecutor` 将抛出 `RejectedExecutionException` 来拒绝新来的任务 ,这代表你将丢失对这个任务的处理。 对于可伸缩的应用程序,建议使用 `ThreadPoolExecutor.CallerRunsPolicy`。当最大池被填满时,此策略为我们提供可伸缩队列。(这个直接查看 `ThreadPoolExecutor` 的构造函数源码就可以看出,比较简单的原因,这里就不贴代码了。) - -**Executors 返回线程池对象的弊端如下**: - -- **`FixedThreadPool` 和 `SingleThreadExecutor`** : 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致 OOM。 -- **CachedThreadPool 和 ScheduledThreadPool** : 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致 OOM。 - -```java -public class ThreadPoolExecutorDemo { - public static void main(String[] args) { - ExecutorService threadpools = new ThreadPoolExecutor( - 3, - 5, - 1l, - TimeUnit.SECONDS, - new LinkedBlockingDeque<>(3), - Executors.defaultThreadFactory(), - new ThreadPoolExecutor.AbortPolicy()); -//new ThreadPoolExecutor.AbortPolicy(); -//new ThreadPoolExecutor.CallerRunsPolicy(); -//new ThreadPoolExecutor.DiscardOldestPolicy(); -//new ThreadPoolExecutor.DiscardPolicy(); - try { - for (int i = 0; i < 8; i++) { - threadpools.execute(() -> { - System.out.println(Thread.currentThread().getName() + "\t办理业务"); - }); - } - } catch (Exception e) { - e.printStackTrace(); - } finally { - threadpools.shutdown(); - } - } -} -``` \ No newline at end of file diff --git "a/Java/codes/thread/\347\272\277\347\250\213\347\224\237\345\221\275\345\221\250\346\234\237\344\270\200\344\272\233\351\227\256\351\242\230.md" "b/Java/codes/thread/\347\272\277\347\250\213\347\224\237\345\221\275\345\221\250\346\234\237\344\270\200\344\272\233\351\227\256\351\242\230.md" deleted file mode 100644 index c6d28b1e..00000000 --- "a/Java/codes/thread/\347\272\277\347\250\213\347\224\237\345\221\275\345\221\250\346\234\237\344\270\200\344\272\233\351\227\256\351\242\230.md" +++ /dev/null @@ -1,90 +0,0 @@ -# 线程的生命周期 - -先放一张图: -![线程周期图](https://www.pdai.tech/_images/pics/ace830df-9919-48ca-91b5-60b193f593d2.png) - -线程创建之后它将处于**New**(新建)状态,调用 **start()** 方法后开始运行,线程这时候处于 **READY**(可运行) 状态。可运行状态的线程获得了 **CPU 时间片**(timeslice)后就处于 **RUNNING**(运行) 状态。 - -- 当线程执行 **wait()**方法之后,线程进入 **WAITING**(等待) 状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态 -- 而 **TIME_WAITING**(超时等待) 状态相当于在等待状态的基础上增加了超时限制,比如通过 **sleep**(long millis)方法或 **wait**(long millis)方法可以将 Java 线程置于 **TIMED_WAITING** 状态。当超时时间到达后 Java 线程将会返回到 **RUNNABLE** 状态。 -- 当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到 BLOCKED(阻塞)状态。线程在执行 Runnable 的run()方法之后将会进入到 TERMINATED(终止) 状态。 - -## 上下文切换 - -> 多线程编程中一般线程的个数都大于 CPU 核心的个数,而一个 CPU 核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,**CPU 采取的策略是为每个线程分配时间片并轮转的形式**。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。 -实际上就是任务从保存到再加载的过程就是一次上下文切换。 上下文切换对系统来说意味着消耗大量的 CPU 时间,事实上,可能是操作系统中时间消耗最大的操作。 - -## 直接调用Thread的run方法不行吗? -### 区别 -> Java中启动线程有两种方法,继承Thread类和实现Runnable接口,由于Java无法实现多重继承,所以一般通过实现Runnable接口来创建线程。但是无论哪种方法都可以通过start()和run()方法来启动线程,下面就来介绍一下他们的区别。 - -#### start -通过该方法启动线程的同时**也创建了一个线程,真正实现了多线程**。**无需等待run()方法中的代码执行完毕,就可以接着执行下面的代码**。此时start()的这个线程处于**就绪状态**,当得到CPU的时间片后就会执行其中的run()方法。这个run()方法包含了要执行的这个线程的内容,run()方法运行结束,此线程也就终止了。 - -#### run -通过run方法启动线程其实就是调用一个类中的方法,**当作普通的方法的方式调用**。并没有创建一个线程,程序中依旧只有一个主线程,必须等到run()方法里面的代码执行完毕,才会继续执行下面的代码,这样就没有达到写线程的目的。 - -```java -public class Test { - public static void main(String[] args) { - Thread t = new Thread(){ - public void run() { - cat(); - } - }; - t.run(); - System.out.println("dream"); - } - - static void cat() { - System.out.println("cat"); - } -} -// 结果: -// cat -// dream -``` -如果将`t.run()`改成`t.start()`; -```java -dream -cat -``` - -### 探究一下源码 -看一下start方法源码: -```java -public synchronized void start() { - if (threadStatus != 0) - throw new IllegalThreadStateException(); - group.add(this); - - boolean started = false; - try { - start0(); - started = true; - } finally { - try { - if (!started) { - group.threadStartFailed(this); - } - } catch (Throwable ignore) { - /* do nothing. If start0 threw a Throwable then - it will be passed up the call stack */ - } - } -} - -private native void start0(); -``` - 可以看到,当一个线程启动的时候,它的状态(threadStatus)被设置为0,如果不为0,则抛出`IllegalThreadStateException`异常。正常的话,将该**线程加入线程组**,最后尝试调用start0方法,**而start0方法是私有的native方法**(Native Method是一个java调用非java代码的接口)。 - -我们再看一下run方法源码: -```java -public void run() { - if (target != null) { - target.run(); - } -} -``` -target的定义:`private Runnable target;` -所以其实就是一个Runnable接口,正如上面代码中new Thread的部分,其实我们就是在实现它的run()方法。所以如果**直接调用run,就和一个普通的方法没什么区别,是不会创建新的线程的,因为压根就没执行start0方法**。 diff --git "a/Java/codes/thread/\347\272\277\347\250\213\347\232\204\345\220\257\345\212\250\346\226\271\345\274\217.md" "b/Java/codes/thread/\347\272\277\347\250\213\347\232\204\345\220\257\345\212\250\346\226\271\345\274\217.md" deleted file mode 100644 index 08795ae1..00000000 --- "a/Java/codes/thread/\347\272\277\347\250\213\347\232\204\345\220\257\345\212\250\346\226\271\345\274\217.md" +++ /dev/null @@ -1,53 +0,0 @@ -# 线程的启动方式 - -## Thread -```java -public class Test extents Thread { - public void run() { - // 重写Thread的run方法 - System.out.println("dream"); - } - - public static void main(String[] args) { - new Test().start(); - } -} -``` - -## Runnable -```java -public class Test { - public static void main(String[] args) { - new Thread(() -> { - System.out.println("dream"); - }).start(); - } -} -``` - -## Callable -```java -public class Test { - public static void main(String[] args) { - // FutureTask 构造方法包装了Callable和Runnable。 - FutureTask task = new FutureTask<>(() -> { - System.out.println("dream"); - return 0; - }); - new Thread(task).start(); - } -} -``` - -## 线程池 -```java -public class Test { - public static void main(String[] args) { - ExecutorService threadPool = Executors.newFixedThreadPool(1); - threadPool.submit(() -> { - System.out.println("dream"); - }); - threadPool.shutdown(); - } -} -``` diff --git "a/Java/linux/linux-\345\237\272\347\241\200.md" "b/Java/linux/linux-\345\237\272\347\241\200.md" deleted file mode 100644 index fb815ec4..00000000 --- "a/Java/linux/linux-\345\237\272\347\241\200.md" +++ /dev/null @@ -1,695 +0,0 @@ -## 引言 - -**linux-基础** - -## 常用操作以及概念 - -### 快捷键 - -- Tab: 命令和文件名补全; -- Ctrl+C: 中断正在运行的程序; -- Ctrl+D: 结束键盘输入(End Of File,EOF) - -### 求助 - - - -#### --help - -指令的基本用法与选项介绍。 - -#### man - -man 是 manual 的缩写,将指令的具体信息显示出来。 - -#### info - -info 与 man 类似,但是 info 将文档分成一个个页面,每个页面可以进行跳转。 - -#### doc - -/usr/share/doc 存放着软件的一整套说明文件。 - -### 关机 - -#### who - -在关机前需要先使用 who 命令查看有没有其它用户在线。 - -#### sync - -为了加快对磁盘文件的读写速度,位于内存中的文件数据不会立即同步到磁盘上,因此关机之前需要先进行 sync 同步操作。 - -#### shutdown - -```bash -## shutdown [-krhc] 时间 [信息] --k : 不会关机,只是发送警告信息,通知所有在线的用户 --r : 将系统的服务停掉后就重新启动 --h : 将系统的服务停掉后就立即关机 --c : 取消已经在进行的 shutdown 指令内容 -``` - -### PATH - -可以在环境变量 PATH 中声明可执行文件的路径,路径之间用 : 分隔。 - -`/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin` - -### sudo - -sudo 允许一般用户使用 root 可执行的命令,不过只有在 /etc/sudoers 配置文件中添加的用户才能使用该指令. - -### 包管理工具 - -RPM 和 DPKG 为最常见的两类软件包管理工具: - -- RPM 全称为 Redhat Package Manager,最早由 Red Hat 公司制定实施,随后被 GNU 开源操作系统接受并成为很多 Linux 系统 (RHEL) 的既定软件标准。 -- 与 RPM 进行竞争的是基于 Debian 操作系统 (Ubuntu) 的 DEB 软件包管理工具 DPKG,全称为 Debian Package,功能方面与 RPM 相似。 - -YUM 基于 RPM,具有依赖管理功能,并具有软件升级的功能。 - -### 发行版 - -Linux 发行版是 Linux 内核及各种应用软件的集成版本。 - -| 基于的包管理工具 | 商业发行版 | 社区发行版 | -| :--------------: | :--------: | :-------------: | -| RPM | Red Hat | Fedora / CentOS | -| DPKG | Ubuntu | Debian | - -### VIM - -- 一般指令模式(Command mode): VIM 的默认模式,可以用于移动游标查看内容; -- 编辑模式(Insert mode): 按下 "i" 等按键之后进入,可以对文本进行编辑; -- 指令列模式(Bottom-line mode): 按下 ":" 按键之后进入,用于保存退出等操作。 - -在指令列模式下,有以下命令用于离开或者保存文件。 - -| 命令 | 作用 | -| :--: | :----------------------------------------------------------: | -| :w | 写入磁盘 | -| :w! | 当文件为只读时,强制写入磁盘。到底能不能写入,与用户对该文件的权限有关 | -| :q | 离开 | -| :q! | 强制离开不保存 | -| :wq | 写入磁盘后离开 | -| :wq! | 强制写入磁盘后离开 | - -## 磁盘 - -### 磁盘接口 - -#### IDE - -IDE(ATA)全称 Advanced Technology Attachment,接口速度最大为 133MB/s,因为并口线的抗干扰性太差,且排线占用空间较大,不利电脑内部散热,已逐渐被 SATA 所取代。 - -#### SATA - -SATA 全称 Serial ATA,也就是使用串口的 ATA 接口,抗干扰性强,且对数据线的长度要求比 ATA 低很多,支持热插拔等功能。SATA-II 的接口速度为 300MiB/s,而新的 SATA-III 标准可达到 600MiB/s 的传输速度。SATA 的数据线也比 ATA 的细得多,有利于机箱内的空气流通,整理线材也比较方便。 - -#### SCSI - -SCSI 全称是 Small Computer System Interface(小型机系统接口),经历多代的发展,从早期的 SCSI-II 到目前的 Ultra320 SCSI 以及 Fiber-Channel(光纤通道),接口型式也多种多样。SCSI 硬盘广为工作站级个人电脑以及服务器所使用,因此会使用较为先进的技术,如碟片转速 15000rpm 的高转速,且传输时 CPU 占用率较低,但是单价也比相同容量的 ATA 及 SATA 硬盘更加昂贵。 - -#### SAS - -SAS(Serial Attached SCSI)是新一代的 SCSI 技术,和 SATA 硬盘相同,都是采取序列式技术以获得更高的传输速度,可达到 6Gb/s。此外也透过缩小连接线改善系统内部空间等。 - -## 分区 - -### 分区表 - -磁盘分区表主要有两种格式,一种是限制较多的 MBR 分区表,一种是较新且限制较少的 GPT 分区表。 - -#### MBR - -MBR 中,第一个扇区最重要,里面有主要开机记录(Master boot record, MBR)及分区表(partition table),其中主要开机记录占 446 bytes,分区表占 64 bytes。 - -分区表只有 64 bytes,最多只能存储 4 个分区,这 4 个分区为主分区(Primary)和扩展分区(Extended)。其中扩展分区只有一个,它使用其它扇区用记录额外的分区表,因此通过扩展分区可以分出更多分区,这些分区称为逻辑分区。 - -Linux 也把分区当成文件,分区文件的命名方式为: 磁盘文件名 + 编号,例如 /dev/sda1。注意,逻辑分区的编号从 5 开始。 - -#### GPT - -不同的磁盘有不同的扇区大小,例如 512 bytes 和最新磁盘的 4 k。GPT 为了兼容所有磁盘,在定义扇区上使用逻辑区块地址(Logical Block Address, LBA),LBA 默认大小为 512 bytes。 - -GPT 第 1 个区块记录了主要开机记录(MBR),紧接着是 33 个区块记录分区信息,并把最后的 33 个区块用于对分区信息进行备份。这 33 个区块第一个为 GPT 表头纪录,这个部份纪录了分区表本身的位置与大小和备份分区的位置,同时放置了分区表的校验码 (CRC32),操作系统可以根据这个校验码来判断 GPT 是否正确。若有错误,可以使用备份分区进行恢复。 - -GPT 没有扩展分区概念,都是主分区,每个 LAB 可以分 4 个分区,因此总共可以分 4 * 32 = 128 个分区。 - -MBR 不支持 2.2 TB 以上的硬盘,GPT 则最多支持到 233 TB = 8 ZB。 - -### 开机检测程序 - -#### BIOS - -BIOS(Basic Input/Output System,基本输入输出系统),它是一个固件(嵌入在硬件中的软件),BIOS 程序存放在断电后内容不会丢失的只读内存中。 - -BIOS 是开机的时候计算机执行的第一个程序,这个程序知道可以开机的磁盘,并读取磁盘第一个扇区的主要开机记录(MBR),由主要开机记录(MBR)执行其中的开机管理程序,这个开机管理程序会加载操作系统的核心文件。 - -主要开机记录(MBR)中的开机管理程序提供以下功能: 选单、载入核心文件以及转交其它开机管理程序。转交这个功能可以用来实现了多重引导,只需要将另一个操作系统的开机管理程序安装在其它分区的启动扇区上,在启动开机管理程序时,就可以通过选单选择启动当前的操作系统或者转交给其它开机管理程序从而启动另一个操作系统。 - -下图中,第一扇区的主要开机记录(MBR)中的开机管理程序提供了两个选单: M1、M2,M1 指向了 Windows 操作系统,而 M2 指向其它分区的启动扇区,里面包含了另外一个开机管理程序,提供了一个指向 Linux 的选单。 - -安装多重引导,最好先安装 Windows 再安装 Linux。因为安装 Windows 时会覆盖掉主要开机记录(MBR),而 Linux 可以选择将开机管理程序安装在主要开机记录(MBR)或者其它分区的启动扇区,并且可以设置开机管理程序的选单。 - -#### UEFI - -BIOS 不可以读取 GPT 分区表,而 UEFI 可以。 - -## 文件系统 - -### 分区与文件系统 - -对分区进行格式化是为了在分区上建立文件系统。一个分区通常只能格式化为一个文件系统,但是磁盘阵列等技术可以将一个分区格式化为多个文件系统。 - -### 组成 - -最主要的几个组成部分如下: - -- inode: 一个文件占用一个 inode,记录文件的属性,同时记录此文件的内容所在的 block 编号; -- block: 记录文件的内容,文件太大时,会占用多个 block。 -- superblock: 记录文件系统的整体信息,包括 inode 和 block 的总量、使用量、剩余量,以及文件系统的格式与相关信息等; -- block bitmap: 记录 block 是否被使用的位域。 - -### 文件读取 - -对于 Ext2 文件系统,当要读取一个文件的内容时,先在 inode 中去查找文件内容所在的所有 block,然后把所有 block 的内容读出来。 - -### 磁盘碎片 - -指一个文件内容所在的 block 过于分散。 - -### Block - -在 Ext2 文件系统中所支持的 block 大小有 1K,2K 及 4K 三种,不同的大小限制了单个文件和文件系统的最大大小。 - -一个 block 只能被一个文件所使用,未使用的部分直接浪费了。因此如果需要存储大量的小文件,那么最好选用比较小的 block。 - -### inode - -inode 具体包含以下信息: - -- 权限 (read/write/excute); -- 拥有者与群组 (owner/group); -- 容量; -- 建立或状态改变的时间 (ctime); -- 最近一次的读取时间 (atime); -- 最近修改的时间 (mtime); -- 定义文件特性的旗标 (flag),如 SetUID...; -- 该文件真正内容的指向 (pointer)。 - -inode 具有以下特点: - -- 每个 inode 大小均固定为 128 bytes (新的 ext4 与 xfs 可设定到 256 bytes); -- 每个文件都仅会占用一个 inode。 - -inode 中记录了文件内容所在的 block 编号,但是每个 block 非常小,一个大文件随便都需要几十万的 block。而一个 inode 大小有限,无法直接引用这么多 block 编号。因此引入了间接、双间接、三间接引用。间接引用是指,让 inode 记录的引用 block 块记录引用信息。 - -### 目录 - -建立一个目录时,会分配一个 inode 与至少一个 block。block 记录的内容是目录下所有文件的 inode 编号以及文件名。 - -可以看出文件的 inode 本身不记录文件名,文件名记录在目录中,因此新增文件、删除文件、更改文件名这些操作与目录的 w 权限有关。 - -### 日志 - -如果突然断电,那么文件系统会发生错误,例如断电前只修改了 block bitmap,而还没有将数据真正写入 block 中。 - -ext3/ext4 文件系统引入了日志功能,可以利用日志来修复文件系统。 - -### 挂载 - -挂载利用目录作为文件系统的进入点,也就是说,进入目录之后就可以读取文件系统的数据。 - -### 目录配置 - -为了使不同 Linux 发行版本的目录结构保持一致性,Filesystem Hierarchy Standard (FHS) 规定了 Linux 的目录结构。最基础的三个目录如下: - -- / (root, 根目录) -- /usr (unix software resource): 所有系统默认软件都会安装到这个目录; -- /var (variable): 存放系统或程序运行过程中的数据文件。 - -## 文件 - -### 文件属性 - -用户分为三种: 文件拥有者、群组以及其它人,对不同的用户有不同的文件权限。 - -使用 ls 查看一个文件时,会显示一个文件的信息,例如 `drwxr-xr-x. 3 root root 17 May 6 00:14 .config`,对这个信息的解释如下: - -- drwxr-xr-x: 文件类型以及权限,第 1 位为文件类型字段,后 9 位为文件权限字段 -- 3: 链接数 -- root: 文件拥有者 -- root: 所属群组 -- 17: 文件大小 -- May 6 00:14: 文件最后被修改的时间 -- .config: 文件名 - -常见的文件类型及其含义有: - -- d: 目录 -- -: 文件 -- l: 链接文件 - -9 位的文件权限字段中,每 3 个为一组,共 3 组,每一组分别代表对文件拥有者、所属群组以及其它人的文件权限。一组权限中的 3 位分别为 r、w、x 权限,表示可读、可写、可执行。 - -文件时间有以下三种: - -- modification time (mtime): 文件的内容更新就会更新; -- status time (ctime): 文件的状态(权限、属性)更新就会更新; -- access time (atime): 读取文件时就会更新。 - -### 文件与目录的基本操作 - -#### ls - -列出文件或者目录的信息,目录的信息就是其中包含的文件。 - -```bash -## ls [-aAdfFhilnrRSt] file|dir --a : 列出全部的文件 --d : 仅列出目录本身 --l : 以长数据串行列出,包含文件的属性与权限等等数据 - -``` - -#### cd - -更新当前目录 - -`cd [相对路径或者绝对路径]` - -#### mkdir - -创建目录 - -```bash -## mkdir [-mp] 目录名称 --m : 配置目录权限 --p : 递归创建目录 -``` - -#### rmdir - -删除目录,目录必须为空 - -```bash -rmdir [-p] 目录名称 --p : 递归删除目录 -``` - -#### touch - -更新文件时间或者建立新文件 - -```bash -## touch [-acdmt] filename --a : 更新 atime --c : 更新 ctime,若该文件不存在则不建立新文件 --m : 更新 mtime --d : 后面可以接更新日期而不使用当前日期,也可以使用 --date="日期或时间" --t : 后面可以接更新时间而不使用当前时间,格式为[YYYYMMDDhhmm] - -``` - -#### cp - -复制文件 - -如果源文件有两个以上,则目的文件一定要是目录才行。 - -```bash -cp [-adfilprsu] source destination --a : 相当于 -dr --preserve=all 的意思,至于 dr 请参考下列说明 --d : 若来源文件为链接文件,则复制链接文件属性而非文件本身 --i : 若目标文件已经存在时,在覆盖前会先询问 --p : 连同文件的属性一起复制过去 --r : 递归持续复制 --u : destination 比 source 旧才更新 destination,或 destination 不存在的情况下才复制 ---preserve=all : 除了 -p 的权限相关参数外,还加入 SELinux 的属性, links, xattr 等也复制了 - -``` - -#### rm - -删除文件 - -```bash -## rm [-fir] 文件或目录 --r : 递归删除 -``` - -#### mv - -移动文件 - -```bash -## mv [-fiu] source destination -## mv [options] source1 source2 source3 .... directory --f : force 强制的意思,如果目标文件已经存在,不会询问而直接覆盖 -``` - -### 修改权限 - -可以将一组权限用数字来表示,此时一组权限的 3 个位当做二进制数字的位,从左到右每个位的权值为 4、2、1,即每个权限对应的数字权值为 r : 4、w : 2、x : 1。 - -`## chmod [-R] xyz dirname/filename` - -示例: 将 .bashrc 文件的权限修改为 -rwxr-xr--。 - -`## chmod 754 .bashrc` - -也可以使用符号来设定权限。 - -```bash -## chmod [ugoa] [+-=] [rwx] dirname/filename -- u: 拥有者 -- g: 所属群组 -- o: 其他人 -- a: 所有人 -- +: 添加权限 -- -: 移除权限 -- =: 设定权限 -``` - -示例: 为 .bashrc 文件的所有用户添加写权限。 - -`## chmod a+w .bashrc` - -### 文件默认权限 - -- 文件默认权限: 文件默认没有可执行权限,因此为 666,也就是 -rw-rw-rw- 。 -- 目录默认权限: 目录必须要能够进入,也就是必须拥有可执行权限,因此为 777 ,也就是 drwxrwxrwx。 - -可以通过 umask 设置或者查看文件的默认权限,通常以掩码的形式来表示,例如 002 表示其它用户的权限去除了一个 2 的权限,也就是写权限,因此建立新文件时默认的权限为 -rw-rw-r--。 - -### 目录的权限 - -文件名不是存储在一个文件的内容中,而是存储在一个文件所在的目录中。因此,拥有文件的 w 权限并不能对文件名进行修改。 - -目录存储文件列表,一个目录的权限也就是对其文件列表的权限。因此,目录的 r 权限表示可以读取文件列表;w 权限表示可以修改文件列表,具体来说,就是添加删除文件,对文件名进行修改;x 权限可以让该目录成为工作目录,x 权限是 r 和 w 权限的基础,如果不能使一个目录成为工作目录,也就没办法读取文件列表以及对文件列表进行修改了。 - -### 链接 - -```bash -## ln [-sf] source_filename dist_filename --s : 默认是 hard link,加 -s 为 symbolic link --f : 如果目标文件存在时,先删除目标文件 -``` - -### 实体链接 - -在目录下创建一个条目,记录着文件名与 inode 编号,这个 inode 就是源文件的 inode。 - -删除任意一个条目,文件还是存在,只要引用数量不为 0。 - -有以下限制: 不能跨越文件系统、不能对目录进行链接。 - -```bash -## ln /etc/crontab . -## ll -i /etc/crontab crontab -34474855 -rw-r--r--. 2 root root 451 Jun 10 2014 crontab -34474855 -rw-r--r--. 2 root root 451 Jun 10 2014 /etc/crontab -``` - -### 符号链接 - -符号链接文件保存着源文件所在的绝对路径,在读取时会定位到源文件上,可以理解为 Windows 的快捷方式。 - -当源文件被删除了,链接文件就打不开了。 - -可以为目录建立链接。 - -```bash -## ll -i /etc/crontab /root/crontab2 -34474855 -rw-r--r--. 2 root root 451 Jun 10 2014 /etc/crontab -53745909 lrwxrwxrwx. 1 root root 12 Jun 23 22:31 /root/crontab2 -> /etc/crontab -``` - -### 获取文件内容 - -#### cat - -取得文件内容。 - -```bash -## cat [-AbEnTv] filename --n : 打印出行号,连同空白行也会有行号,-b 不会 -``` - -#### tac - -是 cat 的反向操作,从最后一行开始打印。 - -#### more - -和 cat 不同的是它可以一页一页查看文件内容,比较适合大文件的查看。 - -#### less - -和 more 类似,但是多了一个向前翻页的功能。 - -#### head - -取得文件前几行。 - -```bash -## head [-n number] filename --n : 后面接数字,代表显示几行的意思 -``` - -#### tail - -是head的反向操作,之水取得是后几行 - -#### od - -以字符或者十六进制的形式显示二进制文件。 - -### 指令与文件搜索 - -#### which - -指令搜索 - -```bash -## which [-a] command --a : 将所有指令列出,而不是只列第一个 -``` - -#### whereis - -文件搜索。速度比较快,因为它只搜索几个特定的目录。 - -```bash -## whereis [-bmsu] dirname/filename -``` - -#### locate - -文件搜索。可以用关键字或者正则表达式进行搜索。 - -locate 使用 /var/lib/mlocate/ 这个数据库来进行搜索,它存储在内存中,并且每天更新一次,所以无法用 locate 搜索新建的文件。可以使用 updatedb 来立即更新数据库。 - -```bash -## locate [-ir] keyword --r: 正则表达式 -``` - -#### find - -文件搜索。可以使用文件的属性和权限进行搜索。 - -```bash -## find [basedir] [option] -example: find . -name "shadow*" -``` - -## 压缩与打包 - -### 压缩文件名 - -#### gzip - -gzip 是 Linux 使用最广的压缩指令,可以解开 compress、zip 与 gzip 所压缩的文件。 - -经过 gzip 压缩过,源文件就不存在了。 - -有 9 个不同的压缩等级可以使用。 - -可以使用 zcat、zmore、zless 来读取压缩文件的内容。 - -```bash -$ gzip [-cdtv#] filename --c : 将压缩的数据输出到屏幕上 --d : 解压缩 --t : 检验压缩文件是否出错 --v : 显示压缩比等信息 --## : ## 为数字的意思,代表压缩等级,数字越大压缩比越高,默认为 6 -``` - -#### bzip2 - -提供比 gzip 更高的压缩比。 - -查看命令: bzcat、bzmore、bzless、bzgrep。 - -#### xz - -提供比 bzip2 更佳的压缩比。 - -可以看到,gzip、bzip2、xz 的压缩比不断优化。不过要注意的是,压缩比越高,压缩的时间也越长。 - -查看命令: xzcat、xzmore、xzless、xzgrep。 - -### 打包 - -压缩指令只能对一个文件进行压缩,而打包能够将多个文件打包成一个大文件。tar 不仅可以用于打包,也可以使用 gip、bzip2、xz 将打包文件进行压缩。 - -```bash -$ tar [-z|-j|-J] [cv] [-f 新建的 tar 文件] filename... ==打包压缩 -$ tar [-z|-j|-J] [tv] [-f 已有的 tar 文件] ==查看 -$ tar [-z|-j|-J] [xv] [-f 已有的 tar 文件] [-C 目录] ==解压缩 --z : 使用 zip; --j : 使用 bzip2; --J : 使用 xz; --c : 新建打包文件; --t : 查看打包文件里面有哪些文件; --x : 解打包或解压缩的功能; --v : 在压缩/解压缩的过程中,显示正在处理的文件名; --f : filename: 要处理的文件; --C 目录 : 在特定目录解压缩。 -``` - -| 使用方式 | 命令 | -| :------: | ----------------------------------------------------- | -| 打包压缩 | tar -jcv -f filename.tar.bz2 要被压缩的文件或目录名称 | -| 查 看 | tar -jtv -f filename.tar.bz2 | -| 解压缩 | tar -jxv -f filename.tar.bz2 -C 要解压缩的目录 | - -## bash - -可以通过 Shell 请求内核提供服务,Bash 正是 Shell 的一种。 - -### 特性 - -- 命令历史:记录使用过的命令 -- 命令与文件补全:快捷键:tab -- shell scripts -- 通配符: 例如 ls -l /usr/bin/X* 列出 /usr/bin 下面所有以 X 开头的文件 - -### 变量操作 - -对一个变量赋值直接使用 =。 - -对变量取用需要在变量前加上 $ ,也可以用 ${} 的形式; - -输出变量使用 echo 命令。 - -```bash -$ x=abc -$ echo $x -$ echo ${x} -``` - -变量内容如果有空格,必须使用双引号或者单引号。 - -- 双引号内的特殊字符可以保留原本特性,例如 x="lang is $LANG",则 x 的值为 lang is zh_TW.UTF-8; -- 单引号内的特殊字符就是特殊字符本身,例如 x='lang is $LANG',则 x 的值为 lang is $LANG。 - -可以使用 `指令` 或者 $(指令) 的方式将指令的执行结果赋值给变量。例如 version=$(uname -r),则 version 的值为 4.15.0-22-generic。 - -可以使用 `指令` 或者 $(指令) 的方式将指令的执行结果赋值给变量。例如 version=$(uname -r),则 version 的值为 4.15.0-22-generic。 - -Bash 的变量可以声明为数组和整数数字。注意数字类型没有浮点数。如果不进行声明,默认是字符串类型。变量的声明使用 declare 命令: - -```bash -$ declare [-aixr] variable --a : 定义为数组类型 --i : 定义为整数类型 --x : 定义为环境变量 --r : 定义为 readonly 类型 -``` - -对数组操作 - -```bash -$ array[1]=a -$ array[2]=b -$ echo ${array[1]} -``` - -## 正则 - -g/re/p(globally search a regular expression and print),使用正则表示式进行全局查找并打印。 - -```bash -$ grep [-acinv] [--color=auto] 搜寻字符串 filename --c : 统计个数 --i : 忽略大小写 --n : 输出行号 --v : 反向选择,也就是显示出没有 搜寻字符串 内容的那一行 ---color=auto : 找到的关键字加颜色显示 -``` - -示例: 把含有 the 字符串的行提取出来(注意默认会有 --color=auto 选项,因此以下内容在 Linux 中有颜色显示 the 字符串) - -```bash -$ grep -n 'the' regular_express.txt -8:I can't finish the test. -12:the symbol '*' is represented as start. -15:You are the best is mean you are the no. 1. -16:The world Happy is the same with "glad". -18:google is the best tools for search keyword -``` - -## 进程管理 - -### 查看进程 - -#### ps - -查看某个时间点的进程信息 - -示例一: 查看自己的进程`ps -l` - -示例二: 查看系统所有进程`ps aux` - -示例三: 查看特定的进程`ps aux | grep python` - -#### top - -实时显示进程信息 - -示例: 两秒钟刷新一次`top -d 2` - -#### pstree - -查看进程树 - -示例: 查看所有进程树`pstree -A` - -#### netstat - -查看占用端口的进程 - -示例: 查看特定端口的进程`netstat -anp | grep port` - -### 孤儿进程 - -一个父进程退出,而它的一个或多个子进程还在运行,那么这些子进程将成为孤儿进程。 - -孤儿进程将被init进程(进程号为1)所收养,并有init进程对它们完成状态收集工作。 - -由于孤儿进程会被init进程收养,所以孤儿进程不会对系统造成危害。 - -### 僵尸进程 - -一个子进程的进程描述符在子进程退出时不会释放,只有当父进程通过wait()或者waitpid()获取了子进程信息后才会释放。如果子进程退出,而父进程并没有调用wait()或waitpid(),那么子进程的进程描述符仍然保存在系统中,这种进程称之为僵尸进程。 - -僵尸进程通过 ps 命令显示出来的状态为 Z(zombie)。 - -系统所能使用的进程号是有限的,如果产生大量僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程。 - -要消灭系统中大量的僵尸进程,只需要将其父进程杀死,此时僵尸进程就会变成孤儿进程,从而被 init 所收养,这样 init 就会释放所有的僵尸进程所占有的资源,从而结束僵尸进程。 \ No newline at end of file diff --git "a/Java/other/\345\270\270\347\224\250sql.md" "b/Java/other/\345\270\270\347\224\250sql.md" deleted file mode 100644 index 43109e85..00000000 --- "a/Java/other/\345\270\270\347\224\250sql.md" +++ /dev/null @@ -1,1247 +0,0 @@ - -### 创建数据库并制定编码 - -- ```sql - Create database 数据库名 default character set utf8; - Create database ssm default character set utf8; - ``` - -### 删除数据库 - -- ```sql - drop database 数据名; - drop database ssm; - ``` - - ### 创建表 - -- 一般情况: - -- ```sql - Create table 表名 ( - 列名 类型 约束 auto_increment comment '备注'; - ); - - create tablie flower ( - id int(10) primary key auto_increment comment '编号', - name varchar(30) not null comment '花名', - price float not null comment '价格', - production varchar(30) not null comment '原产地' - ); - - create tablie flower ( - `id` int(10) not null auto_increment comment '编号', - `name` varchar(30) not null comment '花名', - `price` float not null comment '价格', - `production` varchar(30) not null comment '原产地', - primary key (`id`) - ); - ``` - -### 插入信息 - -- ```sql - insert into 表明(姓名,性别,年龄) values('李一', '女', '18'); - insert into `flower` (`id`, `name`, `price`, `production`) values (defalut, '矮牵牛', 2.5, '南美阿根廷') - ``` - -### 查询信息 - -- ```sql - select * from 表名; - select * from flower; - select * from 表名 where ID=5 - select * from flower where ID=1 - ``` - -### 更新信息 - -- ```sql - update 表名 set name='李一' where name='王五'; - update flower set price=1.2 where id=1; - ``` - -### 删除信息 - -- ```sql - delete from 表名 where id=1; - delete from flower where id=1; - ``` - -### 删除表 - -- ```sql - drop table 表名; - drop table flower; - ``` - -### 查看表信息 - -- ```sql - desc 表名; - desc flower; - ``` - -### 一对多关系 - -- 可能是写的不熟练吧,所以要做个笔记,以免之后再次忘掉。 - -- 例子:假如有这样的一对多关系,首先有一个User模型,其中该模型有id、name和gender字段。还有一个Artical模型,其中该模型有id、title、content和user_id字段,而user_id则是外键。如下图: - -- ![](https://raw.githubusercontent.com/DreamCats/PicBed/master/20191121170733.png) - -- 那么在mysql或者mysql的一些工具下创建输入以下代码: - -- ```sql - create database demo default character set utf8; -- 创建数据库 - - use demo; - - -- 创建user表格 - create table user ( - `id` int(10) not null auto_increment comment '编号', - `name` varchar(16) not null comment '姓名', - `gender` varchar(4) not null comment '性别', - primary key (`id`) - ); - - -- 创建artical - create table artical ( - `id` int(10) not null auto_increment comment '文章编号', - `title` varchar(32) not null comment '标题', - `content` text default null comment '内容', - `user_id` int(10) not null comment '用户编号', - primary key (`id`), - key (`user_id`), - constraint `artical_idfk_1` foreign key (`user_id`) references `user` (`id`) -- 这句话非常重要 constraint ··· 约束 - ); - - -- 在user中插入数据 - insert into user (`id`,`name`,`gender`) values(default, 'Maifeng', '男'); - insert into user (`id`,`name`,`gender`) values(default, 'Liumeng', '女'); - - -- 在artical中插入数据 - insert into `artical` (`id`, `title`, `content`, `user_id`) values (default, '我是mm', 'null', 1); - insert into `artical` (`id`, `title`, `content`, `user_id`) values (default, '我是xxx', 'null', 2); - ``` - -- 如下图user和artical表 - -- ![](https://raw.githubusercontent.com/DreamCats/PicBed/master/20191121170817.png) - -- ![](https://raw.githubusercontent.com/DreamCats/PicBed/master/20191121170856.png) - - - -### 更改auto_increment - -- ```sql - alter table 表名 auto_increment=5; - alter table flower auto_increment=5; - ``` - -- - -### 常用sql语句 - -#### 数据库 - -```mysql -# 查看所有数据库 -show databases; - -# 创建一个数据库 -create database demo; - -# 删除一个数据库 -drop dababase demo; - -# 使用这个数据库 -use demo; - -``` - -#### 表 - -```mysql -# 查看所有的表 -show tables; - -# 创建一个表 -create table n(`id` int, `name` varchar(10)); -create table m(`id` int, `name` varchar(10), primary key(`id`), foreign key(`id`) references n(`id`), unique); -create table m(`id` int, `name` varchar(10)); - -# 直接将查询结果导入或复制到新创建的表 -create table n select * from m; - -# 将创建的表与一个存在的表的数据结构类似 -create table m like n; - -# 创建一个临时表 -# 临时表将在你连接MySQL期间存在。当断开连接时,MySQL将自动删除表并释放所用的空间。也可手动删除。 -create temporary table l(`id` int, name varchar(10)); -# 直接将查询结果导入或复制到新的临时表 -create temporary table tt select * from n; - -# 删除一个已经存在的表 -drop table if exists m; - -# 更改存在表的名称 -alter table n rename m; -rename table n to m; - -# 查看表的结构(5种,效果相同) -desc n; -describe n; -show columns in n; -show columns from n; -explain n; -# 查看表的创建语句 -show create table n; -``` - -#### 表的结构 - -```mysql -# 添加字段 -alter table n add age varchar(2); -# 删除字段 -alter table n drop age; -# 更改字段属性和属性 -alter table n change age a int; -# 只更改字段属性 -alter table n modify age varchar(7); -``` - -#### 表的数据 - -```mysql -# 增加数据 -insert into n values(1, 'tom', '23'), (2, 'john', '22'); -insert into n select * from n; # 把数据复制一遍重新插入 -# 删除数据 -delete from n where id = 2; -# 更改数据 -update n set name = 'tom' where id = 2; -# 数据查找 -select * from n where name like '%h%'; -# 数据排序(反序) -select * from n order by name, id desc; -``` - -#### 键 - -```mysql -# 添加主键 -alter table n add primary key(id); -# 删除主键 -alter table n drop primary key; -# 添加外键 -alter table m add foreign key(id) references n(id); # 自动生成键名m_ibfk_1 -# 自定义名称的外键 -alter table m add constraint fk_id foreign key(id) references n(id); -# 删除外键 -alter table m drop foreign key `fk_id`; -# 修改外键 -alter table m drop foreign key `fk_id`, add constraint fk_id2 foreign key(id) references n(id); -# 添加唯一键 -alter table n add unique (name); -alter table n add unique u_name (name); -alter table n add unique index u_name (name); -alter table n add constraint u_name unique (name); -create unique index u_name on n(name); -# 添加索引 -alter table n add index (age); -alter table n add index i_age (age); -create index i_age on n(age); - -# 删除索引或唯一键 -drop index u_name on n; -drop index i_age on n; -``` - -#### 视图 - -```mysql -# 创建视图 -create view v as select id, name from n; -create view v(id, name) as select id, name from n; -# 查看视图(与表操作类似) -select * from v; -desc v; -# 查看视图语句 -show create view v; -# 更改视图 -CREATE OR REPLACE VIEW v AS SELECT name, age FROM n; -ALTER VIEW v AS SELECT name FROM n; -# 删除视图 -drop view if exists v; -``` - -#### 联接 - -```mysql -# 内联接 -select * from m inner join n on m.id = n.id; -# 左外连接 -select * from m left join n on m.id = n.id; -# 右外连接 -select * from m right join n on m.id = n.id; -# 交叉连接 -select * from m cross join n; # 标准写法 -select * from m,n; -# 类似于全连接full join 的联接用法 -select id, name from m -union -select id, name from n -``` - -#### 函数 - -```mysql -# 聚合函数 -select count(id) as total from n; # 总数 -select sum(age) as all_age from n; # 总和 -select avg(age) as all_age from n; # 平均值 -select max(age) as all_age from n; # 最大值 -select min(age) as all_age from n; # 最小值 -# 数学函数 -select abs(-5); # 绝对值 -select bin(15), oct(15), hex(15); # 二进制,八进制,十六进制 -SELECT pi(); # 圆周率3.141593 -SELECT ceil(5.5); # 大于x的最小整数值6 -SELECT floor(5.5); # 小于x的最大整数值5 -SELECT greatest(3,1,4,1,5,9,2,6); # 返回集合中最大的值9 -SELECT least(3,1,4,1,5,9,2,6); # 返回集合中最小的值1 -SELECT mod(5,3); # 余数2 -SELECT rand(); # 返回0到1内的随机值,每次不一样 -SELECT rand(5); # 提供一个参数(种子)使RAND()随机数生成器生成一个指定的值。 -SELECT round(1415.1415); # 四舍五入1415 -SELECT round(1415.1415, 3); # 四舍五入三位数1415.142 -SELECT round(1415.1415, -1); # 四舍五入整数位数1420 -SELECT truncate(1415.1415, 3); # 截短为3位小数1415.141 -SELECT truncate(1415.1415, -1); # 截短为-1位小数1410 -SELECT sign(-5); # 符号的值负数-1 -SELECT sign(5); # 符号的值正数1 -SELECT sqrt(9); # 平方根3 -SELECT sqrt(9); # 平方根3 -# 字符串函数 -SELECT concat('a', 'p', 'p', 'le'); # 连接字符串-apple -SELECT concat_ws(',', 'a', 'p', 'p', 'le'); # 连接用','分割字符串-a,p,p,le -SELECT insert('chinese', 3, 2, 'IN'); # 将字符串'chinese'从3位置开始的2个字符替换为'IN'-chINese -SELECT left('chinese', 4); # 返回字符串'chinese'左边的4个字符-chin -SELECT right('chinese', 3); # 返回字符串'chinese'右边的3个字符-ese - -SELECT substring('chinese', 3); # 返回字符串'chinese'第三个字符之后的子字符串-inese -SELECT substring('chinese', -3); # 返回字符串'chinese'倒数第三个字符之后的子字符串-ese -SELECT substring('chinese', 3, 2); # 返回字符串'chinese'第三个字符之后的两个字符-in -SELECT trim(' chinese '); # 切割字符串' chinese '两边的空字符-'chinese' -SELECT ltrim(' chinese '); # 切割字符串' chinese '两边的空字符-'chinese ' -SELECT rtrim(' chinese '); # 切割字符串' chinese '两边的空字符-' chinese' -SELECT repeat('boy', 3); # 重复字符'boy'三次-'boyboyboy' -SELECT reverse('chinese'); # 反向排序-'esenihc' -SELECT length('chinese'); # 返回字符串的长度-7 -SELECT upper('chINese'), lower('chINese'); # 大写小写 CHINESE chinese -SELECT ucase('chINese'), lcase('chINese'); # 大写小写 CHINESE chinese -SELECT position('i' IN 'chinese'); # 返回'i'在'chinese'的第一个位置-3 -SELECT position('e' IN 'chinese'); # 返回'i'在'chinese'的第一个位置-5 -SELECT strcmp('abc', 'abd'); # 比较字符串,第一个参数小于第二个返回负数- -1 -SELECT strcmp('abc', 'abb'); # 比较字符串,第一个参数大于第二个返回正数- 1 -# 时间函数 -SELECT current_date, current_time, now(); # 2018-01-13 12:33:43 2018-01-13 12:33:43 -SELECT hour(current_time), minute(current_time), second(current_time); # 12 31 34 -SELECT year(current_date), month(current_date), week(current_date); # 2018 1 1 -SELECT quarter(current_date); # 1 -SELECT monthname(current_date), dayname(current_date); # January Saturday -SELECT dayofweek(current_date), dayofmonth(current_date), dayofyear(current_date); # 7 13 13 -# 控制流函数 -SELECT if(3>2, 't', 'f'), if(3<2, 't', 'f'); # t f -SELECT ifnull(NULL, 't'), ifnull(2, 't'); # t 2 -SELECT isnull(1), isnull(1/0); # 0 1 是null返回1,不是null返回0 -SELECT nullif('a', 'a'), nullif('a', 'b'); # null a 参数相同或成立返回null,不同或不成立则返回第一个参数 -SELECT CASE 2 - WHEN 1 THEN 'first' - WHEN 2 THEN 'second' - WHEN 3 THEN 'third' - ELSE 'other' - END ; # second -# 系统信息函数 -SELECT database(); # 当前数据库名-test -SELECT connection_id(); # 当前用户id-306 -SELECT user(); # 当前用户-root@localhost -SELECT version(); # 当前mysql版本 -SELECT found_rows(); # 返回上次查询的检索行数 -``` - -#### 用户 - -```mysql -# 增加用户 -CREATE USER 'test'@'localhost' IDENTIFIED BY 'test'; -INSERT INTO mysql.user(Host, User, Password) VALUES ('localhost', 'test', Password('test')); # 在用户表中插入用户信息,直接操作User表不推荐 -# 删除用户 -DROP USER 'test'@'localhost'; -DELETE FROM mysql.user WHERE User='test' AND Host='localhost'; -FLUSH PRIVILEGES ; -# 更改用户密码 -SET PASSWORD FOR 'test'@'localhost' = PASSWORD('test'); -UPDATE mysql.user SET Password=Password('t') WHERE User='test' AND Host='localhost'; -FLUSH PRIVILEGES ; -# 用户授权 -GRANT ALL PRIVILEGES ON *.* TO test@localhost IDENTIFIED BY 'test'; -# 授予用'test'密码登陆成功的test@localhost用户操作所有数据库的所有表的所有的权限 -FLUSH PRIVILEGES ; # 刷新系统权限表,使授予权限生效 -# 撤销用户授权 -REVOKE DELETE ON *.* FROM 'test'@'localhost'; # 取消该用户的删除权限 -``` - -#### 其他语句 - -```mysql -# 查看所有的表信息(包括视图) -SHOW TABLE STATUS; -``` - -### 小破站学习的mysql - -- [美女老师](https://www.bilibili.com/video/av49181542?p=1) -- [sql表](https://blog.csdn.net/GongmissYan/article/details/102937816) - -#### 先贴sql - -```sql -CREATE TABLE `t_user` ( - `id` int(11) unsigned NOT NULL AUTO_INCREMENT, - `user_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '账户', - `password` varchar(50) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '密码', - `gender` varchar(11) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', - `age` int(11) NOT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; -INSERT INTO `t_user` (`id`, `user_name`, `password`, `gender`, `age`) -VALUES - (1, 'abc', '123', '0', 20), - (2, 'a', '123', '0', 20), - (3, 'b', '123', '1', 21), - (4, 'c', '123', '0', 22), - (5, 'dae', '123', '1', 23), - (6, 'e', '123', '0', 24), - (7, 'f', '123', '0', 25); - -``` - -### 基础查询 - -```sql -/* -语法: - select 查询列表 from 表明; -特点: - 1. 查询列表可以是:表中的字段、常量值、表达式、函数 - 2. 查询的结构是一个虚拟的表格 -*/ - /* - 别名 - ①便于理解 - ②如果要查询的字段有重名的情况,使用别名可以区分开来 - - */ - # +号的作用 - /* -select 100+90; 两个操作数都为数值型,则做加法运算 -select '123'+90;只要其中一方为字符型,试图将字符型数值转换成数值型 - 如果转换成功,则继续做加法运算 -select 'john'+90; 如果转换失败,则将字符型数值转换成0 - -select null+10; 只要其中一方为null,则结果肯定为null - */ -# 题目 - -#1. 下面的语句是否可以执行成功 :能 - SELECT - last_name , - job_id , - salary AS sal -FROM - employees; -#2.下面的语句是否可以执行成功 :能 - SELECT - * -FROM - employees; -#3.找出下面语句中的错误 :有个中文标点符号 - SELECT - employee_id , - last_name, - salary * 12 AS "ANNUAL SALARY" -FROM - employees; - -#4.显示表departments的结构,并查询其中的全部数据 -DESC departments ; -SELECT * FROM departments; -#5.显示出表employees中的全部job_id(不能重复) -SELECT DISTINCT job_id FROM employees; - -``` - -### 条件查询 - -```sql -# 2: 条件查询 -/* - -语法: - select - 查询列表 - from - 表名 - where - 筛选条件; - -分类: - 一、按条件表达式筛选 - - 简单条件运算符:> < = != <> >= <= - - 二、按逻辑表达式筛选 - 逻辑运算符: - 作用:用于连接条件表达式 - && || ! - and or not - - &&和and:两个条件都为true,结果为true,反之为false - ||或or: 只要有一个条件为true,结果为true,反之为false - !或not: 如果连接的条件本身为false,结果为true,反之为false - - 三、模糊查询 - like - between and - in - is null -*/ -# 1. 查询年龄大于22的用户信息 -SELECT - * -FROM - `t_user` -WHERE - `age` > 22; - -# 2. 查询年龄不等于22的用户和密码 -SELECT - `user_name`, - `password` -FROM - `t_user` -WHERE - `age` <> 20; - -# 查询年龄在21到23之间的用户名和密码 -SELECT - `user_name`, - `password` -FROM - `t_user` -WHERE - `age` >=21 AND `age` <= 23; -SELECT - `user_name`, - `password`, - `age` -FROM - `t_user` -WHERE - `age` BETWEEN 21 AND 23; - -# 查询年龄21和23的用户名和密码 -SELECT - `user_name`, - `password` -FROM - `t_user` -WHERE - `age` IN(21, 23); - -# 模糊查询 like -/* -特点: -①一般和通配符搭配使用 - 通配符: - % 任意多个字符,包含0个字符 - _ 任意单个字符 -*/ -# 查询用户名包字符a的用户信息 -SELECT - * -FROM - `t_user` -WHERE - `user_name` LIKE '%a%'; - -SELECT - * -FROM - `t_user` -WHERE - `user_name` LIKE '_a%'; - -``` - -### 排序查询 - -```sql -/* -排序 - select 查询列表 - from 表 - order by 排序列表 asc|desc. asc:升序,desc降序 - 可以放单个字段,也可以放多个字段,可以表达式,也可以放函数 - order by 放最后 除limit语句 -*/ -题目: -#1.查询员工的姓名和部门号和年薪,按年薪降序 按姓名升序 -SELECT - last_name, - department_id, - salary*12*(1+IFNULL(commission_pct,0)) 年薪 -FROM - employees -ORDER BY 年薪 DESC, last_name ASC; - -#2.选择工资不在8000到17000的员工的姓名和工资,按工资降序 -SELECT - last_name , - salary -FROM employees -WHERE - salary NOT BETWEEN 8000 AND 17000 -ORDER BY salary DESC ; -#3.查询邮箱中包含e的员工信息,并先按邮箱的字节数降序,再按部门号升序 -SELECT - *,LENGTH(email) -FROM employees -WHERE email LIKE '%e%' -ORDER BY LENGTH(email) DESC,department_id ASC; - - -``` - -### 常见函数 - -```sql -/* - -概念:类似于java的方法,将一组逻辑语句封装在方法体中,对外暴露方法名 -好处:1、隐藏了实现细节 2、提高代码的重用性 -调用:select 函数名(实参列表) 【from 表】; -特点: - ①叫什么(函数名) - ②干什么(函数功能) - -分类: - 1、单行函数 - 如 concat、length、ifnull等 - 2、分组函数 - - 功能:做统计使用,又称为统计函数、聚合函数、组函数 - -常见函数: - 一、单行函数 - 字符函数: - length:获取字节个数(utf-8一个汉字代表3个字节,gbk为2个字节) - concat - substr - instr - trim - upper - lower - lpad - rpad - replace - - 数学函数: - round - ceil - floor - truncate - mod - - 日期函数: - now - curdate - curtime - year - month - monthname - day - hour - minute - second - str_to_date - date_format - 其他函数: - version - database - user - 控制函数 - if - case -*/ -# 1. 字符函数 -SELECT LENGTH('join'); -SELECT LENGTH('张三丰hahaha'); -SHOW VARIABLES LIKE '%char%'; -# 2. 拼接字符串 -SELECT CONCAT(`user_name`, '_', `password`) infos FROM t_user; -#3.upper、lower -SELECT UPPER('john'); -SELECT LOWER('joHn'); -#示例:将姓变大写,名变小写,然后拼接 -SELECT CONCAT(UPPER(`user_name`), LOWER(`user_name`)) infos FROM t_user; -#4.substr、substring -#注意:索引从1开始 -#截取从指定索引处后面所有字符 -SELECT SUBSTR('我是dreamcat',7) out_put; -#截取从指定索引处指定字符长度的字符 -SELECT SUBSTR('我是dreamcat',1,3) out_put; - -SELECT -CONCAT(UPPER(SUBSTR(`user_name`, 1, 1)), '_', LOWER(SUBSTR(`user_name`, 2))) out_put -FROM t_user; -#5.instr 返回子串第一次出现的索引,如果找不到返回0 -SELECT INSTR('我是dreamcat', 'dream') out_put; -#6.trim -SELECT -LENGTH( - TRIM( - ' dreamcat. ' - ) -) out_put; -SELECT TRIM('aa' FROM 'aaaaaaaaa张aaaaaaaaaaaa翠山aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') AS out_put; -#7.lpad 用指定的字符实现左填充指定长度 -SELECT LPAD('殷素素',5,'*') AS out_put; -#8.rpad 用指定的字符实现右填充指定长度 -SELECT RPAD('殷素素',12,'ab') AS out_put; -#9.replace 替换 -SELECT REPLACE('周芷若周芷若周芷若周芷若张无忌爱上了周芷若','周芷若','赵敏') AS out_put; - - -#二、数学函数 -#round 四舍五入 -SELECT ROUND(-1.55); -SELECT ROUND(1.567,2); -#ceil 向上取整,返回>=该参数的最小整数 -SELECT CEIL(-1.02); -SELECT CEIL(1.02); -#floor 向下取整,返回<=该参数的最大整数 -SELECT FLOOR(-9.99); -SELECT FLOOR(9.99); -#truncate 截断 -SELECT TRUNCATE(1.69999,1); -#mod取余 -SELECT MOD(10,-3); -SELECT 10%3; - - -#三、日期函数 -#now 返回当前系统日期+时间 -SELECT NOW(); -#curdate 返回当前系统日期,不包含时间 -SELECT CURDATE(); -#curtime 返回当前时间,不包含日期 -SELECT CURTIME(); -#可以获取指定的部分,年、月、日、小时、分钟、秒 -SELECT YEAR(NOW()) 年; -SELECT YEAR('1998-1-1') 年; -SELECT MONTH(NOW()) 月; -SELECT MONTHNAME(NOW()) 月; -#str_to_date 将字符通过指定的格式转换成日期 -SELECT STR_TO_DATE('1998-3-2','%Y-%c-%d') AS out_put; -#date_format 将日期转换成字符 -SELECT DATE_FORMAT(NOW(),'%y年%m月%d日') AS out_put; - -#四、其他函数 - -SELECT VERSION(); -SELECT DATABASE(); -SELECT USER(); - -#五、流程控制函数 -#1.if函数: if else 的效果 - -SELECT IF(10<5,'大','小'); - -SELECT - *, -IF(`age` < 24, 'young', 'old') 'new age' -FROM - t_user; - -#2.case函数的使用一: switch case 的效果 -/* -java中 -switch(变量或表达式){ - case 常量1:语句1;break; - ... - default:语句n;break; - - -} - -mysql中 - -case 要判断的字段或表达式 -when 常量1 then 要显示的值1或语句1; -when 常量2 then 要显示的值2或语句2; -... -else 要显示的值n或语句n; -end -*/ - -SELECT - *, -CASE `age` -WHEN 21 THEN 21+1 -WHEN 22 THEN 22+2 -WHEN 23 THEN 23+3 -ELSE age -END 'NEW age' -FROM - t_user; -题目: -#1. 显示系统时间(注:日期+时间) -SELECT NOW(); - -#2. 查询员工号,姓名,工资,以及工资提高百分之20%后的结果(new salary) -SELECT - employee_id , - last_name , - salary , - salary * 1.2 'new salary' -FROM employees; - -#3. 将员工的姓名按首字母排序,并写出姓名的长度(length) -SELECT - LENGTH (last_name) 长度, SUBSTR(last_name,1,1) 首字母, last_name -FROM employees -ORDER BY 首字母; - -#4. 做一个查询,产生下面的结果 -# earns monthly but wants -#Dream Salary -#King earns 24000 monthly but wants 72000 -SELECT - CONCAT(last_name, ' earns ', salary, ' monthly but wants ', salary * 3) AS 'Dream Salary' -FROM employees -WHERE salary = 24000; - -``` - -### 分组函数 - -```sql -#二、分组函数 -/* -功能:用作统计使用,又称为聚合函数或统计函数或组函数 - -分类: -sum 求和、avg 平均值、max 最大值 、min 最小值 、count 计算个数 - -特点: -1、sum、avg一般用于处理数值型 - max、min、count可以处理任何类型 -2、以上分组函数都忽略null值 - -3、可以和distinct搭配实现去重的运算 - -4、count函数的单独介绍 -一般使用count(*)用作统计行数 - -5、和分组函数一同查询的字段要求是group by后的字段 - -*/ -#1、简单 的使用 -SELECT SUM(age) FROM t_user; -SELECT AVG(age) FROM t_user; -SELECT MIN(age) FROM t_user; -SELECT MAX(age) FROM t_user; -SELECT COUNT(age) FROM t_user; -SELECT SUM(user_name) FROM t_user; -SELECT AVG(user_name) FROM t_user; -SELECT COUNT(user_name) FROM t_user; - -SELECT SUM(DISTINCT password ) FROM t_user; -SELECT COUNT(DISTINCT password) FROM t_user; - -SELECT COUNT(*) FROM t_user; -SELECT COUNT(1) FROM t_user; -``` - -### 分组查询 - -```sql - -#进阶5:分组查询 - /* -语法: - -select 查询列表 -from 表 -【where 筛选条件】 -group by 分组的字段 -【order by 排序的字段】; - -特点: -1、和分组函数一同查询的字段必须是group by后出现的字段 -2、筛选分为两类:分组前筛选和分组后筛选 - 针对的表 位置 连接的关键字 -分组前筛选 原始表 group by前 where - -分组后筛选 group by后的结果集 group by后 having - -问题1:分组函数做筛选能不能放在where后面 -答:不能 - -问题2:where——group by——having - -一般来讲,能用分组前筛选的,尽量使用分组前筛选,提高效率 - -3、分组可以按单个字段也可以按多个字段 -4、可以搭配着排序使用 -*/ -SELECT - COUNT(*) -FROM - t_user -WHERE - password = '123'; -SELECT - AVG(age), - password -FROM - t_user -GROUP BY password; - -SELECT - AVG(age), - gender -FROM - t_user -GROUP BY gender ; -题目: -#1.查询各job_id的员工工资的最大值,最小值,平均值,总和,并按job_id升序 -SELECT - MAX(salary), - MIN(salary), - AVG(salary), - SUM(salary) -FROM employees -GROUP BY job_id -ORDER BY job_id ; - -#2.查询员工最高工资和最低工资的差距(DIFFERENCE) -SELECT - MAX(salary) - MIN(salary) AS DIFFERENCE -FROM employees; -#3.查询各个管理者手下员工的最低工资,其中最低工资不能低于6000,没有管理者的员工不计算在内 - -SELECT - MIN(salary), manager_id -FROM employees -WHERE manager_id IS NOT NULL -GROUP BY manager_id -HAVING MIN(salary) >= 6000; - -#4.查询所有部门的编号,员工数量和工资平均值,并按平均工资降序 - - -SELECT - department_id , - COUNT(*), - AVG(salary) -FROM - employees -GROUP BY department_id -ORDER BY AVG(salary) DESC; - -#5.选择具有各个job_id的员工人数 - -SELECT - COUNT(*), - job_id -FROM - employees -GROUP BY job_id; -``` - -### 连接查询 - -```sql - -#进阶6:连接查询 - /* -含义:又称多表查询,当查询的字段来自于多个表时,就会用到连接查询 - -笛卡尔乘积现象:表1 有m行,表2有n行,结果=m*n行 - -发生原因:没有有效的连接条件 -如何避免:添加有效的连接条件 - -分类: - - 按年代分类: - sql92标准:仅仅支持内连接 - sql99标准【推荐】:支持内连接+外连接(左外和右外)+交叉连接 - - 按功能分类: - 内连接: - 等值连接 - 非等值连接 - 自连接 - 外连接: - 左外连接 - 右外连接 - 全外连接 - - 交叉连接 -*/ -#1.显示所有员工的姓名,部门号和部门名称。 - SELECT - e.last_name , - d.department_id , - d.department_name -FROM - employees e , - departments d -WHERE - e.department_id = d.department_id ; -#2.查询90号部门员工的job_id和90号部门的location_id - SELECT - e.job_id , - d.location_id -FROM - employees e , - departments d -WHERE - e.department_id = d.department_id - AND e.department_id = 90; -#3. 选择所有有奖金的员工的 last_name , department_name , location_id , city - SELECT - last_name , - department_name , - l.location_id , - city -FROM - employees e, - departments d, - locations l -WHERE - e.department_id = d.department_id - AND d.location_id = l.location_id - AND e.commission_pct IS NOT NULL; -#4.选择city在Toronto工作的员工的 last_name , job_id , department_id , department_name - SELECT - e.last_name , - e.job_id , - e.department_id , - d.department_name -FROM - employees e, - departments d, - locations l -WHERE - e.department_id = d.department_id - AND d.location_id = l.location_id - AND l.city = 'Toronto'; -#5.查询每个工种、每个部门的部门名、工种名和最低工资 - SELECT - d.department_name , - j.job_title , - MIN(e.salary) -FROM - employees e , - departments d , - jobs j -WHERE - e.department_id = d.department_id - AND e.job_id = j.job_id -GROUP BY - d.department_name , - j.job_title ; -#6.查询每个国家下的部门个数大于2的国家编号 - SELECT - l.country_id , - COUNT(*) -FROM - locations l , - departments d -WHERE - d.location_id = l.location_id -GROUP BY - country_id -HAVING - COUNT(*) > 2; - -#7 、选择指定员工的姓名,员工号,以及他的管理者的姓名和员工号,结果类似于下面的格式 -SELECT - e.last_name employees, - e.employee_id "Emp#", - m.last_name manager, - m.employee_id "Mgr#" -FROM - employees e, - employees m -WHERE - e.manager_id = m.employee_id - AND e.last_name = 'kochhar'; - -#二、sql99语法 -/* -语法: - select 查询列表 - from 表1 别名 【连接类型】 - join 表2 别名 - on 连接条件 - 【where 筛选条件】 - 【group by 分组】 - 【having 筛选条件】 - 【order by 排序列表】 - - -分类: -内连接(★):inner -外连接 - 左外(★):left 【outer】 - 右外(★):right 【outer】 - 全外:full【outer】 -交叉连接:cross - -*/ -#一)内连接 -/* -语法: - -select 查询列表 -from 表1 别名 -inner join 表2 别名 -on 连接条件; - -分类: -等值 -非等值 -自连接 - -特点: -①添加排序、分组、筛选 -②inner可以省略 -③ 筛选条件放在where后面,连接条件放在on后面,提高分离性,便于阅读 -④inner join连接和sql92语法中的等值连接效果是一样的,都是查询多表的交集 -*/ -#二、外连接 - - /* - 应用场景:用于查询一个表中有,另一个表没有的记录 - - 特点: - 1、外连接的查询结果为主表中的所有记录 - 如果从表中有和它匹配的,则显示匹配的值 - 如果从表中没有和它匹配的,则显示null - 外连接查询结果=内连接结果+主表中有而从表没有的记录 - 2、左外连接,left join左边的是主表 - 右外连接,right join右边的是主表 - 3、左外和右外交换两个表的顺序,可以实现同样的效果 - 4、全外连接=内连接的结果+表1中有但表2没有的+表2中有但表1没有的 - */ -``` - -### 子查询 - -```sql -#进阶7:子查询 -/* -含义: -出现在其他语句中的select语句,称为子查询或内查询 -外部的查询语句,称为主查询或外查询 - -分类: -按子查询出现的位置: - select后面: - 仅仅支持标量子查询 - - from后面: - 支持表子查询 - where或having后面:★ - 标量子查询(单行) √ - 列子查询 (多行) √ - - 行子查询 - - exists后面(相关子查询) - 表子查询 -按结果集的行列数不同: - 标量子查询(结果集只有一行一列) - 列子查询(结果集只有一列多行) - 行子查询(结果集有一行多列) - 表子查询(结果集一般为多行多列) -*/ -#一、where或having后面 -/* -1、标量子查询(单行子查询) -2、列子查询(多行子查询) - -3、行子查询(多列多行) - -特点: -①子查询放在小括号内 -②子查询一般放在条件的右侧 -③标量子查询,一般搭配着单行操作符使用 -> < >= <= = <> - -列子查询,一般搭配着多行操作符使用 -in、any/some、all - -④子查询的执行优先于主查询执行,主查询的条件用到了子查询的结果 - -*/ -``` - -### 分页查询 - -```sql -#进阶8:分页查询 ★ -/* - -应用场景:当要显示的数据,一页显示不全,需要分页提交sql请求 -语法: - select 查询列表 - from 表 - 【join type join 表2 - on 连接条件 - where 筛选条件 - group by 分组字段 - having 分组后的筛选 - order by 排序的字段】 - limit 【offset,】size; - - offset要显示条目的起始索引(起始索引从0开始) - size 要显示的条目个数 -特点: - ①limit语句放在查询语句的最后 - ②公式 - 要显示的页数 page,每页的条目数size - - select 查询列表 - from 表 - limit (page-1)*size,size; - - size=10 - page - 1 0 - 2 10 - 3 20 -*/ -``` - - diff --git "a/Java/other/\345\274\200\346\272\220github.md" "b/Java/other/\345\274\200\346\272\220github.md" deleted file mode 100644 index 4449e13b..00000000 --- "a/Java/other/\345\274\200\346\272\220github.md" +++ /dev/null @@ -1,119 +0,0 @@ -> **一些有趣的github项目总结,其中包括终端、Python、Java、笔试&面试、效率软件等。** - - -## Java - -- [Awesome Java]() A curated list of awesome frameworks, libraries and software for the Java programming language. **star:21651** -- [CS-Notes]() 技术面试必备基础知识、Leetcode 题解、Java、C++、Python、后端面试、操作系统、计算机网络、系统设计。**star:67433** -- [spring-boot-examples]() 对于初学者学习Spring-boot,是个非常不错的例子。**star:16408** -- [SpringAll]()循序渐进,学习Spring Boot、Spring Boot & Shiro、Spring Cloud、Spring Security & Spring Security OAuth2,博客Spring系列源码。**star:6181** -- [interest]() Vue+Spring boot前后端分离博客项目。**star:494** -- [litemall]() 一个小商城。litemall = Spring Boot后端 + Vue管理员前端 + 微信小程序用户前端 + Vue用户移动端。**star:7586** -- [vhr]()微人事是一个前后端分离的人力资源管理系统,项目采用SpringBoot+Vue开发。**star:4705** -- [mybatis]() MyBatis SQL mapper framework for Java **star:11335** -- [miaosha]() ⭐⭐⭐⭐秒杀系统设计与实现.互联网工程师进阶与分析🙋🐓 **star:9400** -- [spring-boot-plus]()🔥spring-boot-plus集成Spring Boot 2.1.6,Mybatis,Mybatis Plus,Druid,FastJson,Redis,Rabbit MQ,Kafka等,可使用代码生成器快速开发项目. **star:551** -- [hope-boot]() 🌱🚀一款现代化的脚手架项目。🍻整合Springboot2 **star:1543** -- [spring-boot-demo]() spring boot demo 是一个用来学习 spring boot 的项目,总共包含 57 个集成demo,已经完成 47 个。**star:2149** -- [spring-boot-api-project-seed]() 🌱🚀一个基于Spring Boot & MyBatis的种子项目,用于快速构建中小型API、RESTful API项目~ **star:1543** -- [White-Jotter]()白卷是一款使用 Vue+Spring Boot 开发的前后端分离的图书管理项目 **star:115** -- [eladmin]() 项目基于 Spring Boot 2.1.0 、 Jpa、 Spring Security、redis、Vue的前后端分离的后台管理系统,项目采用分模块开发方式, 权限控制采用 RBAC,支持数据字典与数据权限管理,支持一键生成前后端代码,支持动态路由 **start:3163** -- [dbblog]() 基于SpringBoot2.x+Vue2.x+ElementUI+Iview+Elasticsearch+RabbitMQ+Redis+Shiro的多模块前后端分离的博客项目 **star:375** -- [spring-analysis]() Spring源码阅读 **star:4045** -- [mall]() mall项目是一套电商系统,包括前台商城系统及后台管理系统,基于SpringBoot+MyBatis实现。**star:21926** -- [后端架构师技术图谱](https://github.com/xingshaocheng/architect-awesome) **star:42900** -- [mall-swarm](https://github.com/macrozheng/mall-swarm)mall-swarm是一套微服务商城系统,采用了 Spring Cloud Greenwich、Spring Boot 2、MyBatis、Docker、Elasticsearch等核心技术,同时提供了基于Vue的管理后台方便快速搭建系统。**star:1400** -- [jeecg-boot](https://github.com/zhangdaiscott/jeecg-boot)一款基于代码生成器的JAVA快速开发平台,开源界“小普元”超越传统商业企业级开发平台!**star:9600** -- [dbblog](https://github.com/llldddbbb/dbblog) 基于SpringBoot2.x+Vue2.x+ElementUI+Iview+Elasticsearch+RabbitMQ+Redis+Shiro的多模块前后端分离的博客项目 [http://www.dblearn.cn](http://www.dblearn.cn/) **star:643** -- [springboot-seckill](https://github.com/zaiyunduan123/springboot-seckill) 基于SpringBoot + MySQL + Redis + RabbitMQ + Guava开发的高并发商品限时秒杀系统 **star:943** -- [MeetingFilm](https://github.com/daydreamdev/MeetingFilm)基于微服务架构的在线电影购票平台 **star:111** -- [guava](https://github.com/google/guava) Google core libraries for Java **star:36400** -- [hutool](https://github.com/looly/hutool) A set of tools that keep Java sweet. [http://www.hutool.cn](http://www.hutool.cn/) **star:10800** -- [JavaGuide]() 一份涵盖大部分Java程序员所需要掌握的核心知识。**star:45159** -- [advanced-java]() 互联网 Java 工程师进阶知识完全扫盲:涵盖高并发、分布式、高可用、微服务等领域知识,后端同学必看,前端同学也可学习。**star:22747** -- [technology-talk]() 汇总java生态圈常用技术框架等。**star:6229** -- [interview_internal_reference]() 2019年最新总结,阿里,腾讯,百度,美团,头条等技术面试题目,以及答案,专家出题人分析汇总。 **star:14335** -- [interviews]() Everything you need to know to get the job. **star:37598** -- [interview_internal_reference]()2019年最新总结,阿里,腾讯,百度,美团,头条等技术面试题目,以及答案,专家出题人分析汇总。 **star:15570** -- [reverse-interview-zh]() 技术面试最后反问面试官的话 **star:4500** -- [JavaFamily](https://github.com/AobingJava/JavaFamily)【互联网一线大厂面试+学习指南】进阶知识完全扫盲:涵盖高并发、分布式、高可用、微服务等领域知识,作者风格幽默,看起来津津有味,把学习当做一种乐趣,何乐而不为,后端同学必看,前端同学我保证你也看得懂,看不懂你加我微信骂我渣男就好了。 **star:7100** -- [Java-Interview](https://github.com/gzc426/Java-Interview) Java 面试必会 直通BAT **star:3500** -- [arthas](https://github.com/alibaba/arthas):Alibaba Java Diagnostic Tool Arthas/Alibaba Java诊断利器Arthas **star:24200** -- [Sentinel](https://github.com/alibaba/Sentinel)A powerful flow control component enabling reliability, resilience and monitoring for microservices. (面向云原生微服务的高可用流控防护组件) **star:14900** - -## 算法 -- [LeetcodeTop](https://github.com/afatcoder/LeetcodeTop)About汇总各大互联网公司容易考察的高频leetcode题🔥 **star:5000** -- [fucking-algorithm](https://github.com/labuladong/fucking-algorithm) 手把手撕LeetCode题目,扒各种算法套路的裤子,not only how,but also why. English version supported! https://labuladong.gitbook.io/algo/ **star:78200** -- [LeetCodeAnimation]() LeetCode用动画的形式的呈现。**star:32650** -- [leetcode]() LeetCode Solutions: A Record of My Problem Solving Journey.( leetcode题解,记录自己的leetcode解题之路。) -- [algo](https://github.com/wangzheng0822/algo) 数据结构和算法必知必会的50个代码实现 **star:10700** - -## Vue&&前端 - -- [awesome-vue]() awesome things related to Vue.js **star:46634** -- [vue-form-making]()基于Vue的表单设计器,让表单开发简单而高效。 **star:1347** -- [fe-interview]() 前端面试每日 3+1,以面试题来驱动学习,提倡每日学习与思考,每天进步一点!每天早上5点纯手工发布面试题(死磕自己,愉悦大家)**star:6667** -- [vue2-elm]() 基于 vue2 + vuex 构建一个具有 45 个页面的大型单页面应用 **star:30300** -- [Web]() 前端入门和进阶学习笔记,超详细的Web前端学习图文教程。从零开始学前端,做一名精致的前端工程师。持续更新… **star:55300** -- [javascript-algorithms]() Algorithms and data structures implemented in JavaScript with explanations and links to further readings **star:55277** -- [nodebestpractices]() ✅ The largest Node.js best practices list (September 2019) **star:35000** -- [gods-pen](https://github.com/ymm-tech/gods-pen) 基于vue的高扩展在线网页制作平台,可自定义组件,可添加脚本,可数据统计。A mobile page builder/editor, similar with amolink. [https://godspen.ymm56.com](https://godspen.ymm56.com/) **star:1200** -- [Daily-Interview-Question](https://github.com/Advanced-Frontend/Daily-Interview-Question) 我是木易杨,公众号「高级前端进阶」作者,每天搞定一道前端大厂面试题,祝大家天天进步,一年后会看到不一样的自己。**star:17000** - -## Python - -- [awesome-python-login-model]() 模拟登陆一些大型网站的demo,个人觉得不错,值得学习。**star:8403** -- [pyppeteer]() 模拟动态加载js,比selenium稍微好用一些。**star:1924** -- [requests]() Python HTTP Requests for Humans™ ✨🍰✨ **star:39860** -- [requests-html]() Pythonic HTML Parsing for Humans™ **star:10111** -- [httpx]() A next generation HTTP client for Python. 🦋 **star:1900** -- [PySimpleGUI]() 做一些简单的GUI,可以用这个,简单应用。**star:1608** -- [bokeh]() Interactive Web Plotting for Python。 **star:10701** -- [wxpy]() 微信机器人 / 可能是最优雅的微信个人号 API ✨✨ [http://wxpy.readthedocs.io](http://wxpy.readthedocs.io/) **star:10700** - - -## 效率软件 - -- [frp]() 内网穿透,你懂的。**star:24539** -- [musicbox]() 网易云音乐命令行版本。**star:7601** -- [motrix]() 配合百度云有着奇淫技巧。 **star:11098** -- [Electronic WeChat]() 该项目虽然不再维护,但挺好用的(linux)。**star:12622** -- [hexo]() 搭建博客系统框架,挺好用。**star:26868** -- [awesome-mac]()推荐的非常给力且实用。 **star:29814** -- [蓝灯]() 免费的vpn。 **star:9520** -- [LazyDocker]() The lazier way to manage everything docker. **star:10906** -- [postwoman]() 👽 API request builder - Helps you create your requests faster, saving you precious time on your development. **star:2190** -- [x64dbg]()An open-source x64/x32 debugger for windows. **star:34260** -- [google-access-helper]() 谷歌访问助手破解版 **star:3887** -- [v2ray-core]() A platform for building proxies to bypass network restrictions. **star:21444** -- [v2rayN]() vpn **star:1249** -- [Alfred-collection]() A collection of all known Alfred3 workflows **star:411** -- [RevokeMsgPatcher](https://github.com/huiyadanli/RevokeMsgPatcher) editor for WeChat/QQ/TIM - PC版微信/QQ/TIM防撤回补丁(我已经看到了,撤回也没用了) **star:1500** -- [lx-music-desktop](https://github.com/lyswhut/lx-music-desktop) 一个基于 electron 的音乐软件 **star:1300** -- [UnblockNeteaseMusic](https://github.com/nondanee/UnblockNeteaseMusic) Revive unavailable songs for Netease Cloud Music **star:7900** -- [CodeVar](https://github.com/xudaolong/CodeVar) 生成可用的代码变量 (CodeVar that return u a better variable from Chinese to English . ) **star:684** -- [Bob](https://github.com/ripperhe/Bob) Bob 是一款 Mac 端翻译软件,支持划词翻译、截图翻译以及手动输入翻译。 **star:3100** - -## 其他 - -- [ChineseBQB](ChineseBQB)🇨🇳Chinese sticker pack,More joy / 中国表情包大集合,更欢乐 **star:2319** -- [free-programming-books-zh_CN]()📚 免费的计算机编程类中文书籍,欢迎投稿 **star:55296** -- [freeCodeCamp]() The [https://www.freeCodeCamp.org](https://www.freecodecamp.org/) open source codebase and curriculum. Learn to code for free together with millions of people. **star:304920** -- [hosts]() 镜像 **star:15582** -- [free-api]() 收集免费的接口服务,做一个api的搬运工 **star:6000** -- [fanhaodaquan](https://github.com/imfht/fanhaodaquan) 番号大全。 **star:1200** -- [BullshitGenerator](https://github.com/menzi11/BullshitGenerator) Needs to generate some texts to test if my GUI rendering codes good or not. so I made this. **star:3700** -- [howto-make-more-money](https://github.com/easychen/howto-make-more-money): 程序员如何优雅的挣零花钱 **star:8200** -- [marktext](https://github.com/marktext/marktext) A simple and elegant markdown editor, available for Linux, macOS and Windows. [https://marktext.app](https://marktext.app/) **star:14800** -- [FreePAC](https://github.com/xiaoming2028/FreePAC) 科学上网/梯子/自由上网/翻墙 SS/SSR/V2Ray/Brook 搭建教程 **star:2146** - -- [The Art of Command Line]() :系统的学习命令行的用法。**star:57509** -- [oh-my-zsh]()这玩意不用我简单介绍了吧?**star:90516** -- [git-tips](https://github.com/521xueweihan/git-tips) Git的奇技淫巧 **star:11400** -- [powerline-fonts]() mac挺好用的一款终端字体。**star:169** -- [powerlevel9k]() oh-my-zsh的一款皮肤。**star:9952** -- [zsh-syntax-highlighting]() 终端输入的命令有语法高亮效果。**star:7326** -- [zsh-autosuggestions]()终端代码补全插件。**star:6662** -- [howto-make-more-money](https://github.com/easychen/howto-make-more-money): 程序员如何优雅的挣零花钱,2.0版,升级为小书了。Most of this not work outside China , so no English translate **star:13800** -- [yapi](https://github.com/YMFE/yapi)YApi 是一个可本地部署的、打通前后端及QA的、可视化的接口管理平台 **star:19400** -- [algorithm-visualizer](https://github.com/algorithm-visualizer/algorithm-visualizer):🎆Interactive Online Platform that Visualizes Algorithms from Code **star:33000** \ No newline at end of file diff --git a/README.md b/README.md index acdcf15b..d6d7bc3c 100644 --- a/README.md +++ b/README.md @@ -4,63 +4,28 @@ > - [在线面试阅读](http://dreamcat.ink/java-interview/) > - **Dreamcats的公众号**:[访问链接](https://mp.weixin.qq.com/s/NTRnfdPcr2pVnTvhFMYJCg) -在线面试阅读: - -java-interview-readme-1-qAdVxj - -java-interview-readme-2-m0q7qX - -## 面经助手 - -> 包含面经、算法题和知识点,持续维护 - -
-Dc-Notes-small-OlULPl -
- -## 秋招经历 - -> 校招Java职位面试经历 - -### 面经汇总 -- [大厂面经](/Java/mianjing/README.md) - -### 知识体系 -- [Java后端知识分类](/Java/classify/README.md) -- [定时推送后端面经题目脚本](https://github.com/DreamCats/dream-script/blob/master/notify_know.py) - -### 刷题系列 -- [推荐:CS-Notes](http://www.cyc2018.xyz/):个人建议,如果时间不充足的情况下,将CS-Notes的Leetcode刷明白,毕竟200道经典的题,也都很有套路,其次剑指offer,刷到5分钟自己能写完为止。 -- [推荐:一写算法套路模版](https://github.com/labuladong/fucking-algorithm):有些套路挺实用的,推荐阅读... -- [手写多线程编程面试题](/Java/alg/多线程编程题.md) -- [按热度总结lc](/Java/alg/按热度总结lc.md) -- [剑指offer](/Java/alg/剑指offer.md) -- [个人刷熟题](/Java/alg/个人刷熟题.md) -- [总结的一套秋招常考的热点题](Java/alg/README.md) -- [定时每日推送leetcode脚本](https://github.com/DreamCats/dream-script/blob/master/notify_lc.py) - -### 笔试题 -- [LC-SQL](/Java/bishi/sql.md):这里说一下, 图解SQL面试题,个人建议全部练习,也不难,但也的确面试高频手写SQL题 -- [图解SQL面试题](https://zhuanlan.zhihu.com/p/38354000) -- [常用SQL语句](Java/other/常用sql.md) -- [部分笔试题](/Java/bishi/README.md):可以从牛客找笔试题的感觉,也可以练习输入输出,做多了,你就发现基本没有树和链表! - -### Java面试思维导图(包括分布式架构) -- [知识架构思维导图](Java/mind/README.md) - -### 吐血系列 -- [疯狂吐血系列](Java/crazy/README.md) - -### 项目 -- [微服务班车在线预约系统](/Java/bus/README.md) - -### 其他 -- [面试必问设计模式](/Java/mode/README.md) -- [JDK1.8部分源码](Java/jdk/README.md) -- [相应阅读的书籍](Java/other/books.md) - - +### 🔖DreamCats +| 类型 | 名称 | 来源 | +| ----- | ------------------------------------------------------------ | -------------- | +| 小程序 | [在线面试助手-待定](wx.md) | DreamCats | +| 面经 | [大厂面经汇总](Java/mianjing/README.md) | DreamCats | +| 知识体系 | [Java后端知识分类](Java/classify/README.md) | DreamCats | +| | [疯狂吐血系列](Java/crazy/README.md) | DreamCats | +| | [知识架构思维导图](Java/mind/README.md) | DreamCats | +| 刷题 | [CS-Notes](http://www.cyc2018.xyz/) | cyc | +| | [写算法套路模板](https://github.com/labuladong/fucking-algorithm) | labuladong | +| | [按热度总结lc](Java/alg/按热度总结lc.md) | DreamCats | +| | [剑指Offer](Java/alg/剑指offer.md) | DreamCats | +| | [个人秋招刷熟题](Java/alg/个人刷熟题.md) | DreamCats | +| | [秋招常考的热点题](Java/alg/README.md) | DreamCats | +| | [LC-SQL](Java/bishi/sql.md) | DreamCats | +| | [图解SQL面试题](https://zhuanlan.zhihu.com/p/38354000) | xxx | +| | [牛客走起来](https://www.nowcoder.com/contestRoom) | xxx | +| 项目 | [微服务班车在线预约系统-文档](Java/bus/README.md) | DreamCats | +| | [微服务班车在线预约系统-项目地址](https://github.com/DreamCats/school-bus) | DreamCats | +| 其他 | [JDK1.8部分源码](Java/jdk/README.md) | DreamCats | +| | [相应阅读的书籍](books.md) | DreamCats | ## 文章系列 @@ -75,7 +40,6 @@ | Other | [计算机专业电子书下载](https://tanqingbo.cn/CSBook001/) | IT码农 | | Other | [计算机专业电子书下载-2](https://tanqingbo.cn/CSBook001/) | javaer-roadmap | | Other | [各个技术开发文档](https://www.bookstack.cn/) | 书栈网 | -| Other | [程序员写好技术文章的几点小技巧](https://mp.weixin.qq.com/s?__biz=MzIzOTU0NTQ0MA==&mid=2247503035&idx=1&sn=940e18795275cd0428c150822898f53a&chksm=e92af1b4de5d78a2b381cfc860f0ae000538432bd7a49d59c37bbb769784be6025d157962573#rd) | 阿里技术 | | Other | [TikTok二面: 说下二维码登录的原理?](https://mp.weixin.qq.com/s/HUJxTbMr0mep9uxAOa4F0A) | 小哈学 | | Other | [性能优化:关于缓存的一些思考](https://mp.weixin.qq.com/s/9aUTvdKFbsi_fzuO0BjPvw) | 阿里技术 | diff --git a/Java/other/books.md b/books.md similarity index 100% rename from Java/other/books.md rename to books.md diff --git a/wx.md b/wx.md new file mode 100644 index 00000000..e69de29b From 332aa03dadb38bcdbe2ba4d65c548a7e28ef9774 Mon Sep 17 00:00:00 2001 From: DreamCats Date: Thu, 12 Aug 2021 21:19:47 +0800 Subject: [PATCH 235/303] remove ds --- .DS_Store | Bin 6148 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 3cce28e5c4bb2383f8bfaee47ed0f47f02f2e421..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKPfrs;6rTrGS`b3et zf)_pc89aFOZu|zGj2}U--hA_?eI|x z^ltg^sPUrt^0kx`aQ6{5q_7Qd(CMtsDb&>N1n=9c`)U}S_4N;28oWGY43CTsj}6D; z@hju;iOI>Ud1KVJ3I`>h|I#JqGC$>3=~l^iirI=)aJ*73q|d{wb42sT$QkMyEj~LA ztshp9Gj<#hcrF;Q?pb9TizSj76^xQ8a*sSF8{~i-kyqp`c}G5wPvi^vMt(paOu}`T zg4?hFi|`QAumTx)0&6n6r~N9gwWp8>TZo-kS Date: Fri, 13 Aug 2021 10:14:31 +0800 Subject: [PATCH 236/303] add mq article --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d6d7bc3c..c072e3d5 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,7 @@ | 分布式 | [干货 \|携程最终一致和强一致性缓存实践](https://mp.weixin.qq.com/s/E-chAZyHtaZOdA19mW59-Q) | GSF | | 分布式锁 | [基于Redis的分布式锁设计](https://www.cnblogs.com/xiaoxiaotank/p/14982602.html) | xiaoxiaotank | | 分布式 | [分布式系统中一致性哈希算法](https://www.cnblogs.com/jajian/p/10896624.html) | 码辣架构 | +| 消息 | [消息幂等(去重)如何解决?来看看这个方案!](https://mp.weixin.qq.com/s/kLb1pweomL19aGaiHjO3Fg) | JAVA日知录 | ### 🎈数据结构 From 8f86587d63b260366989259653d573e65486cd55 Mon Sep 17 00:00:00 2001 From: DreamCats Date: Fri, 13 Aug 2021 10:16:01 +0800 Subject: [PATCH 237/303] add wx articke --- Java/.DS_Store | Bin 16388 -> 14340 bytes wx.md | 110 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+) diff --git a/Java/.DS_Store b/Java/.DS_Store index 84f1385a337541d9004d0f46881895577272a1b1..1fe0163f8cc3fd6d136f8e46c9f4b35cc9d29e98 100644 GIT binary patch delta 239 zcmZo^U~DN+VPIfjbSh0TWMD7=GC6=4L<{f$i8clX1rW_JQAJx=8Avk&B^Vfr%YuvY za`N-iCl;zsHn3rlW&}#HfTfcdN)ggP5yr`aMsk}SrBAVJR$!@Q+RVI7l8F@GJC`9l~F3>dE{7~o)6HsibBrDJ)ATZzt w60XQ@+bqcPo_R9AjV>q12?v0fVX_U6GzSyJBNmerY^F}`Q`gg4lUqKurA@5{=1C_4lI%>e>s2X6=i=s#@+ zM}}aA7=}WI28QVjiy5{u9Ar4h@R;E%!+%CzMlnVuMr}qNMpH&}Mh8YmMrTG3M$gIp zN~*d@&M6Bn%FD^mO9z_C2*k_`#SBFZ$&e7qPXdaZoFv>ZnNQw_9V#_>zKrQ+E~#Ed zVW0kh403#MG$1yp>Wa{Jt+A))> OB)vBGiTN;rtN;L$PkJQ) diff --git a/wx.md b/wx.md index e69de29b..42b2b471 100644 --- a/wx.md +++ b/wx.md @@ -0,0 +1,110 @@ +--- +title: 互联网面试题助手 +weight: 1 +--- + +# 互联网面试题助手 + +
+Dc-Notes-small-OlULPl +
+ +![](https://cdn.nlark.com/yuque/0/2021/png/1067743/1616314950564-0a745bb8-e526-41ba-a11f-b6a4c9c1241b.png#align=left&display=inline&height=2228&margin=%5Bobject%20Object%5D&originHeight=2228&originWidth=2658&size=0&status=done&style=none&width=2658) + +# 需求 +> 秋招一般从三个方面准备: +> - 知识点:形成个人体系,要根据自己的简历所建立的知识点 +> - 算法:开发岗的话,一般都是leetcode的题,不需要盲目刷完,后面我会讲 +> - 项目:要有针对性的项目,而且要不断的思考项目的亮点、困难点和如何解决的,以及多个方案对比。 + +如上图,从功能分析,大概分为三个子功能,也就对应三个功能的主页面。 +## 面经 +据我个人秋招经历来讲,看面经是又必须要的,有两种好处: + +1. 秋招之前,不知道面试官问什么,也并不知道如何回答,如果提前看了一些面经上的问题,心中大概有一些眉目,可以私下多准备,这时候一方面可以提供自信心;另一方面,不容易紧张。 +1. 查漏补缺,如果有了个人知识体系,看面经可以不断的查看自己哪部分没考虑到,可以及时补缺。 + +接着,对这个功能,我首先是分为前后端,算法岗暂时不涉及。毕竟前后端的面经是不一样的。目前涉及的公司大概有20-30之间吧,后续可以补充,当然,常见的大厂肯定是有的。当然,🐂客网有这些面经呀,但是有时候用手机看也挺方便的,虽然🐂🐂也有小程序,哈哈哈。但总体来讲,还是稍微复杂了一些,我仅仅是想一个简简单单的功能的嘛,简约。 +## 算法 +这一块分为笔试和面试。以我秋招的经历,我个人觉得,笔试常考的类型: + +1. 二分法 +1. 字符串 +1. dfs +1. bfs +1. 贪心 +1. 排序 +1. 哈希 +1. 规律题 +1. 动态规划 +1. 双指针 +1. 数学 +1. 模拟题 +1. 图 + +而,面试一般常考的类型: + +1. 链表 +1. 树(迭代和递归,可都要熟练) +1. 二分法 +1. dfs +1. bfs +1. 动态规划 +1. 背包 +1. 双指针 +1. 哈希 +1. 排序 +1. 数组 +1. 字符串 + +这个功能,有三大好处: + +1. 不同语言是如何解题(js、go和java) +1. 对着标题,尝试一下是否有思路 +1. 不断的手写,要熟能生巧 + +其实,我个人觉得,没有必要将leetcode全部刷完撒,而且面试不会考那些很陌生的题,一般都是常考的题,因为有价值撒。其实根据我的观察,常考的那些题,也就300-400道,而且一般都是考中等题,比较典型的hard题,可以做做,但没必要全做。 +## 知识点 +这一块,我秋招的面试岗位是Java服务端,所以总结了大量的关于Java的知识点,以及形成了一套Java生态的个人体系,当然也是根据我所设计的**班车项目**而来的。前不久,学了一下前端的知识,其实我也是想对比前端的js和java相同点和不同点,学习一下其中的思想。这一块,也是分为前后端,当然,我总结了300套面经的内容,并总结了面试题的频率,所以才有此针对做了一些知识点。前端包括: + +1. js +1. css +1. html +1. 浏览器 +1. vue +1. 计算机网络 + +当然,也得根据自身的简历哈。后端这一块: + +1. Java基础 +1. Java集合 +1. Java多线程 +1. JVM +1. Spring +1. MySQL +1. Redis +1. 计算机网络 +1. 操作系统 +1. 分布式 + +当然,以上关于班车项目,我后续会总结。班车项目,目前存放在了Github上。链接:[https://github.com/DreamCats/Dc-Notes](https://github.com/DreamCats/Dc-Notes) + + + +# 页面 +页面虽然比较简单,但功能实用,将面经、算法和知识点集合在一起方便背诵。 +## 面经页面 +![互联网助手-面经页面-iPhoneX-BQQ9MQ](http://imgs.dreamcat.ink/uPic/%E4%BA%92%E8%81%94%E7%BD%91%E5%8A%A9%E6%89%8B-%E9%9D%A2%E7%BB%8F%E9%A1%B5%E9%9D%A2-iPhone%20X-BQQ9MQ.png) +![互联网助手-面经列表页面-iPhoneX-AkQP0V](http://imgs.dreamcat.ink/uPic/%E4%BA%92%E8%81%94%E7%BD%91%E5%8A%A9%E6%89%8B-%E9%9D%A2%E7%BB%8F%E5%88%97%E8%A1%A8%E9%A1%B5%E9%9D%A2-iPhone%20X-AkQP0V.png) +![互联网助手-面经详情页面-iPhoneX-mTPXPt](http://imgs.dreamcat.ink/uPic/%E4%BA%92%E8%81%94%E7%BD%91%E5%8A%A9%E6%89%8B-%E9%9D%A2%E7%BB%8F%E8%AF%A6%E6%83%85%E9%A1%B5%E9%9D%A2-iPhone%20X-mTPXPt.png) +## 算法页面 + + +![互联网助手-算法页面-iPhoneX-tMYxJj](http://imgs.dreamcat.ink/uPic/%E4%BA%92%E8%81%94%E7%BD%91%E5%8A%A9%E6%89%8B-%E7%AE%97%E6%B3%95%E9%A1%B5%E9%9D%A2-iPhone%20X-tMYxJj.png) +![互联网助手-算法详情页面-iPhoneX-hJwWLu](http://imgs.dreamcat.ink/uPic/%E4%BA%92%E8%81%94%E7%BD%91%E5%8A%A9%E6%89%8B-%E7%AE%97%E6%B3%95%E8%AF%A6%E6%83%85%E9%A1%B5%E9%9D%A2-iPhone%20X-hJwWLu.png) +## 知识页面 +![互联网助手-知识页面-iPhoneX-l1D7Q9](http://imgs.dreamcat.ink/uPic/%E4%BA%92%E8%81%94%E7%BD%91%E5%8A%A9%E6%89%8B-%E7%9F%A5%E8%AF%86%E9%A1%B5%E9%9D%A2-iPhone%20X-l1D7Q9.png) +![互联网助手-知识详情页面-iPhoneX-SPF7m9](http://imgs.dreamcat.ink/uPic/%E4%BA%92%E8%81%94%E7%BD%91%E5%8A%A9%E6%89%8B-%E7%9F%A5%E8%AF%86%E8%AF%A6%E6%83%85%E9%A1%B5%E9%9D%A2-iPhone%20X-SPF7m9.png) + + + From 7f3d91ae0eb3a954e224ab490360cc57a0ad22dd Mon Sep 17 00:00:00 2001 From: DreamCats Date: Fri, 13 Aug 2021 10:20:47 +0800 Subject: [PATCH 238/303] fix wx --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c072e3d5..4f70e65c 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ | 类型 | 名称 | 来源 | | ----- | ------------------------------------------------------------ | -------------- | -| 小程序 | [在线面试助手-待定](wx.md) | DreamCats | +| 小程序 | [在线面试助手](wx.md) | DreamCats | | 面经 | [大厂面经汇总](Java/mianjing/README.md) | DreamCats | | 知识体系 | [Java后端知识分类](Java/classify/README.md) | DreamCats | | | [疯狂吐血系列](Java/crazy/README.md) | DreamCats | From 940082e9e810ace596eb258f305f3d86ca067a7a Mon Sep 17 00:00:00 2001 From: DreamCats Date: Fri, 13 Aug 2021 10:57:57 +0800 Subject: [PATCH 239/303] remove github source --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 4f70e65c..357786f3 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,6 @@ | 类型 | 名称 | 来源 | | ----- | ------------------------------------------------------------ | -------------- | -| Other | [持续更新非常优秀的Github项目](Java/other/开源github.md) | Dreamcats | | Other | [计算机专业电子书下载](https://tanqingbo.cn/CSBook001/) | IT码农 | | Other | [计算机专业电子书下载-2](https://tanqingbo.cn/CSBook001/) | javaer-roadmap | | Other | [各个技术开发文档](https://www.bookstack.cn/) | 书栈网 | From 7f7dbd754fa6472458043ccd8fdb3b1e515825f9 Mon Sep 17 00:00:00 2001 From: Dreamcats Date: Sun, 15 Aug 2021 00:23:02 +0800 Subject: [PATCH 240/303] :memo:update blog --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d6d7bc3c..5d257108 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # java-notes ## 引言 -> - [个人博客](http://dreamcat.ink/blog) +> - [个人博客](https://blog.dreamcat.ink/) > - [在线面试阅读](http://dreamcat.ink/java-interview/) > - **Dreamcats的公众号**:[访问链接](https://mp.weixin.qq.com/s/NTRnfdPcr2pVnTvhFMYJCg) From e383be615fe6508b54b30de7c693672737b11e14 Mon Sep 17 00:00:00 2001 From: DreamCats Date: Mon, 16 Aug 2021 11:26:43 +0800 Subject: [PATCH 241/303] :memo: add Guava Cache --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ffbbf7a3..371cc345 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,7 @@ | 分布式锁 | [基于Redis的分布式锁设计](https://www.cnblogs.com/xiaoxiaotank/p/14982602.html) | xiaoxiaotank | | 分布式 | [分布式系统中一致性哈希算法](https://www.cnblogs.com/jajian/p/10896624.html) | 码辣架构 | | 消息 | [消息幂等(去重)如何解决?来看看这个方案!](https://mp.weixin.qq.com/s/kLb1pweomL19aGaiHjO3Fg) | JAVA日知录 | +| 本地缓存 | [Guava Cache 原理分析与最佳实践](https://mp.weixin.qq.com/s/ADcu_XKTJxXectMQ8S20SQ) | 梓川 | ### 🎈数据结构 From 91638a533e898af1e0cb3048c25b0279d06ba1c9 Mon Sep 17 00:00:00 2001 From: Dreamcats Date: Tue, 17 Aug 2021 00:26:56 +0800 Subject: [PATCH 242/303] :sparkles:add spring-books --- .DS_Store | Bin 0 -> 6148 bytes Java/.DS_Store | Bin 14340 -> 14340 bytes ...70\350\247\201\351\227\256\351\242\230.md" | 154 +++++++++++ Java/spring-books/README.md | 73 +++++ ...01\347\250\213\345\210\206\346\236\220.md" | 151 +++++++++++ ...20\347\240\201\346\200\273\347\273\223.md" | 253 ++++++++++++++++++ ...50\247\243bean\346\226\271\345\274\217.md" | 17 ++ ...37\345\221\275\345\221\250\346\234\237.md" | 78 ++++++ ...ue\347\232\204\344\275\277\347\224\250.md" | 32 +++ ...ce\347\232\204\344\275\277\347\224\250.md" | 32 +++ ...ed\347\232\204\344\275\277\347\224\250.md" | 71 +++++ ...er\347\232\204\344\275\277\347\224\250.md" | 71 +++++ ...le\347\232\204\344\275\277\347\224\250.md" | 83 ++++++ ...OP\345\260\217\344\276\213\345\255\220.md" | 210 +++++++++++++++ .../17.Spring\344\272\213\345\212\241.md" | 98 +++++++ .../docs/18.BeanFactoryPostProcessor.md | 39 +++ .../19.BeanDefinitionRegistryPostProcessor.md | 32 +++ ...53\346\217\217\346\226\271\345\274\217.md" | 17 ++ .../docs/20.ApplicationListener.md | 25 ++ ...45\206\214bean\346\226\271\345\274\217.md" | 40 +++ ...53\346\217\217\346\226\271\345\274\217.md" | 41 +++ ...er\347\232\204\344\275\277\347\224\250.md" | 34 +++ ...pe\347\232\204\344\275\277\347\224\250.md" | 50 ++++ ...zy\347\232\204\344\275\277\347\224\250.md" | 50 ++++ ...al\347\232\204\344\275\277\347\224\250.md" | 46 ++++ ...rt\347\232\204\344\275\277\347\224\250.md" | 37 +++ Java/spring-books/spring-aop2/src/Advice.java | 7 + .../spring-aop2/src/AfterAdvice.java | 29 ++ .../spring-aop2/src/BeforeAdvice.java | 27 ++ .../spring-aop2/src/HelloService.java | 11 + .../spring-aop2/src/HelloServiceImpl.java | 14 + .../spring-aop2/src/MethodInvocation.java | 11 + .../spring-aop2/src/SimpleAOP.java | 16 ++ .../spring-aop2/src/SimpleAOPTest.java | 29 ++ Java/spring-books/spring-ioc/src/Car.java | 39 +++ .../spring-ioc/src/SimpleIOC.java | 101 +++++++ .../spring-ioc/src/SimpleIOCTest.java | 20 ++ Java/spring-books/spring-ioc/src/Wheel.java | 39 +++ Java/spring-books/spring-ioc/src/ioc.xml | 11 + README.md | 1 + 40 files changed, 2089 insertions(+) create mode 100644 .DS_Store create mode 100644 "Java/spring-books/MyBatis\346\241\206\346\236\266\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230.md" create mode 100644 Java/spring-books/README.md create mode 100644 "Java/spring-books/SpringBoot\345\220\257\345\212\250\346\265\201\347\250\213\345\210\206\346\236\220.md" create mode 100644 "Java/spring-books/Spring\345\222\214SpringAOP\346\272\220\347\240\201\346\200\273\347\273\223.md" create mode 100644 "Java/spring-books/docs/1.xml\346\263\250\350\247\243bean\346\226\271\345\274\217.md" create mode 100644 "Java/spring-books/docs/10.bean\347\232\204\347\224\237\345\221\275\345\221\250\346\234\237.md" create mode 100644 "Java/spring-books/docs/11.@Value\347\232\204\344\275\277\347\224\250.md" create mode 100644 "Java/spring-books/docs/12.@PropertySource\347\232\204\344\275\277\347\224\250.md" create mode 100644 "Java/spring-books/docs/13.@Autowired\347\232\204\344\275\277\347\224\250.md" create mode 100644 "Java/spring-books/docs/14.@Qualifier\347\232\204\344\275\277\347\224\250.md" create mode 100644 "Java/spring-books/docs/15.@Profile\347\232\204\344\275\277\347\224\250.md" create mode 100644 "Java/spring-books/docs/16.AOP\345\260\217\344\276\213\345\255\220.md" create mode 100644 "Java/spring-books/docs/17.Spring\344\272\213\345\212\241.md" create mode 100644 Java/spring-books/docs/18.BeanFactoryPostProcessor.md create mode 100644 Java/spring-books/docs/19.BeanDefinitionRegistryPostProcessor.md create mode 100644 "Java/spring-books/docs/2.xml\346\263\250\345\206\214\345\214\205\346\211\253\346\217\217\346\226\271\345\274\217.md" create mode 100644 Java/spring-books/docs/20.ApplicationListener.md create mode 100644 "Java/spring-books/docs/3.\346\263\250\350\247\243\346\263\250\345\206\214bean\346\226\271\345\274\217.md" create mode 100644 "Java/spring-books/docs/4.\346\263\250\350\247\243\346\263\250\345\206\214\345\214\205\346\211\253\346\217\217\346\226\271\345\274\217.md" create mode 100644 "Java/spring-books/docs/5.@Filter\347\232\204\344\275\277\347\224\250.md" create mode 100644 "Java/spring-books/docs/6.@Scope\347\232\204\344\275\277\347\224\250.md" create mode 100644 "Java/spring-books/docs/7.@Lazy\347\232\204\344\275\277\347\224\250.md" create mode 100644 "Java/spring-books/docs/8.@Conditional\347\232\204\344\275\277\347\224\250.md" create mode 100644 "Java/spring-books/docs/9.@Import\347\232\204\344\275\277\347\224\250.md" create mode 100644 Java/spring-books/spring-aop2/src/Advice.java create mode 100644 Java/spring-books/spring-aop2/src/AfterAdvice.java create mode 100644 Java/spring-books/spring-aop2/src/BeforeAdvice.java create mode 100644 Java/spring-books/spring-aop2/src/HelloService.java create mode 100644 Java/spring-books/spring-aop2/src/HelloServiceImpl.java create mode 100644 Java/spring-books/spring-aop2/src/MethodInvocation.java create mode 100644 Java/spring-books/spring-aop2/src/SimpleAOP.java create mode 100644 Java/spring-books/spring-aop2/src/SimpleAOPTest.java create mode 100644 Java/spring-books/spring-ioc/src/Car.java create mode 100644 Java/spring-books/spring-ioc/src/SimpleIOC.java create mode 100644 Java/spring-books/spring-ioc/src/SimpleIOCTest.java create mode 100644 Java/spring-books/spring-ioc/src/Wheel.java create mode 100644 Java/spring-books/spring-ioc/src/ioc.xml diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5083484dbd636a69dfba1bc52992b9ba20df2573 GIT binary patch literal 6148 zcmeHK!AiqG5Z!H~CWx4WV2^w8)XH<+2a)3({&Y0( z!`O%Z+fp<+{vreT?F@FtJT_t8=kMna!eEqCD(}2dG}hKPOv^H@E$i0p$5S`)lJThN z4X?3x?j?O++ZS;h&TY@X40_YHy>lE#i5En@p-c|CJqWqE3Zia2ZN}rMJCgIbYCta3 zwo7SRZ8XZFc38{GB5mwf%A$Ty&oay0-K!p*cJ3b@)2HnDMX}1^2b8j{aR#qo%r`i3 zhfx?uci@}F&*BJ)0b+m{SRn@V5om6%(4J|l!~ikyGY0T{5TJ;T#$2I1I-tSpBaR!0 zC}87T0#RsmH0BCn1ca+pK$Xh%6N9UC@CzO1Xv`I=bjIb%FpplD>lX@_tAk(2aK;^l z)Di>4z#;=h)wQwypM3xRUreGNF+dFbD+ajHa$8Na)4+vcZBn{LM1Ha0^2T>YOVE_OC literal 0 HcmV?d00001 diff --git a/Java/.DS_Store b/Java/.DS_Store index 1fe0163f8cc3fd6d136f8e46c9f4b35cc9d29e98..e393f85cd2a97c048d46ddf77ddfa9a98eb82047 100644 GIT binary patch delta 111 zcmZoEXepTBC7U^hRb*k&GqG=5bchGK>Sh9ZVchCGIJ23>|EhI}B*W+*NTF3QWv b&r4@uU|^hhv1Bv1LORi^3pTSG{AC9KXb&CF delta 43 ucmZoEXepTBgHU^hRb=w=>)H2%r6 项目中,经常用到MyBatis,面试也需要掌握滴! + + +## 基本概念 + +### 数据持久化 + +**数据持久化是将内存中的数据模型转换为存储模型,以及将存储模型转换为内存中的数据模型的统称。例如,文件的存储、数据的读取等都是数据持久化操作。数据模型可以是任何数据结构或对象的模型、XML、二进制流等。** + +**当我们编写应用程序操作数据库,对表数据进行增删改查的操作的时候就是数据持久化的操作。** + +### Mybatis框架简介 + +- **MyBatis框架是一个开源的数据持久层框架。** +- **它的内部封装了通过JDBC访问数据库的操作,支持普通的SQL查询、存储过程和高级映射,几乎消除了所有的JDBC代码和参数的手工设置以及结果集的检索。** +- **MyBatis作为持久层框架,其主要思想是将程序中的大量SQL语句剥离出来,配置在配置文件当中,实现SQL的灵活配置。** +- **这样做的好处是将SQL与程序代码分离,可以在不修改代码的情况下,直接在配置文件当中修改SQL。** + +### ORM + +**ORM(Object/Relational Mapping)即对象关系映射,是一种数据持久化技术。它在对象模型和关系型数据库直接建立起对应关系,并且提供一种机制,通过JavaBean对象去操作数据库表的数据。** + +**MyBatis通过简单的XML或者注解的方式进行配置和原始映射,将实体类和SQL语句之间建立映射关系,是一种半自动(之所以说是半自动,因为我们要自己写SQL)的ORM实现。** + +## MyBatis框架的优缺点及其适用的场合 + +### 优点 + +1. 与JDBC相比,减少了50%以上的代码量。 +2. MyBatis是嘴加单的持久层框架,小巧并且简单易学。 +3. MyBatis相当灵活,不会对应用程序或者数据库的现有设计强加任何影响,SQL写在XML文件里,从程序代码中彻底分离,降低耦合度,便于统一的管理和优化,并可重用。 +4. 提供XML标签,支持编写动态的SQL,满足不同的业务需求。 +5. 提供映射标签,支持对象与数据库的ORM字段关系映射。 + +### 缺点 + +1. SQL语句的编写工作量较大,对开发人员编写SQL的能力有一定的要求。 +2. SQL语句依赖于数据库,导致数据库不具有好的移植性,不可以随便更换数据库。 + +### 适用场合 + +**MyBatis专注于SQL自身,是一个足够灵活的DAO层解决方案。对性能的要求很高,或者需求变化较多的项目,例如Web项目,那么MyBatis是不二的选择。** + +## MyBatis与Hibernate有哪些不同? + +1. Mybatis和hibernate不同,它不完全是一个ORM框架,因为MyBatis需要程序员自己编写Sql语句。 +2. Mybatis直接编写原生态sql,可以严格控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,因为这类软件需求变化频繁,一但需求变化要求迅速输出成果。但是灵活的前提是mybatis无法做到数据库无关性,如果需要实现支持多种数据库的软件,则需要自定义多套sql映射文件,工作量大。 +3. Hibernate对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件,如果用hibernate开发可以节省很多代码,提高效率。 + +## **#{}和${}的区别是什么?** + +1. \#{} 是预编译处理,${}是字符串替换。 +2. Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值; +3. Mybatis在处理\${}时,就是把\${}替换成变量的值。 + +4. 使用#{}可以有效的防止SQL注入,提高系统安全性。 + +## 当实体类中的属性名和表中的字段名不一样 ,怎么办 ? + +1. 第1种: 通过在查询的sql语句中定义字段名的别名,让字段名的别名和实体类的属性名一致。 +2. 第2种: 通过 `` 来映射字段名和实体类属性名的一一对应的关系。 + +## 模糊查询like语句该怎么写? + +1. 第1种:在Java代码中添加sql通配符。 +2. 第2种:在sql语句中拼接通配符,会引起sql注入 + +## 通常一个Xml映射文件,都会写一个Dao接口与之对应,请问,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗? + +**Dao接口即Mapper接口。接口的全限名,就是映射文件中的namespace的值;接口的方法名,就是映射文件中Mapper的Statement的id值;接口方法内的参数,就是传递给sql的参数。** +**Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯一定位一个MapperStatement。在Mybatis中每`