diff --git a/README.md b/README.md index 214c1c8..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) @@ -47,3 +50,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 0000000..fd2bbff Binary files /dev/null and b/note/Enum/images/enum.png differ 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/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。 diff --git a/note/ThreadLocal/threadlocal.md b/note/ThreadLocal/threadlocal.md index 7852a42..364a0d6 100644 --- a/note/ThreadLocal/threadlocal.md +++ b/note/ThreadLocal/threadlocal.md @@ -226,4 +226,104 @@ 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) + +# 继承性问题 + +子线程中是否可以获得父线程设置的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 diff --git a/note/URLConnection/urlconnection.md b/note/URLConnection/urlconnection.md index 7cd3584..11e9bd0 100644 --- a/note/URLConnection/urlconnection.md +++ b/note/URLConnection/urlconnection.md @@ -302,4 +302,195 @@ 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 + + # 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`方法。 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 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..9167d67 100644 --- a/src/main/java/nio/Server.java +++ b/src/main/java/nio/Server.java @@ -19,64 +19,38 @@ 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); + // register accept event 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(); + + while (selector.select() != 0) { + + 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 connected: " + client.getRemoteAddress()); + client.configureBlocking(false); + client.register(selector, SelectionKey.OP_READ); } - } else { - System.out.println("select 0"); - } - } - } - 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(); + if (key.isWritable()) + System.out.println("writable."); + + if (key.isReadable()) + System.out.println("readable"); + + iterator.remove(); } - }, "close-thread").start(); + } } } 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); + } + } + }