From 5ec7931ee95b6753af9a34b97216cf4d118981e5 Mon Sep 17 00:00:00 2001 From: seaswalker Date: Fri, 22 Dec 2017 21:51:32 +0800 Subject: [PATCH 01/12] =?UTF-8?q?=E8=A1=A5=E5=85=85ThreadLocal=E5=86=85?= =?UTF-8?q?=E5=AD=98=E6=B3=84=E6=BC=8F=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- note/FileChannel/filechannel.md | 2 +- note/ThreadLocal/threadlocal.md | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/note/FileChannel/filechannel.md b/note/FileChannel/filechannel.md index f5bb1ae..65dce6d 100644 --- a/note/FileChannel/filechannel.md +++ b/note/FileChannel/filechannel.md @@ -495,7 +495,7 @@ if (!shared && !writable) ## FileLockTable -如上文所述,**文件锁的作用域为整个虚拟机**,也就是死说,两个channel如果对同一个文件的重复区域进行加锁,势必会导致OverlappingFileLockException,那么Java是如何在整个虚拟机范围(全局)进行检查的呢?答案便是FileLockTable。 +如上文所述,**文件锁的作用域为整个虚拟机**,也就是说,两个channel如果对同一个文件的重复区域进行加锁,势必会导致OverlappingFileLockException,那么Java是如何在整个虚拟机范围(全局)进行检查的呢?答案便是FileLockTable。 其位于sun.nio.ch下,类图: diff --git a/note/ThreadLocal/threadlocal.md b/note/ThreadLocal/threadlocal.md index 7852a42..02ae339 100644 --- a/note/ThreadLocal/threadlocal.md +++ b/note/ThreadLocal/threadlocal.md @@ -226,4 +226,12 @@ static final class SuppliedThreadLocal extends ThreadLocal { } ``` -一目了然。 \ No newline at end of file +一目了然。 + +# 内存泄漏 + +以下两篇博客足矣: + +[深入分析 ThreadLocal 内存泄漏问题](http://www.importnew.com/22039.html) + +[ThreadLocal 内存泄露的实例分析](http://www.importnew.com/22046.html) \ No newline at end of file From c044a0187710813bc6a7316224973553cf86cf07 Mon Sep 17 00:00:00 2001 From: seaswalker Date: Sat, 23 Dec 2017 19:57:27 +0800 Subject: [PATCH 02/12] =?UTF-8?q?=E6=9E=9A=E4=B8=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 + note/Enum/enum.md | 104 ++++++++++++++++++++++++++++++++++++++ note/Enum/images/enum.png | Bin 0 -> 5413 bytes 3 files changed, 106 insertions(+) create mode 100644 note/Enum/enum.md create mode 100644 note/Enum/images/enum.png diff --git a/README.md b/README.md index 214c1c8..c2afbc3 100644 --- a/README.md +++ b/README.md @@ -47,3 +47,5 @@ [AsynchronousFileChannel](https://github.com/seaswalker/JDK/blob/master/note/AsynchronousFileChannel/asynchronousfilechannel.md) [BufferedInputStream](https://github.com/seaswalker/JDK/blob/master/note/BufferedInputStream/bufferedinputstream.md) + +[Enum](https://github.com/seaswalker/JDK/blob/master/note/Enum/enum.md) diff --git a/note/Enum/enum.md b/note/Enum/enum.md new file mode 100644 index 0000000..67c848b --- /dev/null +++ b/note/Enum/enum.md @@ -0,0 +1,104 @@ +# 说明 + +对枚举这一看似平常的东西单开一节的目的是要探究一下为什么说枚举是最好的实现单例的方式。 + +# 从例子开始 + +假设有这么一个简单的枚举: + +```java +public enum Test { + + SOMEONE, + + ANOTHER_ONE + +} +``` + +首先对其进行编译,然后使用命令`javap -verbose Test.class`对其反编译,得到如下部分: + +```java +public final class Test extends java.lang.Enum {} +``` + +所以,对于Java来说,枚举是一个语法糖。 + +# Why + +Enum的类图: + +![Enum](images/enum.png) + +## finalize + +Enum中将finalize方法直接定义为final,这就从根本上上避免了枚举类对此方法的实现: + +```java +protected final void finalize() { } +``` + +### 序列化 + +以如下代码示例对枚举类型的序列化: + +```java +File file = new File("C:/Users/xsdwe/Desktop/test"); +ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file)); +out.writeObject(Person.EVIL); +out.writeObject(Person.JACK); +out.flush(); +out.close(); +``` + +限制其实是在ObjectOutputStream的writeObject0方法中进行控制的,相关源码: + +```java +private void writeObject0(Object obj, boolean unshared) throws IOException { + //... + if (obj instanceof String) { + writeString((String) obj, unshared); + } else if (cl.isArray()) { + writeArray(obj, desc, unshared); + } else if (obj instanceof Enum) { + writeEnum((Enum) obj, desc, unshared); + } + //... +} +``` + +可以看出,对于枚举类型,这里使用了专门的方法writeEnum进行序列化: + +```java +private void writeEnum(Enum en, ObjectStreamClass desc, boolean unshared) { + bout.writeByte(TC_ENUM); + ObjectStreamClass sdesc = desc.getSuperDesc(); + writeClassDesc((sdesc.forClass() == Enum.class) ? desc : sdesc, false); + handles.assign(unshared ? null : en); + writeString(en.name(), false); +} +``` + +这里**只对枚举的名称进行了序列化**。 + +### 反序列化 + +反序列化的控制由ObjectInputStream的readEnum方法完成,简略版源码: + +```java +private Enum readEnum(boolean unshared) throws IOException { + //... + String name = readString(false); + return Enum.valueOf((Class)cl, name); +} +``` + +这样就保证了枚举的设计目标: 在**一个JVM中一个枚举值只能有一个实例**。 +这里还有一个细节,序列化之后对象都以字节的形式存在,反序列化时Java是如何识别出这是一个枚举的呢? + +其实从上面序列化方法writeEnum中可以看出,方法再将枚举名写入之前,首先写入了一个字节(0X7E, 126)的标志,然后写入了一串这东西: +`mq.Person: static final long serialVersionUID = 0L`,mq.Person是测试枚举Person的完整类名。 + +# 总结 + +至于单例模式中的初始化线程安全这一点,是由JVM虚拟机的类加载机制决定的,我们无需担心。 \ No newline at end of file diff --git a/note/Enum/images/enum.png b/note/Enum/images/enum.png new file mode 100644 index 0000000000000000000000000000000000000000..fd2bbffe28c989f3dfb79d5dd6e49897ce75f4a9 GIT binary patch literal 5413 zcmaJ_2UJtdwmx)}PC$AKRhlCFAXQ32ks?w>nh=^2ib$kLlU@QCdQp)oinJg|7wNqU z1dtMXhXB8X{^DKhy?gIl_r0~wIeWg@d-lxPYtNc*PmG~~7R`0`>i_`I=x9TX0Dy>* z(5|2)Cw$vduwDdl%~RXr1pv^t|9Od0#b`MQoebX4N8ZLBPToFtUXFmV`%_Q57v7H6 zEQ7NEz*MgTQ8V#3f7^fq0_6=TPCOk zf$!bARy|c*GqDnzzbO(wDsJxjF5^~2g6_5aSZz`|-p7yay@6ZNt!;1`n3%!e_iQisMMO z=J@?uhi)kh1HMTNM;R*InFCAtm|mWb-{bSb!jLxHrUT_xCNWu#!I80Kavx2kdQ;>r zBy(_5iiJ1ighmY-H$LCvIy+*mf56qTzEYLZjK7hsc`FBc{3(B3aWdyCr==b?r$tbS zEZFx>Pd(TE^;_WZ9;TX+Y=hRl#}ppg9m!D0(0Ku?@tFW zguzR~C|2H>vF;8A&aM@C# zQefXi2fyIDj5iK`?u(s9ldI?BQb;SA;5cuGa{*Bnvk&%aCrHOA{yn>ZMPlHI-^Lwy znP0niovK`8t6a#!wP<&umfPoL%9M54*vL2P@&KpeF#m4LQB;9&P(}Vtc)svwym?7; zY+&JO(1EW;R^TFuYVozuHFK9YFag=272Ejtotr;<(KKi6@r534V3tUy?c2jglE4!z z+9=?q9dW~B?st{?9ifHNz3e%xUHq;*0MIDs4e`mzYUNp~J=|mP?4rZ4%lvK(Kf0}s%hm}qduZ42su?HUvF`eF{tonZyUKIeCs%3z z6HdnLV!|{;yo<8V)#t&89Q<1YKd@O!Dhz5-hgJDYSc_Tqs5#VJ!+*=WO$;8+t1o%= zMkbgtXj0QxW7NwW2*Uza>eA}CAp5&Oy1gYLUB~1LXhvvzp08Z&!Q8HZV+YAe@#pth zXLkbbO=K5;5(~joUT{pm8?yU0qtU}lRTe) z;;_l(LWBj{^Jm;inMpHx z>TXkfKJMdako{KMM%$B_S)pE|pKYGDntNJ%wNA!-?NJZwaIjN-PLDuK+*HgyW$#ij zb#v3`8cSle#&OpK*AY>3PUg9|adg=G9nUAVyo;TS-()kXQr>NSTd3MfFAS>izrZvK zm}k!3QfIp23M*ys4OiszP3&BaQW#E{Z!>=-Olk)r#p>YwUoVlgMLQowE_}nL&3`;t zC|8HgjD=iz*w6USnTcnQo_v>ym=EdW2V<&eX!Qhd>gIFrmgkP2$kIkI_C7cbV*F@@ zaNFkO!BwFL4-6>?fd&shl2`WV#Lb6HgdADaZ;gb|xXcEc!>zkYF(H)XtxhU^OiLeB z32UZ#>TjW|hqh{Hj5xf%;?|7b?5kBlknp@ybg$J1Yb~in$1Cq&A8KtVWO1V>TAR14 zSbKWc=?L^dtT*9S9K|{l{Uw=IbvaR~hZ@r)fX!($RYYbEXm-fb14k{EXRdHAc$8wZ z`1y-4G~}JX>MPs&J*_R)mCvsE-}+sG5*FJ6dDxY%kZo8u7jIT;U2UaH4nuR$y-P!J zIsbRdRWit%@orQC?&1hHQI=U`dzHqAjXWEhN%KTQMTXG}YDRwF-zzAr8u2h+#sdFb!BuLrKiKD>*wIoeh zrSy(mQCOdokcBN5Z;`>!NXL%Nk)U;r69neU8t>23b;GB%IjNbabSYDxjGm{Ks=<=T z)-CMRCTI9~;ef8M%R4<7Qn^io-v?wBQ=He@tE%T*!-hs7oW+|qyw719mf2a3Vp9Gm z#^a6F5_E!J3d;-!(5dw+L@fuS9jrOE8Mi%Ru(F;?5vsHE>hv%Bs@OxVM|loxKe+8@ zdydcXuVEvfuuPlmiN$Up%argl!}Z6m#mqsJfTyeyxpPvPe)pM7Kog*J{K2}czmI+f z*_)JAyl&JO)kV*glbrRXH;}D~N1|?Xy5Aui4=lKxOSRXV^ZuM8u?=u)BiM@)ndIpQ zu-RarQXaGt#(Wn==%#krY9A={I6q}IX2-eM2P(G5#|Rf#t65CtcvIc|y`cl?9Lk!u z7&tJky+}jcG=1}>&TGZ#=_L;X)Z6l*vfsvMh!2FvYb(z!vu8X$ zk>8H!#ux7@0EShKMvo`){c4$MC1vq7i*bo~oLP+*)kE3_+2`NpjUsv@bQ$VAY4g*& zO!`DwQuCdr?wg2f&z%>*yKN_h%4!!iN*b)9y-rI9zQ&Tp8CE_){a~M>bs0??@^C9+ zeh}~l0vl>D3?8?R{&Q@(*C)-cd~?-^5T&!`Kv$jnxE0f{d~(%z>6lnX!_5BDO*_Jx z<32@$Y(Z-oO(B1qb#`;JE8wp4O*X}668%_V308Ao!krqVgFyVHsi`?UHTASm(}84s ze4OojRVq&EEHj>?&2l+T?S;8?)>ihRHH|2MWDcC-K5Cy(&{7x~oedpC4F;G;z!NI% zzZ8RXbj{;7ZNbV&W*9VEn_#h06?bq2iD4D%VRi%SC{^w2Y54yEj^Gi65(0S4Dz?f* z0E+npKrmZnvIa{jWD~%BB=cdcBuYRrnE;;u0ivi3`8WaqV1lO5RPFxvYQ9B$o%f%` z3ZV+TelKJe)k>Keb;^qy_3{!On8&CzD% zgiJ6866ng-ztWZNf}*pm18cGw(Njq40&kUXNl0_v0|9ExPzZvb=xzleMxfP(sMSVocU8vcufD?+R`>!@cHBD*`WvWnl(JTOr`M)dpe+SzHW+;4DQ_ zh;hoKSxnWWB178&j-oI8XFpqmEM&tTj1u%fy{#iYO1u_rWi(W7K@AsWsjL^*HRVUk z3o%+8Ko0tHugZ z@Ik7+-ZR*YK~_M)WA6IMtNArnwkBjfA`a-+dED8(?)b|{g|06>$gXp=4Rw555y$XC0I}3&JM)u*^zYnV~&RaSV&lCICvlEjEW)xUg+xL z_7ATu$5y?`dqS*&0Qx zBd(oKpfRn63LZvc9MZr|6aB$Ja?K3hLcvO@g?(9}2t6fai~oT6!%D06gH2ILeS@J* z^^L=H$q^C$n#*&eojWXpVyTlSy`OI_o&KaDoQ%Fn3IY@SxI}+ZyUhB*u~`v5EMoCx zj}7Q442*jEjod6{s6!YgNAKYVKizU$zlYcLz8t3G4XXT`49Z5}h4TfLHhSB17j0D+ zMVlXNiV&3uf+sI)hL$d`l7F0`kEk4dbY>q*`Rfp0^@ehq2+0?TxfmlGR5>mf}n!n7xpqpOt+Zs{8!(?i3M}f>Q(09fWnIohJs*OiLXlN~kU;otBMfb>zT|PfU#+ z4Rh_mQiR>98#Feui5{w~-#+C08Bttu8`X`;;=RuTiXqf&BG!bQzkDD14_TufJ=y^Q z6RNmB+WyqN>u*Y-2i8QEOEA)Zt?8EjVkB7`v{f%Yq+?Z3j+}Yc9KDn1(d&~M4UdU| zbk!ZE?~jEwW#9uC0neRH=3lI*fH8}f$yTcPr`ETze~Vbu-r*S+K$E?6^A1@|EF^vVJG76u-X^7hxeyy{Fff9^Jl|DiI1 zyDccl?0J^J zZPM>sWZ}Q&qd4038+JQd-j7|Q`)=l`6vmCqd^?e0pP}sy4-QE7)?XF{o?KcnbY1$W zq@@SM?th~COq$1}9IP&fiZ++D+iVriINl!vLbiC$!HTK3Ot8s!cj4+STsf6D;+1c$ zRcSgfHpr5vOhRPK8e_S9kl2CX3lE^>-sQ57&Nm%_6bW)5<;$b6BxYBmVjaTzK>KGo z>DlL%uS*25fu2uaQWWf{kM%Y2F;H%#BoCJw-s&UjQh=|!{V2(8CnJRExOI~1>#GS> z&z&}2mB{&O_PIAE&XX$CkuzAv_ga+TByQ>PSE&;(b~Ns881HLSfplVUt->Z0l+qhM+4BtcM@y6v-EQc>so-g_06VSpdv-;w@q*o=Xs!CjzH-06&9HJWK zN&Y%(^VHAt)ixdO4f^dQbFgoDuDhYAmQrk~$87eJD-&IwgpHOwpY~x#z6*kw=EoTQ ztM_W9N4Ga$4mDuFpV#!q6rExvQ+&zu@FWYRB=P|{RdgU7+K%#gf$WcNpvOL4oive4 zd~ihPS~(OpBOMZ~yS=@sa~_5XpJ$Ke+VEtt z1Cm4`SEp3FF-Re8obj`CDuSom*~a;x^s9XKZB~ydZQ}<^)BQiCp$VPAuSIsXG|miy zUKj%z+SDoHpL%;`<@`fyN1OwS{*qwSEU&uuy7L*Jir=?8Dl$$udnhAdl|$*5IvdAH zxfkZDN#y|Ae0HZXhiwl*dD*l*{pbh8{)s`yDbn2-4UhQbck_8LNvVrkDg9Q`Haq@M z#!`ST&#mHGm$K_}Tyj(mD}n>vf}?Cly-t?^8aMh_w=- zsVksK?F4Q1v&`EuOi6rr>t1(DipKXOp6-F#(lLf!jeG;)btSu+JxQXFp`3-p-%b(L zF4T7y7$;iz2cFk(56ncYOoha>7bHc9Wr{n2<_vNkZvSB{|C^TlTi2O3wddXI;1`6w z_D3EPHoBBpR6gZRZs816E{DPbv)6g?YZFi3*td4zksnx*=ziFwX4e;t70eYZw7D5NQf68A$l)jxW^3q9MS3=ox~$g|DM6%TzF)KX_;Bk>?1o?NB(}{7{*u)L=GPse zN8_Vb#SV%gI-}OY@lLm2U=|w8pJSOQH-sL1C zJc3UMms?sRk~{Yp@jb(`*M_aeLeW3jQHUXdoYoq zt!-jjT3Ud4Sn(5Fqzr+BPX0glegC)>JPTgrJN?RuYse*BaRNF}14x Date: Thu, 1 Mar 2018 19:51:48 +0800 Subject: [PATCH 03/12] =?UTF-8?q?ThreadLocal=E7=BB=A7=E6=89=BF=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- note/ThreadLocal/threadlocal.md | 94 ++++++++++++++++++++++++++++++++- 1 file changed, 93 insertions(+), 1 deletion(-) diff --git a/note/ThreadLocal/threadlocal.md b/note/ThreadLocal/threadlocal.md index 02ae339..364a0d6 100644 --- a/note/ThreadLocal/threadlocal.md +++ b/note/ThreadLocal/threadlocal.md @@ -234,4 +234,96 @@ static final class SuppliedThreadLocal extends ThreadLocal { [深入分析 ThreadLocal 内存泄漏问题](http://www.importnew.com/22039.html) -[ThreadLocal 内存泄露的实例分析](http://www.importnew.com/22046.html) \ No newline at end of file +[ThreadLocal 内存泄露的实例分析](http://www.importnew.com/22046.html) + +# 继承性问题 + +子线程中是否可以获得父线程设置的ThreadLocal变量? 答案是不可以,如以下测试代码: + +```java +public class Test { + + private ThreadLocal threadLocal = new InheritableThreadLocal<>(); + + public void test() throws InterruptedException { + threadLocal.set("parent"); + + Thread thread = new Thread(() -> { + System.out.println(threadLocal.get()); + threadLocal.set("child"); + System.out.println(threadLocal.get()); + }); + + thread.start(); + + thread.join(); + + System.out.println(threadLocal.get()); + } + + public static void main(String[] args) throws InterruptedException { + new Test().test(); + } + +} +``` + +执行结果是: + +```plaintext +null +child +parent +``` + +从中可以得出两个结论: + +- 子线程无法获得父线程设置的ThreadLocal。 +- 父子线程的ThreadLocal是相互独立的。 + +解决方法是使用java.lang.InheritableThreadLocal类。原因其实很容易理解: **ThreadLocal数据以线程为单位进行保存**,**InheritableThreadLocal的原理是在子线程创建的时候 +将父线程的变量(浅)拷贝到自身中**。 + +从源码的角度进行原理的说明,Thread中其实有两个ThreadLocalMap: + +```java +ThreadLocal.ThreadLocalMap threadLocals = null; +ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; +``` + +**普通的ThreadLocal被保存在threadLocals中,InheritableThreadLocal被保存在inheritableThreadLocal中**,注意这里是并列的关系,即两者可以同时存在且不为空。另外一个关键的问题便是 +父线程的变量是何时被复制到子线程中的,答案是在子线程创建时,init方法: + +```java +private void init(ThreadGroup g, Runnable target, String name, + long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { + if (inheritThreadLocals && parent.inheritableThreadLocals != null) + this.inheritableThreadLocals = + ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); +} +``` + +inheritThreadLocals除非我们使用了带AccessControlContext参数的构造器,默认都是true。 + +然而到了这里仍有问题存在:那就是线程池场景。一个线程**只会在创建时从其父线程中拷贝一次属性**,而线程池中的线程需要动态地执行从不同的上级线程提交地任务,在此种情形下逻辑上的 +父线程也就不再存在了,阿里巴巴的[transmittable-thread-local](https://github.com/alibaba/transmittable-thread-local)解决了这一问题,核心原理其实是实现了一个Runnable的包装, +伪代码如下: + +```java +public class Wrapper implements Runnable { + + private final Runnable target; + + @Override + public final void run() { + //1.拷贝父变量 + try { + target.run(); + } finally { + //2.还原... + } + } +} +``` + +参考: [ThreadLocal父子线程传递实现方案](https://zhuanlan.zhihu.com/p/28501035) \ No newline at end of file From 6f7945873bdc0f29c278bd80da70b14905138aba Mon Sep 17 00:00:00 2001 From: seaswalker Date: Mon, 2 Jul 2018 16:47:25 +0800 Subject: [PATCH 04/12] pretty code --- src/main/java/nio/Client.java | 8 ++-- src/main/java/nio/Server.java | 85 ++++++++++++++--------------------- 2 files changed, 39 insertions(+), 54 deletions(-) diff --git a/src/main/java/nio/Client.java b/src/main/java/nio/Client.java index 6f72ac8..dcc17a7 100644 --- a/src/main/java/nio/Client.java +++ b/src/main/java/nio/Client.java @@ -7,7 +7,6 @@ import java.net.Socket; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; -import java.util.Arrays; /** * NIO Client. @@ -23,13 +22,16 @@ public class Client { public void nioRead() throws IOException { SocketChannel channel = SocketChannel.open(); channel.configureBlocking(true); + channel.connect(new InetSocketAddress("192.168.80.128", 10010)); while (channel.isConnectionPending()) { channel.finishConnect(); } + ByteBuffer buffer = ByteBuffer.allocate(10); - int readed = channel.read(buffer); - System.out.println(readed); + int read = channel.read(buffer); + + System.out.println(read); } /** diff --git a/src/main/java/nio/Server.java b/src/main/java/nio/Server.java index a004506..16bc65d 100644 --- a/src/main/java/nio/Server.java +++ b/src/main/java/nio/Server.java @@ -19,64 +19,47 @@ public class Server { public static void main(String[] args) throws IOException { Selector selector = Selector.open(); ServerSocketChannel channel = ServerSocketChannel.open(); - //服务器绑定到特定的端口 + channel.socket().bind(new InetSocketAddress(8080)); - /* - * 设为非阻塞模式,FileChannel不可设为此模式 - */ + // none-blocking mode channel.configureBlocking(false); channel.register(selector, SelectionKey.OP_ACCEPT); + while (true) { - /* - * select()返回selector上就绪的通道的数量 - */ - if (selector.select() > 0) { - /* - * 返回就绪的通道集合 - */ - System.out.println("select醒来"); - Set keys = selector.selectedKeys(); - Iterator iterator = keys.iterator(); - SelectionKey key; - while (iterator.hasNext()) { - key = iterator.next(); - if (key.isAcceptable()) { - System.out.println("可acept"); - SocketChannel client = channel.accept(); - System.out.println("客户端连接: " + client.getRemoteAddress()); - client.configureBlocking(false); - client.register(selector, 0); - } - if (key.isWritable()) - System.out.println("可写"); - if (key.isReadable()) - System.out.println("可读"); - if (key.isConnectable()) - System.out.println("可连接"); - if (key.isConnectable()) { - System.out.println("可连接"); - } - iterator.remove(); - } - } else { - System.out.println("select 0"); + if (selector.select() == 0) { + continue; } - } - } - private static void close(SocketChannel client) { - new Thread(() -> { - try { - Thread.sleep(3000); - System.out.println("执行关关闭: " + Thread.currentThread().getName()); - client.close(); - System.out.println("关闭结束"); - } catch (InterruptedException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); + Set keys = selector.selectedKeys(); + Iterator iterator = keys.iterator(); + + SelectionKey key; + while (iterator.hasNext()) { + key = iterator.next(); + + if (key.isAcceptable()) { + SocketChannel client = channel.accept(); + System.out.println("客户端连接: " + client.getRemoteAddress()); + client.configureBlocking(false); + client.register(selector, 0); + } + + if (key.isWritable()) + System.out.println("可写"); + + if (key.isReadable()) + System.out.println("可读"); + + if (key.isConnectable()) + System.out.println("可连接"); + + if (key.isConnectable()) { + System.out.println("可连接"); + } + + iterator.remove(); } - }, "close-thread").start(); + } } } From 6297fdff1d3cd995b41ea33821a7deabd07939a3 Mon Sep 17 00:00:00 2001 From: skywalker Date: Wed, 4 Jul 2018 23:19:05 +0800 Subject: [PATCH 05/12] Update Server.java --- src/main/java/nio/Server.java | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/src/main/java/nio/Server.java b/src/main/java/nio/Server.java index 16bc65d..e7be4ea 100644 --- a/src/main/java/nio/Server.java +++ b/src/main/java/nio/Server.java @@ -1,5 +1,3 @@ -package nio; - import java.io.IOException; import java.net.InetSocketAddress; import java.nio.channels.SelectionKey; @@ -23,12 +21,10 @@ public static void main(String[] args) throws IOException { channel.socket().bind(new InetSocketAddress(8080)); // none-blocking mode channel.configureBlocking(false); + // register accept event channel.register(selector, SelectionKey.OP_ACCEPT); - while (true) { - if (selector.select() == 0) { - continue; - } + while (selector.select() != 0) { Set keys = selector.selectedKeys(); Iterator iterator = keys.iterator(); @@ -39,23 +35,16 @@ public static void main(String[] args) throws IOException { if (key.isAcceptable()) { SocketChannel client = channel.accept(); - System.out.println("客户端连接: " + client.getRemoteAddress()); + System.out.println("Client connected: " + client.getRemoteAddress()); client.configureBlocking(false); - client.register(selector, 0); + client.register(selector, SelectionKey.OP_READ); } if (key.isWritable()) - System.out.println("可写"); + System.out.println("writable."); if (key.isReadable()) - System.out.println("可读"); - - if (key.isConnectable()) - System.out.println("可连接"); - - if (key.isConnectable()) { - System.out.println("可连接"); - } + System.out.println("readable"); iterator.remove(); } From c7dd91b01232563146c2078b8051ca20d840281c Mon Sep 17 00:00:00 2001 From: skywalker Date: Wed, 4 Jul 2018 23:19:30 +0800 Subject: [PATCH 06/12] Update Server.java --- src/main/java/nio/Server.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/nio/Server.java b/src/main/java/nio/Server.java index e7be4ea..9167d67 100644 --- a/src/main/java/nio/Server.java +++ b/src/main/java/nio/Server.java @@ -1,3 +1,5 @@ +package nio; + import java.io.IOException; import java.net.InetSocketAddress; import java.nio.channels.SelectionKey; From 99b0f66c55944db454bc42369f7d243356a932a9 Mon Sep 17 00:00:00 2001 From: seaswalker Date: Tue, 16 Oct 2018 15:27:57 +0800 Subject: [PATCH 07/12] =?UTF-8?q?DNS=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- note/URLConnection/urlconnection.md | 48 ++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/note/URLConnection/urlconnection.md b/note/URLConnection/urlconnection.md index 7cd3584..30f26c8 100644 --- a/note/URLConnection/urlconnection.md +++ b/note/URLConnection/urlconnection.md @@ -302,4 +302,50 @@ public synchronized void print(PrintStream p) { # 响应解析 -其实就是获得输入流逐行解析的过程,不再向下展开。 \ No newline at end of file +其实就是获得输入流逐行解析的过程,不再向下展开。 + +# DNS解析 + +触发DNS解析的时机是HttpClient的New方法,默认的实现是Inet4AddressImpl的lookupAllHostAddr方法: + +```java +public native InetAddress[] + lookupAllHostAddr(String hostname) throws UnknownHostException; +``` + +native实现其实调用的是Linux的**getaddrinfo系统**调用,当然JDK在java层面也有对解析结果的缓存。 + +如何查看Linux的DNS服务器地址呢? + +- 配置文件 + + ```shell + cat /etc/resolv.conf + ``` + + 结果如下: + + ```html + nameserver 10.0.0.2 + ``` + +- nslookup: + + ```shell + nslookup baidu.com + ``` + + 结果: + + ```html + Server: 10.0.0.2 + Address: 10.0.0.2#53 + + Non-authoritative answer: + Name: baidu.com + Address: 220.181.57.216 + Name: baidu.com + Address: 123.125.115.110 + ``` + + 所以DNS便是10.0.0.2 \ No newline at end of file From 5faa515fe9ca45ac54fd4010d86436a7cd766dc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=B3=E9=98=B3?= <260893248@qq.com> Date: Sun, 23 Feb 2020 10:36:00 +0800 Subject: [PATCH 08/12] Gauva -> Guava fix typo --- note/HashMap/hashmap.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/note/HashMap/hashmap.md b/note/HashMap/hashmap.md index a9d0258..371c36c 100644 --- a/note/HashMap/hashmap.md +++ b/note/HashMap/hashmap.md @@ -254,5 +254,5 @@ public boolean containsValue(Object value) { } ``` -这是一个遍历所有bin + 链表/红黑树的过程,所以有过有根据value查找key的需求我们可以使用双向Map,比如Gauva的BiMap。 +这是一个遍历所有bin + 链表/红黑树的过程,所以有过有根据value查找key的需求我们可以使用双向Map,比如Guava的BiMap。 From bf66f937ad020602930b0d291a9244a677dd3121 Mon Sep 17 00:00:00 2001 From: seaswalker Date: Sat, 27 Jun 2020 14:18:55 +0800 Subject: [PATCH 09/12] test volatile --- src/main/java/test/Test.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/main/java/test/Test.java b/src/main/java/test/Test.java index 5499e74..20f0e4b 100644 --- a/src/main/java/test/Test.java +++ b/src/main/java/test/Test.java @@ -87,4 +87,23 @@ public void canWakeUp() throws InterruptedException, ExecutionException { System.out.println("被唤醒"); } + private boolean flag = true; + + @org.junit.Test + public void testVolatile() { + new Thread(() -> { + try { + System.out.println("子线程启动"); + TimeUnit.SECONDS.sleep(3); + flag = false; + System.out.println("flag false"); + } catch (InterruptedException ignore) { + } + }).start(); + + while (flag) { + System.out.print(1); + } + } + } From 24597c3f12058e343f6ce79e4f4747f5b1ede00c Mon Sep 17 00:00:00 2001 From: skywalker Date: Sat, 4 Jul 2020 15:11:14 +0800 Subject: [PATCH 10/12] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index c2afbc3..df389c4 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +# Issue +一些新的记录会写在issue里面 + # 传送门 [Condition](https://github.com/seaswalker/JDK/blob/master/note/Condition/Condition.md) From 2c2b1a7a163e5a304e3e67191d9879265f47c3c3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Oct 2020 01:36:44 +0000 Subject: [PATCH 11/12] Bump junit from 4.12 to 4.13.1 Bumps [junit](https://github.com/junit-team/junit4) from 4.12 to 4.13.1. - [Release notes](https://github.com/junit-team/junit4/releases) - [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.12.md) - [Commits](https://github.com/junit-team/junit4/compare/r4.12...r4.13.1) Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 92c5861..670e849 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ junit junit - 4.12 + 4.13.1 From b830a57a67c7fbdf22bff345ca6ad5e5555000a9 Mon Sep 17 00:00:00 2001 From: skywalker Date: Thu, 3 Dec 2020 12:22:10 +0800 Subject: [PATCH 12/12] keep-alive --- note/URLConnection/urlconnection.md | 147 +++++++++++++++++++++++++++- 1 file changed, 146 insertions(+), 1 deletion(-) diff --git a/note/URLConnection/urlconnection.md b/note/URLConnection/urlconnection.md index 30f26c8..11e9bd0 100644 --- a/note/URLConnection/urlconnection.md +++ b/note/URLConnection/urlconnection.md @@ -348,4 +348,149 @@ native实现其实调用的是Linux的**getaddrinfo系统**调用,当然JDK在 Address: 123.125.115.110 ``` - 所以DNS便是10.0.0.2 \ No newline at end of file + 所以DNS便是10.0.0.2 + + # keep-alive + +在创建连接时,源码位于sun.net.www.http.New方法,省略版本: + +```java +public static HttpClient New(URL url, Proxy p, int to, boolean useCache, + HttpURLConnection httpuc) throws IOException { + HttpClient ret = null; + /* see if one's already around */ + if (useCache) { + ret = kac.get(url, null); + } + // ... +} +``` + +kac是connection的缓存,定义在HttpClient类中: + +```java +/* where we cache currently open, persistent connections */ +protected static KeepAliveCache kac = new KeepAliveCache(); +``` + +KeepAliveCache的定义如下: + +```java +/** + * A class that implements a cache of idle Http connections for keep-alive + * + * @author Stephen R. Pietrowicz (NCSA) + * @author Dave Brown + */ +public class KeepAliveCache + extends HashMap + implements Runnable { + + static final int MAX_CONNECTIONS = 5; + static int result = -1; + static int getMaxConnections() { + if (result == -1) { + result = AccessController.doPrivileged( + new GetIntegerAction("http.maxConnections", MAX_CONNECTIONS)) + .intValue(); + if (result <= 0) { + result = MAX_CONNECTIONS; + } + } + return result; + } + +} +``` + +`getMaxConnections`指的是能够缓存的最大的连接数,如果不指定,默认是5. 那么缓存的链接的有效期是多少呢? + +在KeepAliveCache内有一个静态变量: + +```java +static final int LIFETIME = 5000; +``` + +这个是**过期检查线程运行的时间间隔(毫秒)**。此线程初始化: + +```java +AccessController.doPrivileged(new PrivilegedAction<>() { + public Void run() { + keepAliveTimer = InnocuousThread.newSystemThread("Keep-Alive-Timer", cache); + keepAliveTimer.setDaemon(true); + keepAliveTimer.setPriority(Thread.MAX_PRIORITY - 2); + keepAliveTimer.start(); + return null; + } +}); +``` + +运行的核心逻辑: + +```java +@Override +public void run() { + do { + try { + Thread.sleep(LIFETIME); + } catch (InterruptedException e) {} + + // Remove all outdated HttpClients. + synchronized (this) { + long currentTime = System.currentTimeMillis(); + List keysToRemove = new ArrayList<>(); + + for (KeepAliveKey key : keySet()) { + ClientVector v = get(key); + synchronized (v) { + KeepAliveEntry e = v.peek(); + while (e != null) { + if ((currentTime - e.idleStartTime) > v.nap) { + v.poll(); + e.hc.closeServer(); + } else { + break; + } + e = v.peek(); + } + + if (v.isEmpty()) { + keysToRemove.add(key); + } + } + } + + for (KeepAliveKey key : keysToRemove) { + removeVector(key); + } + } + } while (!isEmpty()); +} +``` + +一个缓存的连接的有效期在KeepAliveCache.put时确定: + +```java +/** + * Register this URL and HttpClient (that supports keep-alive) with the cache + * @param url The URL contains info about the host and port + * @param http The HttpClient to be cached + */ +public synchronized void put(final URL url, Object obj, HttpClient http) { + if (v == null) { + int keepAliveTimeout = http.getKeepAliveTimeout(); + v = new ClientVector(keepAliveTimeout > 0 ? + keepAliveTimeout * 1000 : LIFETIME); + v.put(http); + super.put(key, v); + } else { + v.put(http); + } +} +``` + +`http.getKeepAliveTimeout()`取的实际上是环境变量`http.keepAlive`的值。 + +最后还有一个问题,连接是在什么时机被放进缓存的? + +在HttpURLConnection场景下是其`getInputStream`方法。