├── .gitignore ├── README.md ├── data └── capture.rules ├── note ├── AsynchronousFileChannel │ ├── asynchronousfilechannel.md │ ├── asynchronousfilechannel.uml │ └── images │ │ ├── AsynchronousFileChannel.jpg │ │ └── CompletionHandler.jpg ├── Buffer │ ├── buffer.md │ ├── buffer.uml │ └── images │ │ ├── Buffer.jpg │ │ └── Cleaner.jpg ├── BufferedInputStream │ ├── bufferedinputstream.md │ ├── bufferedinputstream.uml │ └── images │ │ └── BufferedInputStream.jpg ├── ConcurrentHashMap │ └── concurrenthashmap.md ├── ConcurrentLinkedQueue │ ├── concurrentlinkedqueue.md │ ├── concurrentlinkedqueue.uml │ └── images │ │ ├── ConcurrentLinkedQueue.jpg │ │ ├── Node.jpg │ │ └── node_insert.png ├── Condition │ ├── Condition.md │ ├── Condition.uml │ └── images │ │ └── Condition.jpg ├── CountDownLatch │ ├── CountDownLatch.md │ ├── CountDownLatch.uml │ └── images │ │ └── CountDownLatch.jpg ├── CyclicBarrier │ ├── CyclicBarrier.md │ ├── CyclicBarrier.uml │ └── images │ │ └── CyclicBarrier.jpg ├── Enum │ ├── enum.md │ └── images │ │ └── enum.png ├── FileChannel │ ├── filechannel.md │ ├── filechannel.uml │ └── images │ │ ├── FileChannel.jpg │ │ ├── FileKey.jpg │ │ ├── FileLock.jpg │ │ └── FileLockTable.jpg ├── HashMap │ ├── hashmap.md │ ├── hashmap.uml │ └── images │ │ ├── HashMap.jpg │ │ └── Node.jpg ├── IO │ ├── images │ │ ├── File.jpg │ │ ├── FileOutputStream.jpg │ │ ├── FileSystem.jpg │ │ ├── FilterInputStream.jpg │ │ ├── RandomAccessFile.jpg │ │ └── Reader.jpg │ ├── io.md │ └── io.uml ├── LinkedHashMap │ ├── images │ │ └── Entry.jpg │ ├── linkedhashmap.md │ └── linkedhashmap.uml ├── NIO │ ├── images │ │ ├── AbstractInterruptibleChannel.jpg │ │ ├── Channels.jpg │ │ ├── SelectionKey.jpg │ │ ├── Selector.jpg │ │ ├── SelectorProvider.jpg │ │ ├── Selector_full.png │ │ ├── channel_close.png │ │ ├── fd.png │ │ ├── fd_inode.png │ │ ├── provider_full.png │ │ ├── tcp_meaning.png │ │ ├── wmem_default.png │ │ └── wmem_max.png │ ├── nio.md │ └── nio.uml ├── Process │ ├── images │ │ ├── Process.jpg │ │ ├── ProcessBuilder.jpg │ │ ├── Redirect.jpg │ │ ├── Thread.jpg │ │ ├── pipe_size.png │ │ ├── redirectErrorStream.png │ │ ├── redirect_group.png │ │ └── stream.PNG │ ├── process.md │ └── process.uml ├── ReadWriteLock │ ├── ReadWriteLock.md │ ├── ReadWriteLock.uml │ └── images │ │ ├── ReadWriteLock.jpg │ │ └── Sync.jpg ├── ReentrantLock │ ├── ReentrantLock.md │ ├── ReentrantLock.uml │ └── images │ │ ├── Node.jpg │ │ ├── ReentrantLock.jpg │ │ ├── Sync.jpg │ │ ├── condition_queue.jpg │ │ └── queue.jpg ├── Reflection │ ├── Reflection.uml │ ├── images │ │ └── Class.jpg │ └── reflection.md ├── ScheduledThreadPool │ ├── images │ │ ├── DelayedWorkQueue.jpg │ │ ├── ScheduledFutureTask.jpg │ │ ├── ScheduledThreadPoolExecutor.jpg │ │ └── heap.png │ ├── scheduledthreadpool.md │ └── scheduledthreadpool.uml ├── Socket │ ├── images │ │ ├── InetAddress.jpg │ │ ├── InetAddressImpl.jpg │ │ ├── InetSocketAddress.jpg │ │ ├── SocketInputStream.jpg │ │ └── SocksSocketImpl.jpg │ ├── socket.md │ └── socket.uml ├── Thread │ ├── images │ │ └── Thread.jpg │ ├── thread.md │ └── thread.uml ├── ThreadLocal │ ├── images │ │ └── ThreadLocal.jpg │ ├── threadlocal.md │ └── threadlocal.uml ├── ThreadPool │ ├── images │ │ ├── FutureTask.jpg │ │ ├── RejectedExecutionHandler.jpg │ │ ├── ThreadFactory.jpg │ │ ├── ThreadPoolExecutor.jpg │ │ ├── WaitNode.jpg │ │ └── Worker.jpg │ ├── threadpool.md │ └── threadpool.uml ├── TreeMap │ ├── images │ │ └── TreeMap.jpg │ ├── treemap.md │ └── treemap.uml ├── UDP │ ├── images │ │ ├── DatagramPacket.jpg │ │ ├── DatagramSocket.jpg │ │ └── DatagramSocketImpl.jpg │ ├── udp.md │ └── udp.uml └── URLConnection │ ├── images │ ├── CookieHandler.jpg │ ├── HttpCapture.jpg │ ├── HttpClient.jpg │ ├── MessageHeader.jpg │ ├── PrintStream.jpg │ ├── ResponseCache.jpg │ ├── URLConnection.jpg │ ├── URLStreamHandler.jpg │ └── URLStreamHandler子类.png │ ├── urlconnection.md │ └── urlconnection.uml ├── pom.xml └── src └── main └── java ├── META-INF └── MANIFEST.MF ├── buffer └── BufferTest.java ├── condition └── Demo.java ├── file └── FileTest.java ├── nio ├── Client.java └── Server.java ├── process └── ProcessTest.java ├── socket ├── Client.java └── Server.java ├── test ├── SomeQueue.java └── Test.java ├── udp ├── Client.java └── Server.java └── urlconnection └── Test.java /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Mobile Tools for Java (J2ME) 4 | .mtj.tmp/ 5 | 6 | # Package Files # 7 | *.jar 8 | *.war 9 | *.ear 10 | .settings/ 11 | build/ 12 | .classpath 13 | .project 14 | 15 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 16 | hs_err_pid* 17 | /.idea/ 18 | /JDK.iml 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Issue 2 | 一些新的记录会写在issue里面 3 | 4 | # 传送门 5 | 6 | [Condition](https://github.com/seaswalker/JDK/blob/master/note/Condition/Condition.md) 7 | 8 | [CountDownLatch](https://github.com/seaswalker/JDK/blob/master/note/CountDownLatch/CountDownLatch.md) 9 | 10 | [CyclicBarrier](https://github.com/seaswalker/JDK/blob/master/note/CyclicBarrier/CyclicBarrier.md) 11 | 12 | [ReadWriteLock](https://github.com/seaswalker/JDK/blob/master/note/ReadWriteLock/ReadWriteLock.md) 13 | 14 | [ReentrantLock](https://github.com/seaswalker/JDK/blob/master/note/ReentrantLock/ReentrantLock.md) 15 | 16 | [Socket](https://github.com/seaswalker/JDK/blob/master/note/Socket/socket.md) 17 | 18 | [UDP](https://github.com/seaswalker/JDK/blob/master/note/UDP/udp.md) 19 | 20 | [IO](https://github.com/seaswalker/JDK/blob/master/note/IO/io.md) 21 | 22 | [FileChannel](https://github.com/seaswalker/JDK/blob/master/note/FileChannel/filechannel.md) 23 | 24 | [Buffer](https://github.com/seaswalker/JDK/blob/master/note/Buffer/buffer.md) 25 | 26 | [URLConnection](https://github.com/seaswalker/JDK/blob/master/note/URLConnection/urlconnection.md) 27 | 28 | [NIO](https://github.com/seaswalker/JDK/blob/master/note/NIO/nio.md) 29 | 30 | [Process](https://github.com/seaswalker/JDK/blob/master/note/Process/process.md) 31 | 32 | [HashMap](https://github.com/seaswalker/JDK/blob/master/note/HashMap/hashmap.md) 33 | 34 | [LinkedHashMap](https://github.com/seaswalker/JDK/blob/master/note/LinkedHashMap/linkedhashmap.md) 35 | 36 | [TreeMap](https://github.com/seaswalker/JDK/blob/master/note/TreeMap/treemap.md) 37 | 38 | [ConcurrentHashMap](https://github.com/seaswalker/JDK/blob/master/note/ConcurrentHashMap/concurrenthashmap.md) 39 | 40 | [ConcurrentLinkedQueue](https://github.com/seaswalker/JDK/blob/master/note/ConcurrentLinkedQueue/concurrentlinkedqueue.md) 41 | 42 | [ThreadPool](https://github.com/seaswalker/JDK/blob/master/note/ThreadPool/threadpool.md) 43 | 44 | [ThreadLocal](https://github.com/seaswalker/JDK/blob/master/note/ThreadLocal/threadlocal.md) 45 | 46 | [Reflection](https://github.com/seaswalker/JDK/blob/master/note/Reflection/reflection.md) 47 | 48 | [ScheduledThreadPool](https://github.com/seaswalker/JDK/blob/master/note/ScheduledThreadPool/scheduledthreadpool.md) 49 | 50 | [AsynchronousFileChannel](https://github.com/seaswalker/JDK/blob/master/note/AsynchronousFileChannel/asynchronousfilechannel.md) 51 | 52 | [BufferedInputStream](https://github.com/seaswalker/JDK/blob/master/note/BufferedInputStream/bufferedinputstream.md) 53 | 54 | [Enum](https://github.com/seaswalker/JDK/blob/master/note/Enum/enum.md) 55 | -------------------------------------------------------------------------------- /data/capture.rules: -------------------------------------------------------------------------------- 1 | www\.baidu\.com , baidu%d.log -------------------------------------------------------------------------------- /note/AsynchronousFileChannel/asynchronousfilechannel.md: -------------------------------------------------------------------------------- 1 | 异步的文件通道,FileChannel属于"同步非阻塞",AsynchronousFileChannel才是真正的异步。 2 | 3 | ![AsynchronousFileChannel](images/AsynchronousFileChannel.jpg) 4 | 5 | 从主要方法read/write的返回值为Future可以看出其"异步"的端倪。 6 | 7 | # open 8 | 9 | ```java 10 | public static AsynchronousFileChannel open(Path file, 11 | Set options, 12 | ExecutorService executor, 13 | FileAttribute... attrs) { 14 | FileSystemProvider provider = file.getFileSystem().provider(); 15 | return provider.newAsynchronousFileChannel(file, options, executor, attrs); 16 | } 17 | ``` 18 | 19 | 另外一个重载的简化方法声明如下: 20 | 21 | ```java 22 | public static AsynchronousFileChannel open(Path file, OpenOption... options) {} 23 | ``` 24 | 25 | 所做的处理便是将可变参数options手动转为Set,executor为null,文件属性为NO_ATTRIBUTES,其实就是一个空的数组。 26 | 27 | 忽略调用关系,对于Linux来说最终由sun.nio.ch.SimpleAsynchronousFileChannelImpl的open方法实现: 28 | 29 | ```java 30 | public static AsynchronousFileChannel open(FileDescriptor fdo, 31 | boolean reading, 32 | boolean writing, 33 | ThreadPool pool) { 34 | // Executor is either default or based on pool parameters 35 | ExecutorService executor = (pool == null) ? DefaultExecutorHolder.defaultExecutor : pool.executor(); 36 | return new SimpleAsynchronousFileChannelImpl(fdo, reading, writing, executor); 37 | } 38 | ``` 39 | 40 | 默认情况下pool参数为null,ThreadPool位于sun.nio.ch包下,其实是对Java线程池ExecutorService的一层包装。DefaultExecutorHolder是SimpleAsynchronousFileChannelImpl的嵌套类,这其实是一个单例模式: 41 | 42 | ```java 43 | private static class DefaultExecutorHolder { 44 | static final ExecutorService defaultExecutor = ThreadPool.createDefault().executor(); 45 | } 46 | ``` 47 | 48 | createDefault方法: 49 | 50 | ```java 51 | static ThreadPool createDefault() { 52 | int initialSize = getDefaultThreadPoolInitialSize(); 53 | if (initialSize < 0) 54 | initialSize = Runtime.getRuntime().availableProcessors(); 55 | ThreadFactory threadFactory = getDefaultThreadPoolThreadFactory(); 56 | if (threadFactory == null) 57 | threadFactory = defaultThreadFactory; 58 | ExecutorService executor = Executors.newCachedThreadPool(threadFactory); 59 | return new ThreadPool(executor, false, initialSize); 60 | } 61 | ``` 62 | 63 | ## 初始大小 64 | 65 | 提供AIO操作的线程池初始大小由环境变量`java.nio.channels.DefaultThreadPool.initialSize`决定,不过从源码来看,此参数只是被保存到了ThreadPool内部,没有看到其真正发挥作用的地方。 66 | 67 | ## 线程工厂 68 | 69 | 默认的线程工厂如下: 70 | 71 | ```java 72 | private static final ThreadFactory defaultThreadFactory = new ThreadFactory() { 73 | @Override 74 | public Thread newThread(Runnable r) { 75 | Thread t = new Thread(r); 76 | t.setDaemon(true); 77 | return t; 78 | } 79 | }; 80 | ``` 81 | 82 | 采用了守护线程。我们可以通过参数`java.nio.channels.DefaultThreadPool.threadFactory`自定义使用的线程工厂,参数的值为线程工厂的完整类名,getDefaultThreadPoolThreadFactory使用反射的方法将其初始化。 83 | 84 | # 异步的奥秘 85 | 86 | 我们以读为例,实现位于SimpleAsynchronousFileChannelImpl.implRead(简略版): 87 | 88 | ```java 89 | @Override 90 | Future implRead(final ByteBuffer dst, 91 | final long position, 92 | final A attachment, 93 | final CompletionHandler handler) { 94 | //创建Future 95 | final PendingFuture result = (handler == null) ? 96 | new PendingFuture(this) : null; 97 | Runnable task = new Runnable() { 98 | public void run() { 99 | int n = 0; 100 | Throwable exc = null; 101 | int ti = threads.add(); 102 | try { 103 | begin(); 104 | do { 105 | n = IOUtil.read(fdObj, dst, position, nd); 106 | } while ((n == IOStatus.INTERRUPTED) && isOpen()); 107 | if (n < 0 && !isOpen()) 108 | throw new AsynchronousCloseException(); 109 | } catch (IOException x) { 110 | if (!isOpen()) 111 | x = new AsynchronousCloseException(); 112 | exc = x; 113 | } finally { 114 | end(); 115 | threads.remove(ti); 116 | } 117 | if (handler == null) { 118 | result.setResult(n, exc); 119 | } else { 120 | //调用回调函数 121 | Invoker.invokeUnchecked(handler, attachment, n, exc); 122 | } 123 | } 124 | }; 125 | executor.execute(task); 126 | return result; 127 | } 128 | ``` 129 | 130 | 很简单,就是使用线程池去执行IO操作,执行完毕后再通过Future或回调函数通知我们。 131 | 132 | 回调接口类图如下: 133 | 134 | ![CompletionHandler](images/CompletionHandler.jpg) -------------------------------------------------------------------------------- /note/AsynchronousFileChannel/images/AsynchronousFileChannel.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/AsynchronousFileChannel/images/AsynchronousFileChannel.jpg -------------------------------------------------------------------------------- /note/AsynchronousFileChannel/images/CompletionHandler.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/AsynchronousFileChannel/images/CompletionHandler.jpg -------------------------------------------------------------------------------- /note/Buffer/buffer.md: -------------------------------------------------------------------------------- 1 | # Buffer 2 | 3 | 说到nio就离不开Buffer体系,类图: 4 | 5 | ![Buffer类图](images/Buffer.jpg) 6 | 7 | 这里以ByteBuffer为例,其实**对于布尔类型的其它所有基本类型,都与一种Buffer与之对应,比如还有ShortBuffer**。 8 | 9 | 图中的getXXX指的是获得各种数据类型的便利方法,比如getChar,getInt,putXXX也是一样。asXXX指到其它类型Buffer的转换方法,比如asCharBuffer。 10 | 11 | 注意,只有ByteBuffer才拥有getXXX, putXXX, asXXX等向其它类型转换的方法,也只有ByteBuffer可以获取direct类型的buffer。 12 | 13 | ## 获取 14 | 15 | ### allocate 16 | 17 | 得到heap buffer,源码: 18 | 19 | ```java 20 | public static ByteBuffer allocate(int capacity) { 21 | return new HeapByteBuffer(capacity, capacity); 22 | } 23 | ``` 24 | 25 | HeapByteBuffer构造器: 26 | 27 | ```java 28 | HeapByteBuffer(int cap, int lim) { 29 | super(-1, 0, lim, cap, new byte[cap], 0); 30 | } 31 | ``` 32 | 33 | 可以看出,heap buffer的底层其实就是byte数组,构造完成之后各属性的状态如下: 34 | 35 | - mark: 0 36 | - pos: 0 37 | - limit: capacity 38 | - offset: 0 39 | 40 | ### allocateDirect 41 | 42 | 获取direct buffer,只有ByteBuffer才可以: 43 | 44 | ```java 45 | public static ByteBuffer allocateDirect(int capacity) { 46 | return new DirectByteBuffer(capacity); 47 | } 48 | ``` 49 | 50 | DirectByteBuffer构造器: 51 | 52 | ```java 53 | DirectByteBuffer(int cap) { 54 | super(-1, 0, cap, cap); 55 | boolean pa = VM.isDirectMemoryPageAligned(); 56 | int ps = Bits.pageSize(); 57 | long size = Math.max(1L, (long)cap + (pa ? ps : 0)); 58 | Bits.reserveMemory(size, cap); 59 | long base = 0; 60 | try { 61 | base = unsafe.allocateMemory(size); 62 | } catch (OutOfMemoryError x) { 63 | Bits.unreserveMemory(size, cap); 64 | throw x; 65 | } 66 | unsafe.setMemory(base, size, (byte) 0); 67 | if (pa && (base % ps != 0)) { 68 | // Round up to page boundary 69 | address = base + ps - (base & (ps - 1)); 70 | } else { 71 | address = base; 72 | } 73 | cleaner = Cleaner.create(this, new Deallocator(base, size, cap)); 74 | att = null; 75 | } 76 | ``` 77 | 78 | #### 内存对齐 79 | 80 | VM.isDirectMemoryPageAligned()用以判断JVM是否需要对堆外内存进行对齐,什么是内存页,什么是对齐参考: 81 | 82 | [内存分页大小对性能的提升原理](http://blog.csdn.net/chluknight/article/details/6689323) 83 | 84 | [内存对齐详解](http://blog.csdn.net/sdwuyulunbi/article/details/8510401) 85 | 86 | 此参数可以通过-Dsun.nio.PageAlignDirectMemory进行指定,默认是关闭的,在64位Windows JDK上实践证明-XX:[+|-]PageAlignDirectMemory不能用,提示未识别的参数。 87 | 88 | #### 最大堆外内存 89 | 90 | VM.saveAndRemoveProperties部分源码: 91 | 92 | ```java 93 | public static void saveAndRemoveProperties(Properties props) { 94 | String s = (String)props.remove("sun.nio.MaxDirectMemorySize"); 95 | if (s != null) { 96 | if (s.equals("-1")) { 97 | // -XX:MaxDirectMemorySize not given, take default 98 | directMemory = Runtime.getRuntime().maxMemory(); 99 | } else { 100 | long l = Long.parseLong(s); 101 | if (l > -1) 102 | directMemory = l; 103 | } 104 | } 105 | } 106 | ``` 107 | 108 | 可以看出,我们可以通过参数-XX:MaxDirectMemorySize或-Dsun.nio.MaxDirectMemorySize进行指定,那么默认大小又是多少呢? 109 | 110 | Runtime.maxMemory为native实现,注意以字节为单位。源码: 111 | 112 | ```c 113 | JNIEXPORT jlong JNICALL 114 | Java_java_lang_Runtime_maxMemory(JNIEnv *env, jobject this) { 115 | return JVM_MaxMemory(); 116 | } 117 | ``` 118 | 119 | JVM_MaxMemory为JVM源码,位于hotspot\src\share\vm\prims\jvm.cpp中: 120 | 121 | ```c++ 122 | JVM_ENTRY_NO_ENV(jlong, JVM_MaxMemory(void)) 123 | JVMWrapper("JVM_MaxMemory"); 124 | size_t n = Universe::heap()->max_capacity(); 125 | return convert_size_t_to_jlong(n); 126 | JVM_END 127 | ``` 128 | 129 | - [ ] 再往下深入需要自己搭建JVM源码环境,这里留个坑 130 | 131 | #### 内存页大小 132 | 133 | Bits.pageSize: 134 | 135 | ```java 136 | static int pageSize() { 137 | if (pageSize == -1) 138 | pageSize = unsafe().pageSize(); 139 | return pageSize; 140 | } 141 | ``` 142 | 143 | Unsafe的pageSize为native方法,最终由os_solaris.cpp中的vm_page_size方法实现: 144 | 145 | ```c++ 146 | int os::vm_page_size() { 147 | assert(page_size != -1, "must call os::init"); 148 | return page_size; 149 | } 150 | ``` 151 | 152 | 可以看出,此参数由init方法提供,相关源码: 153 | 154 | ```c++ 155 | void os::init(void) { 156 | page_size = sysconf(_SC_PAGESIZE); 157 | init_page_sizes((size_t) page_size); 158 | } 159 | ``` 160 | 161 | - [ ] 再留个坑 162 | 163 | #### 内存预留 164 | 165 | Bits.reserveMemory方法用以记录JVM当前direct内存的占用情况,注意这里只是记录,而不是真正的分配。源码: 166 | 167 | ```java 168 | static void reserveMemory(long size, int cap) { 169 | if (!memoryLimitSet && VM.isBooted()) { 170 | maxMemory = VM.maxDirectMemory(); 171 | memoryLimitSet = true; 172 | } 173 | // optimist! 174 | if (tryReserveMemory(size, cap)) { 175 | return; 176 | } 177 | final JavaLangRefAccess jlra = SharedSecrets.getJavaLangRefAccess(); 178 | 179 | // retry while helping enqueue pending Reference objects 180 | // which includes executing pending Cleaner(s) which includes 181 | // Cleaner(s) that free direct buffer memory 182 | while (jlra.tryHandlePendingReference()) { 183 | if (tryReserveMemory(size, cap)) { 184 | return; 185 | } 186 | } 187 | // trigger VM's Reference processing 188 | System.gc(); 189 | 190 | // a retry loop with exponential back-off delays 191 | // (this gives VM some time to do it's job) 192 | boolean interrupted = false; 193 | try { 194 | long sleepTime = 1; 195 | int sleeps = 0; 196 | while (true) { 197 | if (tryReserveMemory(size, cap)) { 198 | return; 199 | } 200 | if (sleeps >= MAX_SLEEPS) { 201 | break; 202 | } 203 | if (!jlra.tryHandlePendingReference()) { 204 | try { 205 | Thread.sleep(sleepTime); 206 | sleepTime <<= 1; 207 | sleeps++; 208 | } catch (InterruptedException e) { 209 | interrupted = true; 210 | } 211 | } 212 | } 213 | 214 | // no luck 215 | throw new OutOfMemoryError("Direct buffer memory"); 216 | 217 | } finally { 218 | if (interrupted) { 219 | // don't swallow interrupts 220 | Thread.currentThread().interrupt(); 221 | } 222 | } 223 | } 224 | ``` 225 | 226 | tryReserveMemory方法负责真正的预留工作: 227 | 228 | ```java 229 | private static boolean tryReserveMemory(long size, int cap) { 230 | long totalCap; 231 | while (cap <= maxMemory - (totalCap = totalCapacity.get())) { 232 | if (totalCapacity.compareAndSet(totalCap, totalCap + cap)) { 233 | reservedMemory.addAndGet(size); 234 | count.incrementAndGet(); 235 | return true; 236 | } 237 | } 238 | return false; 239 | } 240 | ``` 241 | 242 | maxMemory表示最大可分配的堆外内存,totalCapacity表示当前已分配的内存,这里使用了CAS,直到操作(分配)成功为止。但是如果堆外空余内存不够了,怎么办?JVM会尝试进行堆外内存的回收工作,参见内存回收一节,可以总结为以下三步: 243 | 244 | - 手动调用ReferenceHandler的tryHandlePending方法,处理可能存在的处于pending状态的非强类型的引用(特别是Cleaner)。 245 | 246 | - 尝试手动触发垃圾回收,注意正是因为这个原因,如果使用了堆外内存,谨慎设置JVM 247 | 248 | -XX:+DisableExplicitGC。 249 | 250 | - 自旋+睡眠等待(可能的垃圾回收完成),共自旋9次,每次睡眠1ms。 251 | 252 | #### 内存分配 253 | 254 | 相关代码: 255 | 256 | ```java 257 | long size = Math.max(1L, (long)cap + (pa ? ps : 0)) 258 | long base = 0; 259 | try { 260 | base = unsafe.allocateMemory(size); 261 | } catch (OutOfMemoryError x) { 262 | Bits.unreserveMemory(size, cap); 263 | throw x; 264 | } 265 | unsafe.setMemory(base, size, (byte) 0); 266 | //内存裁剪 267 | if (pa && (base % ps != 0)) { 268 | // Round up to page boundary 269 | address = base + ps - (base & (ps - 1)); 270 | } else { 271 | address = base; 272 | } 273 | ``` 274 | 275 | allocateMemory为native方法,通过malloc实现。setMemory方法的作用为将分配的内存区块的值全部设为0(最后一个参数便是要填充的值),因为malloc分配的内存的值实际上是上一次使用的值,即垃圾。 276 | 277 | setMemory的实现位于JVM copy.cpp的fill_to_memory_atomic方法: 278 | 279 | ```c++ 280 | void Copy::fill_to_memory_atomic(void* to, size_t size, jubyte value) { 281 | address dst = (address) to; 282 | uintptr_t bits = (uintptr_t) to | (uintptr_t) size; 283 | if (bits % sizeof(jlong) == 0) { 284 | jlong fill = (julong)( (jubyte)value ); // zero-extend 285 | if (fill != 0) { 286 | fill += fill << 8; 287 | fill += fill << 16; 288 | fill += fill << 32; 289 | } 290 | //Copy::fill_to_jlongs_atomic((jlong*) dst, size / sizeof(jlong)); 291 | for (uintptr_t off = 0; off < size; off += sizeof(jlong)) { 292 | *(jlong*)(dst + off) = fill; 293 | } 294 | //忽略int,short整倍数的情况 295 | } else { 296 | // Not aligned, so no need to be atomic. 297 | Copy::fill_to_bytes(dst, size, value); 298 | } 299 | } 300 | ``` 301 | 302 | 可以看出,如果申请的内存是long长度的整倍数,那么使用手动逐一赋值的方式,否则使用系统调用memset(hotspot\src\cpu\x86\vm\copy_x86.hpp)。为什么手动逐一赋值就能保证原子性呢? 303 | 304 | 如果设置了内存对齐,系统实际上比需要的内存多申请一个页的大小,即如果我们需要 4100的内存,而页大小是4096(64位Windows便是这个大小),实际申请了4100 + 4096 = 8196字节的内存,这么做是为了后续的对齐,对齐的关键代码: 305 | 306 | ```java 307 | address = base + ps - (base & (ps - 1)); 308 | ``` 309 | 310 | 这样的结果就是起始的内存地址是一个页的起始地址,好处便是CPU在读取内存可以少读一个内存页。 311 | 312 | #### 内存回收 313 | 314 | 由于direct buffer位于堆外,所以JVM垃圾回收自然无法对齐进行回收。一个完整的堆外内存由两部分组成: 315 | 316 | - 堆内的DirectBuffer对象,持有堆外内存的地址。 317 | - 堆外内存("冰山"内存)。 318 | 319 | 所以一旦DirectBuffer对象被JVM回收,就没有其它对象知道与之对应的堆外内存的地址,也就造成了内存泄漏。这一问题的解决方法就是当DirectBuffer对象不再被需要(即可以进行回收)时由GC线程进行通知,通知我们可以回收掉堆外内存。那如何得到通知呢? 320 | 321 | 这便是Java里面Reference和ReferenceQueue的用途了,这一部分可以参考: 322 | 323 | [Java Reference 源码分析](http://www.cnblogs.com/jabnih/p/6580665.html) 324 | 325 | [深入分析Object.finalize方法的实现原理](http://www.jianshu.com/p/9d2788fffd5f) 326 | 327 | DirectByteBuffer构造器相关源码: 328 | 329 | ```java 330 | DirectByteBuffer(int cap) { 331 | cleaner = Cleaner.create(this, new Deallocator(base, size, cap)); 332 | } 333 | ``` 334 | 335 | Cleaner负责堆外内存的回收,这是Java中一种代替finalize方法的方式,类图: 336 | 337 | ![Cleaner类图](images/Cleaner.jpg) 338 | 339 | 既然是代替,那么它必然解决了finalize的缺点: 340 | 341 | - 如果一个类重写了finalize()方法,那么JVM会将其包装成为java.lang.ref.Finalizer对象,由JVM维护其多余的状态,这无疑加重了JVM的负担。 342 | - finalize()方法由Finalizer.FinalizerThread线程负责调用,而**此线程是一个优先级非常低的线程**,这就导致Finalizer可能永远无法被回收。 343 | 344 | Cleaner的实现特点: 345 | 346 | - 由Reference.ReferenceHandler线程负责调用其clean方法,其run方法源码: 347 | 348 | ```java 349 | public void run() { 350 | while (true) { 351 | tryHandlePending(true); 352 | } 353 | } 354 | ``` 355 | 356 | tryHandlePending部分源码: 357 | 358 | ```java 359 | static boolean tryHandlePending(boolean waitForNotify) { 360 | synchronized (lock) { 361 | if (pending != null) { 362 | r = pending; 363 | c = r instanceof Cleaner ? (Cleaner) r : null; 364 | } 365 | } 366 | // Fast path for cleaners 367 | if (c != null) { 368 | c.clean(); 369 | return true; 370 | } 371 | } 372 | ``` 373 | 374 | 而ReferenceHandler是一个优先级较高的线程,这就保证了清理的及时性。 375 | 376 | 和普通的Java对象一样,如果DirectByteBuffer对象晋升到了老年代,那么除非发生Full GC,否则其对应的堆外内存将会一直存在,因为Cleaner的清理发生在引用处理阶段,即JVM将其状态标记为pending时,这样就有可能耗尽系统内存(JVM heap内存反而有剩余)。参考: 377 | 378 | [JVM源码分析之堆外内存完全解读](https://yq.aliyun.com/articles/2948?spm=5176.100238.yqhn2.9.cpdyiE) 379 | 380 | [finalization-and-phantom](https://dzone.com/articles/finalization-and-phantom) 381 | 382 | ##### 回收 383 | 384 | Cleaner的clean方法其实是对Cleaner.create的第二个参数run方法的调用,所以真正清理的载体是DirectByteBuffer的内部类Deallocator,其实现了Runnable接口: 385 | 386 | ```java 387 | public void run() { 388 | unsafe.freeMemory(address); 389 | address = 0; 390 | Bits.unreserveMemory(size, capacity); 391 | } 392 | ``` 393 | 394 | 由函数free实现。 395 | 396 | ##### 手动回收 397 | 398 | 从类图可以看出,堆外内存实现了sun.nio.ch.DirectBuffer接口,而此接口定义了cleaner方法以获取与之对应的清理器,所以我们可以在这里手动调用其clean方法。 399 | 400 | ### wrap 401 | 402 | ByteBuffer.wrap: 403 | 404 | ```java 405 | public static ByteBuffer wrap(byte[] array, int offset, int length) { 406 | try { 407 | return new HeapByteBuffer(array, offset, length); 408 | } catch (IllegalArgumentException x) { 409 | throw new IndexOutOfBoundsException(); 410 | } 411 | } 412 | ``` 413 | 414 | **wrap得到的buffer只能是heap buffer**。 415 | 416 | ## 数据读取 417 | 418 | 我们以HeapByteBuffer的getInt(int i)方法为例: 419 | 420 | ```java 421 | public int getInt(int i) { 422 | //默认是大端 423 | return Bits.getInt(this, ix(checkIndex(i, 4)), bigEndian); 424 | } 425 | ``` 426 | 427 | checkIndex用于检查在当前位置是否还有4字节的数据可读。 428 | 429 | ix是在当前位置(i)的基础上加上偏移: 430 | 431 | ```java 432 | protected int ix(int i) { 433 | return i + offset; 434 | } 435 | ``` 436 | 437 | 为什么需要偏移呢?因为ByteBuffer允许我们以byte数组为基础创建buffer,当然指定一个偏移也就是清理之中了。 438 | 439 | Bits.getInt: 440 | 441 | ```java 442 | static int getInt(ByteBuffer bb, int bi, boolean bigEndian) { 443 | return bigEndian ? getIntB(bb, bi) : getIntL(bb, bi) ; 444 | } 445 | ``` 446 | 447 | 以大端getIntB为例: 448 | 449 | ```java 450 | static int getIntB(ByteBuffer bb, int bi) { 451 | return makeInt(bb._get(bi),bb._get(bi + 1),bb._get(bi + 2),bb._get(bi + 3)); 452 | } 453 | static private int makeInt(byte b3, byte b2, byte b1, byte b0) { 454 | return (((b3) << 24) | ((b2 & 0xff) << 16) | ((b1 & 0xff) << 8) | ((b0 & 0xff))); 455 | } 456 | ``` 457 | 458 | 很容易理解了,高位在左边。 459 | 460 | ## 视图 461 | 462 | 我们可以在一个Buffer之上创建一个视图,视图和之前的buffer使用同一份数据(即视图的写原生buffer也能看见),但拥有自己的属性,比如position, limit。 463 | 464 | 我们以HeapByteBuffer的asIntBuffer为例: 465 | 466 | ```java 467 | public IntBuffer asIntBuffer() { 468 | //int buffer的数据长度为byte buffer除以4 469 | int size = this.remaining() >> 2; 470 | int off = offset + position(); 471 | return (bigEndian 472 | ? (IntBuffer)(new ByteBufferAsIntBufferB(this,-1,0,size,size,off)) 473 | : (IntBuffer)(new ByteBufferAsIntBufferL(this,-1,0,size,size,off))); 474 | } 475 | ``` 476 | 477 | 构造器只是属性的拷贝,不再赘述。我们来看一下数据的读取,以大端的int读取为例: 478 | 479 | ```java 480 | public int get(int i) { 481 | return Bits.getIntB(bb, ix(checkIndex(i))); 482 | } 483 | ``` 484 | 485 | 和ByteBuffer的实现基本一致,唯一的区别是ix方法,ByteBufferAsIntBufferB.ix: 486 | 487 | ```java 488 | protected int ix(int i) { 489 | return (i << 2) + offset; 490 | } 491 | ``` 492 | 493 | 乘以4再加偏移。 494 | 495 | ## 压缩整理 496 | 497 | compact方法允许我们将内容拷贝至数组开头,比如我们有一段数据从4-9,compact之后便是从0-5.HeapByteBuffer.compact: 498 | 499 | ```java 500 | public ByteBuffer compact() { 501 | System.arraycopy(hb, ix(position()), hb, ix(0), remaining()); 502 | position(remaining()); 503 | limit(capacity()); 504 | discardMark(); 505 | return this; 506 | } 507 | ``` 508 | 509 | 很简单,就是一个数组拷贝再重新设置属性的过程。 510 | 511 | ## 复制 512 | 513 | duplicate方法可以返回一个buffer的拷贝,拷贝与原buffer共享一份存储,拥有自己的属性(但初始时和原buffer完全一致)。 514 | 515 | ## slice 516 | 517 | 和复制、视图一样,这货也是在共享数据存储的情况下搞出一个新的buffer来,**相当于compact和duplicate两个过程的组合**,HeapByteBuffer.slice: 518 | 519 | ```java 520 | public ByteBuffer slice() { 521 | return new HeapByteBuffer(hb,-1,0,this.remaining(), 522 | this.remaining(),this.position() + offset); 523 | } 524 | ``` 525 | 526 | -------------------------------------------------------------------------------- /note/Buffer/images/Buffer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/Buffer/images/Buffer.jpg -------------------------------------------------------------------------------- /note/Buffer/images/Cleaner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/Buffer/images/Cleaner.jpg -------------------------------------------------------------------------------- /note/BufferedInputStream/bufferedinputstream.md: -------------------------------------------------------------------------------- 1 | # 概览 2 | 3 | ![BufferedInputStream](images/BufferedInputStream.jpg) 4 | 5 | BufferedInputStream的主要方法均为线程安全,其内部的byte数组的最大值为int的最大值减去8,这样做的原因是在Java中数组是一个对象,而一个对象最大拥有8字节的对象头。至于为什么是int的最大值而不是long的最大值,需要同时支持32位和64位虚拟机吧。 6 | 7 | 字段count的取值范围为[0, buf.length],意义为[0, count)范围内的字节都是可读的,字段pos的取值范围为[0, count),即读指针的位置。 8 | 9 | # read 10 | 11 | ```java 12 | public synchronized int read() throws IOException { 13 | if (pos >= count) { 14 | fill(); 15 | if (pos >= count) 16 | return -1; 17 | } 18 | return getBufIfOpen()[pos++] & 0xff; 19 | } 20 | ``` 21 | 22 | `pos >= count`说明缓冲区中已没有可读的字节,此时需要调用fill方法从依赖的输入流中真正地读取数据。 23 | 24 | ```java 25 | private void fill() throws IOException { 26 | //如果内部依赖的输入流已经关闭,那么getBufIfOpen会抛出异常 27 | byte[] buffer = getBufIfOpen(); 28 | if (markpos < 0) 29 | pos = 0; //markpos为负数,说明我们并未设置标记位置,即不需要再次读取标记处的数据,所以整个缓冲区直接覆盖写入即可 30 | else if (pos >= buffer.length) 31 | if (markpos > 0) { 32 | //markpos为正数,说明缓冲区中尚有可以利用的空间(因为此时pos为buf的长度),所以这里要做的就是整理缓冲区: 将被标记的数据([markpos, pos)) 33 | //拷贝至缓冲区开头 34 | int sz = pos - markpos; 35 | System.arraycopy(buffer, markpos, buffer, 0, sz); 36 | pos = sz; 37 | markpos = 0; 38 | } else if (buffer.length >= marklimit) { 39 | //此时markpos为0,且标记的内容长度超过了marklimit限制,直接丢弃 40 | markpos = -1; 41 | pos = 0; 42 | } else if (buffer.length >= MAX_BUFFER_SIZE) { 43 | throw new OutOfMemoryError("Required array size too large"); 44 | } else { 45 | //到达这里的前提是markpos为0,pos已达到缓冲区长度,且缓冲区长度未超过marklimit,即我们已没有可用的空间以供进一步的数据读取 46 | //所以需要对缓冲区进行扩容 47 | int nsz = (pos <= MAX_BUFFER_SIZE - pos) ? 48 | pos * 2 : MAX_BUFFER_SIZE; 49 | //将缓冲区大小降低为marklimit,以减轻内存压力? 50 | if (nsz > marklimit) 51 | nsz = marklimit; 52 | byte nbuf[] = new byte[nsz]; 53 | System.arraycopy(buffer, 0, nbuf, 0, pos); 54 | if (!bufUpdater.compareAndSet(this, buffer, nbuf)) { 55 | //fill方法是在当前线程持有锁的情况下被调用的,所以这里CAS失败的唯一原因就是close方法已经被调用(close方法没有加锁),这也是对buf的 56 | //操作还需要通过CAS来完成的原因 57 | throw new IOException("Stream closed"); 58 | } 59 | buffer = nbuf; 60 | } 61 | count = pos; 62 | //调用底层输入流进行读取 63 | int n = getInIfOpen().read(buffer, pos, buffer.length - pos); 64 | if (n > 0) 65 | count = n + pos; 66 | } 67 | ``` 68 | 69 | 所以,如果当前底层输入流中没有可读的数据,那么fill方法将会阻塞直到: 70 | 71 | - 有数据可读。 72 | - 流被关闭。 73 | - EOF. 74 | 75 | # read(byte b[], int off, int len) 76 | 77 | 简略版源码: 78 | 79 | ```java 80 | public synchronized int read(byte b[], int off, int len) throws IOException { 81 | int n = 0; 82 | for (;;) { 83 | int nread = read1(b, off + n, len - n); 84 | if (nread <= 0) 85 | return (n == 0) ? nread : n; 86 | n += nread; 87 | if (n >= len) 88 | return n; 89 | // if not closed but no bytes available, return 90 | InputStream input = in; 91 | if (input != null && input.available() <= 0) 92 | return n; 93 | } 94 | } 95 | ``` 96 | 97 | 此方法的核心逻辑其实是一个do...while循环,即首先进行一次读(并不一定会进行真正的读操作,下面read1方法源码中可以看出),所以此read方法仍有可能被阻塞。 98 | 99 | ```java 100 | private int read1(byte[] b, int off, int len) throws IOException { 101 | int avail = count - pos; 102 | if (avail <= 0) { 103 | //如果需要的数据大小大于缓冲区大小且没有进行标记,那么直接返回读取的结果,可避免一次内存拷贝,为什么要没有进行标记才行呢? 104 | //因为被标记意味着此部分数据需要被再次读取 105 | if (len >= getBufIfOpen().length && markpos < 0) { 106 | return getInIfOpen().read(b, off, len); 107 | } 108 | fill(); 109 | avail = count - pos; 110 | if (avail <= 0) return -1; 111 | } 112 | int cnt = (avail < len) ? avail : len; 113 | //内存拷贝 114 | System.arraycopy(getBufIfOpen(), pos, b, off, cnt); 115 | pos += cnt; 116 | return cnt; 117 | } 118 | ``` 119 | 120 | ## 不会被阻塞的available 121 | 122 | 这里有一个很有意思的问题,之前在Socket部分没有注意到,read方法的下面一行: 123 | 124 | ```java 125 | if (input != null && input.available() <= 0) 126 | ``` 127 | 128 | available方法定义在InputStream中,返回输入流中当前可读的字节数,最主要的是即使在阻塞模式下的Socket输入流中这个方法也是非阻塞的,所以这就给我们提供了避免读阻塞(没有可读数据时)的机会。其在Windows下的native实现如下: 129 | 130 | ```c 131 | JNIEXPORT jint JNICALL Java_java_net_DualStackPlainSocketImpl_available0(JNIEnv *env, jclass clazz, jint fd) { 132 | jint available = -1; 133 | if ((ioctlsocket(fd, FIONREAD, &available)) == SOCKET_ERROR) { 134 | NET_ThrowNew(env, WSAGetLastError(), "socket available"); 135 | } 136 | return available; 137 | } 138 | ``` 139 | 140 | ioctlsocket函数为系统调用,可用于设置或读取socket相关信息,第二个参数为执行的命令,FIONREAD的含义如下(摘自MSDN): 141 | 142 | >Use to determine the amount of data pending in the network's input buffer that can be read from sockets. The argp parameter points to an unsigned long value in which ioctlsocket stores the result. FIONREAD returns the amount of data that can be read in a single call to the recv function, which may not be the same as the total amount of data queued on the socket. If s is message oriented (for example, type SOCK_DGRAM), FIONREAD still returns the amount of pending data in the network buffer, however, the amount that can actually be read in a single call to the recv function is limited to the data size written in the send or sendto function call. 143 | 144 | # skip 145 | 146 | ```java 147 | public synchronized long skip(long n) throws IOException { 148 | getBufIfOpen(); // Check for closed stream 149 | if (n <= 0) { 150 | return 0; 151 | } 152 | long avail = count - pos; 153 | if (avail <= 0) { 154 | // If no mark position set then don't keep in buffer 155 | if (markpos <0) 156 | return getInIfOpen().skip(n); 157 | // Fill in buffer to save bytes for reset 158 | fill(); 159 | avail = count - pos; 160 | if (avail <= 0) 161 | return 0; 162 | } 163 | long skipped = (avail < n) ? avail : n; 164 | pos += skipped; 165 | return skipped; 166 | } 167 | ``` 168 | 169 | 逻辑一目了然,不过注意下面这一行: 170 | 171 | ```java 172 | return getInIfOpen().skip(n); 173 | ``` 174 | 175 | 这里调用的是父类InputStream的同名方法,其实现非常简单粗暴: 直接读出需要跳过的数目的字节就好了。但是可以想象,对于文件输入流一定不是这样的实现,FileInputStream的源码证明了这一点: 176 | 177 | ```java 178 | public native long skip(long n) throws IOException; 179 | ``` 180 | 181 | 因为文件支持文件指针,直接移动文件指针便可以达到跳过字节的效果。 182 | 183 | # close 184 | 185 | ```java 186 | public void close() throws IOException { 187 | byte[] buffer; 188 | while ( (buffer = buf) != null) { 189 | if (bufUpdater.compareAndSet(this, buffer, null)) { 190 | InputStream input = in; 191 | in = null; 192 | if (input != null) 193 | input.close(); 194 | return; 195 | } 196 | // Else retry in case a new buf was CASed in fill() 197 | } 198 | } 199 | ``` 200 | 201 | 无锁情况下的CAS重试。 202 | 203 | -------------------------------------------------------------------------------- /note/BufferedInputStream/images/BufferedInputStream.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/BufferedInputStream/images/BufferedInputStream.jpg -------------------------------------------------------------------------------- /note/ConcurrentLinkedQueue/concurrentlinkedqueue.md: -------------------------------------------------------------------------------- 1 | ![ConcurrentLinkedQueue](images/ConcurrentLinkedQueue.jpg) 2 | 3 | 内部类Node的类图如下: 4 | 5 | ![Node类图](images/Node.jpg) 6 | 7 | # 算法 8 | 9 | 此类的实现基于算法Michael & Scott algorithm,论文: 10 | 11 | [Simple, Fast, and Practical Non-Blocking and Blocking Concurrent Queue Algorithms](https://www.research.ibm.com/people/m/michael/podc-1996.pdf) 12 | 13 | # offer 14 | 15 | ```java 16 | public boolean offer(E e) { 17 | //如果元素为null,那么抛出NPE 18 | checkNotNull(e); 19 | final Node newNode = new Node(e); 20 | for (Node t = tail, p = t;;) { 21 | //volatile读 22 | Node q = p.next; 23 | if (q == null) { 24 | // p is last node 25 | if (p.casNext(null, newNode)) { 26 | if (p != t) // hop two nodes at a time 27 | casTail(t, newNode); // Failure is OK. 28 | return true; 29 | } 30 | //casNext竞争失败,再次读取p.next,此时(即q)不为空 31 | } 32 | else if (p == q) 33 | // We have fallen off list. If tail is unchanged, it 34 | // will also be off-list, in which case we need to 35 | // jump to head, from which all live nodes are always 36 | // reachable. Else the new tail is a better bet. 37 | p = (t != (t = tail)) ? t : head; 38 | else 39 | //每插入两个新节点时才会重新设置尾节点 40 | p = (p != t && t != (t = tail)) ? t : q; 41 | } 42 | } 43 | ``` 44 | 45 | 注意,由于ConcurrentLinkedQueue为无界队列,所以offer方法永远返回true。为什么说插入两个节点之后才会尝试重新设置尾节点呢? 46 | 47 | 假设我们的线程执行casNext失败,那么说明此时tail/p指针的next指向必定不为空(即另外一个线程已经插入了一个节点),节点状态可用下图来表示: 48 | 49 | ![新节点插入](images/node_insert.png) 50 | 51 | 在下面的代码中将p指向新节点或重新读取tail: 52 | 53 | ```java 54 | p = (p != t && t != (t = tail)) ? t : q; 55 | ``` 56 | 57 | 那么为什么不能每插入一个节点便更新一次tail指针呢,因为出于降低CAS线程竞争(空转)的考虑。 58 | 59 | # 结尾 60 | 61 | 此类不再向下继续展开,直接参考"Java并发编程实战"一书的272页内容即可,书中对其使用的算法进行了一针见血的说明。 62 | 63 | # 问题 64 | 65 | 在debug的过程中发现了一个非常奇怪的问题,所以提出了知乎上的这个问题: 66 | 67 | [Java ConcurrentLinkedQueue疑问?](https://www.zhihu.com/question/59241701) 68 | 69 | 但是没有得到回答,在Stack Overflow上也有一个同样的问题: 70 | 71 | [What exactly UNSAFE.compareAndSwapObject does?](https://link.zhihu.com/?target=http%3A//stackoverflow.com/questions/22076083/what-exactly-unsafe-compareandswapobject-does) 72 | 73 | 但是同样没有回答。 74 | 75 | -------------------------------------------------------------------------------- /note/ConcurrentLinkedQueue/images/ConcurrentLinkedQueue.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/ConcurrentLinkedQueue/images/ConcurrentLinkedQueue.jpg -------------------------------------------------------------------------------- /note/ConcurrentLinkedQueue/images/Node.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/ConcurrentLinkedQueue/images/Node.jpg -------------------------------------------------------------------------------- /note/ConcurrentLinkedQueue/images/node_insert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/ConcurrentLinkedQueue/images/node_insert.png -------------------------------------------------------------------------------- /note/Condition/Condition.md: -------------------------------------------------------------------------------- 1 | # Condition 2 | 3 | ## 类图 4 | 5 | ![Condition类图](images/Condition.jpg) 6 | 7 | ConditionObject是AbstractQueuedSynchronizer的内部类。 8 | 9 | ## 创建 10 | 11 | 创建是通过Lock.newCondition()来完成的。 12 | 13 | ReentrantLock.newCondition: 14 | 15 | ```java 16 | public Condition newCondition() { 17 | return sync.newCondition(); 18 | } 19 | ``` 20 | 21 | Sync.newCondition: 22 | 23 | ```java 24 | final ConditionObject newCondition() { 25 | return new ConditionObject(); 26 | } 27 | ``` 28 | 29 | ## await 30 | 31 | ConditionObject.await: 32 | 33 | ```java 34 | public final void await() throws InterruptedException { 35 | if (Thread.interrupted()) 36 | throw new InterruptedException(); 37 | Node node = addConditionWaiter(); 38 | int savedState = fullyRelease(node); 39 | int interruptMode = 0; 40 | while (!isOnSyncQueue(node)) { 41 | LockSupport.park(this); 42 | if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) 43 | break; 44 | } 45 | if (acquireQueued(node, savedState) && interruptMode != THROW_IE) 46 | interruptMode = REINTERRUPT; 47 | if (node.nextWaiter != null) // clean up if cancelled 48 | unlinkCancelledWaiters(); 49 | if (interruptMode != 0) 50 | reportInterruptAfterWait(interruptMode); 51 | } 52 | ``` 53 | 54 | ### 条件队列 55 | 56 | addConditionWaiter用于向Lock的锁队列的条件队列添加新的等待节点,什么是锁队列参见ReentrantLock。 57 | 58 | ```java 59 | private Node addConditionWaiter() { 60 | Node t = lastWaiter; 61 | // If lastWaiter is cancelled, clean out. 62 | if (t != null && t.waitStatus != Node.CONDITION) { 63 | unlinkCancelledWaiters(); 64 | t = lastWaiter; 65 | } 66 | Node node = new Node(Thread.currentThread(), Node.CONDITION); 67 | if (t == null) 68 | firstWaiter = node; 69 | else 70 | t.nextWaiter = node; 71 | lastWaiter = node; 72 | return node; 73 | } 74 | ``` 75 | 76 | 一目了然。 77 | 78 | ### 锁释放 79 | 80 | **调用Condition.await方法的前提是拥有锁,await会释放锁**。 81 | 82 | AbstractQueuedSynchronizer.fullyRelease: 83 | 84 | ```java 85 | final int fullyRelease(Node node) { 86 | boolean failed = true; 87 | try { 88 | int savedState = getState(); 89 | if (release(savedState)) { 90 | failed = false; 91 | return savedState; 92 | } else { 93 | throw new IllegalMonitorStateException(); 94 | } 95 | } finally { 96 | if (failed) 97 | node.waitStatus = Node.CANCELLED; 98 | } 99 | } 100 | ``` 101 | 102 | 可以看出,不管有多少次重入,都会被一次性释放。fullyRelease方法返回的是锁释放之前的重入次数。 103 | 104 | ### 阻塞 105 | 106 | 锁释放后,便会将当前线程阻塞直到被signal或中断。 107 | 108 | ```java 109 | while (!isOnSyncQueue(node)) { 110 | LockSupport.park(this); 111 | if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) 112 | break; 113 | } 114 | ``` 115 | 116 | isOnSyncQueue用以判断节点是否在锁队列(SyncQueue)上,源码: 117 | 118 | ```java 119 | final boolean isOnSyncQueue(Node node) { 120 | if (node.waitStatus == Node.CONDITION || node.prev == null) 121 | return false; 122 | if (node.next != null) // If has successor, it must be on queue 123 | return true; 124 | return findNodeFromTail(node); 125 | } 126 | ``` 127 | 128 | 可见,只要满足waitStatus是CONDITION或prev/next为空,那么就不在锁队列上,因为条件队列是用nextWaiter连接的,而不是prev/next。 129 | 130 | 那么是么时候会spin到锁队列中去呢,应该是在signal中。 131 | 132 | #### 先中断 or 先唤醒 133 | 134 | checkInterruptWhileWaiting方法用以判断中断是发生在被signal之前还是之后。 135 | 136 | ConditionObject.checkInterruptWhileWaiting: 137 | 138 | ```java 139 | private int checkInterruptWhileWaiting(Node node) { 140 | //THROW_IE: -1,先中断 141 | //REINTERRUPT: 1,先唤醒 142 | return Thread.interrupted() ? 143 | (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) : 0; 144 | } 145 | ``` 146 | 147 | AbstractQueuedSynchronizer.transferAfterCancelledWait: 148 | 149 | ```java 150 | final boolean transferAfterCancelledWait(Node node) { 151 | if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) { 152 | enq(node); 153 | return true; 154 | } 155 | //等待signal线程完成到锁队列的转换工作 156 | while (!isOnSyncQueue(node)) 157 | Thread.yield(); 158 | return false; 159 | } 160 | ``` 161 | 162 | 可见,对先中断和先唤醒的区分是通过状态是不是CONDITION完成的,如果是先中断,那么将其状态设为0并将节点加入到锁队列。 163 | 164 | 节点的状态被设为0的原因后面会提到。 165 | 166 | ### 重新获得锁 167 | 168 | 由于当前线程本来是拥有锁的,所以从阻塞中恢复(被中断或唤醒)之后,需要恢复到最初的状态,才能继续执行后续的操作。 169 | 170 | 恢复是通过acquireQueued方法完成的: 171 | 172 | ```java 173 | if (acquireQueued(node, savedState) && interruptMode != THROW_IE) 174 | interruptMode = REINTERRUPT; 175 | ``` 176 | 177 | 从这里也可以看出保存最初的锁状态(savedState)的原因。 178 | 179 | acquireQueued方法在ReentrantLock中已经说过了,在此不再赘述,只是注意一点: 180 | 181 | AbstractQueuedSynchronizer.shouldParkAfterFailedAcquire: 182 | 183 | ```java 184 | private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { 185 | int ws = pred.waitStatus; 186 | if (ws == Node.SIGNAL) 187 | return true; 188 | if (ws > 0) { 189 | do { 190 | node.prev = pred = pred.prev; 191 | } while (pred.waitStatus > 0); 192 | pred.next = node; 193 | } else { 194 | //here 195 | compareAndSetWaitStatus(pred, ws, Node.SIGNAL); 196 | } 197 | return false; 198 | } 199 | ``` 200 | 201 | 可以看出,0状态被设为了SIGNAL,这就解释了上面被中断后为什么要将状态设为0了。 202 | 203 | ### 是否取消 204 | 205 | 获得锁之后,首先会对整个条件队列进行扫描,如果有被取消的节点,那么将其从队列中移除。 206 | 207 | ConditionObject.unlinkCancelledWaiters: 208 | 209 | ```java 210 | private void unlinkCancelledWaiters() { 211 | Node t = firstWaiter; 212 | Node trail = null; 213 | while (t != null) { 214 | Node next = t.nextWaiter; 215 | if (t.waitStatus != Node.CONDITION) { 216 | t.nextWaiter = null; 217 | if (trail == null) 218 | firstWaiter = next; 219 | else 220 | trail.nextWaiter = next; 221 | if (next == null) 222 | lastWaiter = trail; 223 | } 224 | else 225 | trail = t; 226 | t = next; 227 | } 228 | } 229 | ``` 230 | 231 | 就是一个喜闻乐见的链表操作。 232 | 233 | ### 中断报告 234 | 235 | ConditionObject.reportInterruptAfterWait: 236 | 237 | ```java 238 | private void reportInterruptAfterWait(int interruptMode) throws InterruptedException { 239 | if (interruptMode == THROW_IE) 240 | throw new InterruptedException(); 241 | else if (interruptMode == REINTERRUPT) 242 | //重新设置中断标志位 243 | selfInterrupt(); 244 | } 245 | ``` 246 | 247 | ## signalAll 248 | 249 | ConditionObject.signalAll: 250 | 251 | ```java 252 | public final void signalAll() { 253 | //检查当前线程是不是拥有锁 254 | if (!isHeldExclusively()) 255 | throw new IllegalMonitorStateException(); 256 | Node first = firstWaiter; 257 | if (first != null) 258 | doSignalAll(first); 259 | } 260 | ``` 261 | 262 | doSignalAll: 263 | 264 | ```java 265 | private void doSignalAll(Node first) { 266 | lastWaiter = firstWaiter = null; 267 | do { 268 | Node next = first.nextWaiter; 269 | first.nextWaiter = null; 270 | transferForSignal(first); 271 | first = next; 272 | } while (first != null); 273 | } 274 | ``` 275 | 276 | 可见,这里所做的就是**将节点从条件队列转移到锁队列中去**。 277 | 278 | ### 节点转移 279 | 280 | AbstractQueuedSynchronizer.transferForSignal: 281 | 282 | ```java 283 | final boolean transferForSignal(Node node) { 284 | if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) 285 | return false; 286 | Node p = enq(node); 287 | int ws = p.waitStatus; 288 | //将状态设为SIGNAL 289 | if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)) 290 | LockSupport.unpark(node.thread); 291 | return true; 292 | } 293 | ``` 294 | 295 | ### CAS 296 | 297 | 既然await和signal操作都是在有锁的情况下调用的,那么为什么还是存在大量CAS调用? 298 | 299 | 推测是这样的,await有一段代码是在没有锁的情况下执行的: 300 | 301 | ```java 302 | while (!isOnSyncQueue(node)) { 303 | LockSupport.park(this); 304 | if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) 305 | break; 306 | } 307 | ``` 308 | 309 | 与这段代码的交互需要CAS。 310 | 311 | ### 总结 312 | 313 | 唤醒操作(signal/signalAll)并不会使线程马上获得锁,而是使之拥有获得锁的资格。 314 | 315 | -------------------------------------------------------------------------------- /note/Condition/images/Condition.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/Condition/images/Condition.jpg -------------------------------------------------------------------------------- /note/CountDownLatch/CountDownLatch.md: -------------------------------------------------------------------------------- 1 | # CountDownLatch 2 | 3 | ## 类图 4 | 5 | CountDownLatch本身没有任何父类或是实现任何接口,只含有一个内部类Sync. 6 | 7 | ![CountDownLatch](images/CountDownLatch.jpg) 8 | 9 | ## await 10 | 11 | CountDownLatch.await: 12 | 13 | ```java 14 | public void await() throws InterruptedException { 15 | sync.acquireSharedInterruptibly(1); 16 | } 17 | ``` 18 | 19 | AbstractQueuedSynchronizer.acquireSharedInterruptibly: 20 | 21 | ```java 22 | public final void acquireSharedInterruptibly(int arg) throws InterruptedException { 23 | if (Thread.interrupted()) throw new InterruptedException(); 24 | if (tryAcquireShared(arg) < 0) 25 | doAcquireSharedInterruptibly(arg); 26 | } 27 | ``` 28 | 29 | 这个套路已经见过很多次了,关键的逻辑便是Sync.tryAcquireShared: 30 | 31 | ```java 32 | protected int tryAcquireShared(int acquires) { 33 | return (getState() == 0) ? 1 : -1; 34 | } 35 | ``` 36 | 37 | 可以看出,**await成功的条件是当前读锁(共享锁)不再被其它线程持有**。 38 | 39 | ## countDown 40 | 41 | ```java 42 | public void countDown() { 43 | sync.releaseShared(1); 44 | } 45 | ``` 46 | 47 | AbstractQueuedSynchronizer.releaseShared: 48 | 49 | ```java 50 | public final boolean releaseShared(int arg) { 51 | if (tryReleaseShared(arg)) { 52 | doReleaseShared(); 53 | return true; 54 | } 55 | return false; 56 | } 57 | ``` 58 | 59 | 还是一样的套路,Sync.tryReleaseShared: 60 | 61 | ```java 62 | protected boolean tryReleaseShared(int releases) { 63 | // Decrement count; signal when transition to zero 64 | for (;;) { 65 | int c = getState(); 66 | if (c == 0) 67 | return false; 68 | int nextc = c-1; 69 | if (compareAndSetState(c, nextc)) 70 | return nextc == 0; 71 | } 72 | } 73 | ``` 74 | 75 | 可以看出,countDown真正的逻辑其实是将读锁的持有数减一。 76 | 77 | ## 构造器 78 | 79 | ```java 80 | public CountDownLatch(int count) { 81 | this.sync = new Sync(count); 82 | } 83 | ``` 84 | 85 | Sync构造器: 86 | 87 | ```java 88 | Sync(int count) { 89 | setState(count); 90 | } 91 | ``` 92 | 93 | 关键便在这里了: **AbstractQueuedSynchronizer的初始状态(即锁的持有数)不为零**。 94 | 95 | ## 总结 96 | 97 | CountDownLatch的原理是: 98 | 99 | 构造一个初始状态(锁持有数)不为零的AbstractQueuedSynchronizer,每一次countDown方法的执行会导致锁的持有次数减一,而await方法便是一直等待直到锁的持有数为0为止。 100 | -------------------------------------------------------------------------------- /note/CountDownLatch/images/CountDownLatch.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/CountDownLatch/images/CountDownLatch.jpg -------------------------------------------------------------------------------- /note/CyclicBarrier/CyclicBarrier.md: -------------------------------------------------------------------------------- 1 | # CyclicBarrier 2 | 3 | 此类可管理一组线程,当所有线程都到达"检查点"时执行特定的动作(Runnable),示例直接看其源码便好。 4 | 5 | ## 类图 6 | 7 | 和CountDownLatch类似,此类同样没有任何父类(除了Object)或是实现任何接口。 8 | 9 | ![CyclicBarrier](images/CyclicBarrier.jpg) 10 | 11 | ## await 12 | 13 | 整个类的核心便是此方法: 14 | 15 | ```java 16 | public int await() { 17 | return dowait(false, 0L); 18 | } 19 | ``` 20 | 21 | doWait方法简略版源码: 22 | 23 | ```java 24 | private int dowait(boolean timed, long nanos) { 25 | final ReentrantLock lock = this.lock; 26 | lock.lock(); 27 | try { 28 | final Generation g = generation; 29 | // 未到达(检查点)数减一 30 | int index = --count; 31 | // 已达到检查点,执行给定的任务 32 | if (index == 0) { 33 | boolean ranAction = false; 34 | try { 35 | final Runnable command = barrierCommand; 36 | if (command != null) 37 | command.run(); 38 | ranAction = true; 39 | nextGeneration(); 40 | return 0; 41 | } finally { 42 | if (!ranAction) 43 | breakBarrier(); 44 | } 45 | } 46 | 47 | // 尚有线程未到达检查点 48 | for (;;) { 49 | try { 50 | // 等待 51 | if (!timed) 52 | trip.await(); 53 | else if (nanos > 0L) 54 | nanos = trip.awaitNanos(nanos); 55 | } catch (InterruptedException ie) { 56 | if (g == generation && ! g.broken) { 57 | breakBarrier(); 58 | throw ie; 59 | } else { 60 | Thread.currentThread().interrupt(); 61 | } 62 | } 63 | 64 | if (g.broken) 65 | throw new BrokenBarrierException(); 66 | 67 | if (g != generation) 68 | return index; 69 | 70 | if (timed && nanos <= 0L) { 71 | breakBarrier(); 72 | throw new TimeoutException(); 73 | } 74 | } 75 | } finally { 76 | lock.unlock(); 77 | } 78 | } 79 | ``` 80 | 81 | 一目了然,但是有一个疑问,当未到达数减为0时,已经阻塞的线程是在哪里唤醒的呢? 82 | 83 | 答案便是nextGeneration方法: 84 | 85 | ```java 86 | private void nextGeneration() { 87 | // signal completion of last generation 88 | trip.signalAll(); 89 | // set up next generation 90 | count = parties; 91 | generation = new Generation(); 92 | } 93 | ``` 94 | 95 | -------------------------------------------------------------------------------- /note/CyclicBarrier/images/CyclicBarrier.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/CyclicBarrier/images/CyclicBarrier.jpg -------------------------------------------------------------------------------- /note/Enum/enum.md: -------------------------------------------------------------------------------- 1 | # 说明 2 | 3 | 对枚举这一看似平常的东西单开一节的目的是要探究一下为什么说枚举是最好的实现单例的方式。 4 | 5 | # 从例子开始 6 | 7 | 假设有这么一个简单的枚举: 8 | 9 | ```java 10 | public enum Test { 11 | 12 | SOMEONE, 13 | 14 | ANOTHER_ONE 15 | 16 | } 17 | ``` 18 | 19 | 首先对其进行编译,然后使用命令`javap -verbose Test.class`对其反编译,得到如下部分: 20 | 21 | ```java 22 | public final class Test extends java.lang.Enum {} 23 | ``` 24 | 25 | 所以,对于Java来说,枚举是一个语法糖。 26 | 27 | # Why 28 | 29 | Enum的类图: 30 | 31 | ![Enum](images/enum.png) 32 | 33 | ## finalize 34 | 35 | Enum中将finalize方法直接定义为final,这就从根本上上避免了枚举类对此方法的实现: 36 | 37 | ```java 38 | protected final void finalize() { } 39 | ``` 40 | 41 | ### 序列化 42 | 43 | 以如下代码示例对枚举类型的序列化: 44 | 45 | ```java 46 | File file = new File("C:/Users/xsdwe/Desktop/test"); 47 | ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file)); 48 | out.writeObject(Person.EVIL); 49 | out.writeObject(Person.JACK); 50 | out.flush(); 51 | out.close(); 52 | ``` 53 | 54 | 限制其实是在ObjectOutputStream的writeObject0方法中进行控制的,相关源码: 55 | 56 | ```java 57 | private void writeObject0(Object obj, boolean unshared) throws IOException { 58 | //... 59 | if (obj instanceof String) { 60 | writeString((String) obj, unshared); 61 | } else if (cl.isArray()) { 62 | writeArray(obj, desc, unshared); 63 | } else if (obj instanceof Enum) { 64 | writeEnum((Enum) obj, desc, unshared); 65 | } 66 | //... 67 | } 68 | ``` 69 | 70 | 可以看出,对于枚举类型,这里使用了专门的方法writeEnum进行序列化: 71 | 72 | ```java 73 | private void writeEnum(Enum en, ObjectStreamClass desc, boolean unshared) { 74 | bout.writeByte(TC_ENUM); 75 | ObjectStreamClass sdesc = desc.getSuperDesc(); 76 | writeClassDesc((sdesc.forClass() == Enum.class) ? desc : sdesc, false); 77 | handles.assign(unshared ? null : en); 78 | writeString(en.name(), false); 79 | } 80 | ``` 81 | 82 | 这里**只对枚举的名称进行了序列化**。 83 | 84 | ### 反序列化 85 | 86 | 反序列化的控制由ObjectInputStream的readEnum方法完成,简略版源码: 87 | 88 | ```java 89 | private Enum readEnum(boolean unshared) throws IOException { 90 | //... 91 | String name = readString(false); 92 | return Enum.valueOf((Class)cl, name); 93 | } 94 | ``` 95 | 96 | 这样就保证了枚举的设计目标: 在**一个JVM中一个枚举值只能有一个实例**。 97 | 这里还有一个细节,序列化之后对象都以字节的形式存在,反序列化时Java是如何识别出这是一个枚举的呢? 98 | 99 | 其实从上面序列化方法writeEnum中可以看出,方法再将枚举名写入之前,首先写入了一个字节(0X7E, 126)的标志,然后写入了一串这东西: 100 | `mq.Person: static final long serialVersionUID = 0L`,mq.Person是测试枚举Person的完整类名。 101 | 102 | # 总结 103 | 104 | 至于单例模式中的初始化线程安全这一点,是由JVM虚拟机的类加载机制决定的,我们无需担心。 -------------------------------------------------------------------------------- /note/Enum/images/enum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/Enum/images/enum.png -------------------------------------------------------------------------------- /note/FileChannel/images/FileChannel.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/FileChannel/images/FileChannel.jpg -------------------------------------------------------------------------------- /note/FileChannel/images/FileKey.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/FileChannel/images/FileKey.jpg -------------------------------------------------------------------------------- /note/FileChannel/images/FileLock.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/FileChannel/images/FileLock.jpg -------------------------------------------------------------------------------- /note/FileChannel/images/FileLockTable.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/FileChannel/images/FileLockTable.jpg -------------------------------------------------------------------------------- /note/HashMap/hashmap.md: -------------------------------------------------------------------------------- 1 | 类图如下: 2 | 3 | ![HashMap](images/HashMap.jpg) 4 | 5 | # 构造 6 | 7 | 我们以无参构造器为例: 8 | 9 | ```java 10 | public HashMap() { 11 | this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted 12 | } 13 | ``` 14 | 15 | 其它的参数均取默认。 16 | 17 | # put 18 | 19 | ```java 20 | public V put(K key, V value) { 21 | return putVal(hash(key), key, value, false, true); 22 | } 23 | ``` 24 | 25 | putVal源码: 26 | 27 | ```java 28 | final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { 29 | Node[] tab; Node p; int n, i; 30 | //1.初始化 31 | if ((tab = table) == null || (n = tab.length) == 0) 32 | n = (tab = resize()).length; 33 | //2.bin为null,初始化第一个节点 34 | if ((p = tab[i = (n - 1) & hash]) == null) 35 | tab[i] = newNode(hash, key, value, null); 36 | else { 37 | Node e; K k; 38 | if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) 39 | //指定key的值已存在,那么记录下原先的值 40 | e = p; 41 | else if (p instanceof TreeNode) 42 | //红黑树 43 | e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value); 44 | else { 45 | //bin不为空,且与链表头不相同(==或equals) 46 | //3. 47 | for (int binCount = 0; ; ++binCount) { 48 | if ((e = p.next) == null) { 49 | p.next = newNode(hash, key, value, null); 50 | //达到临界值转为红黑树 51 | if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st 52 | treeifyBin(tab, hash); 53 | break; 54 | } 55 | if (e.hash == hash && 56 | ((k = e.key) == key || (key != null && key.equals(k)))) 57 | break; 58 | p = e; 59 | } 60 | } 61 | if (e != null) { // existing mapping for key 62 | V oldValue = e.value; 63 | if (!onlyIfAbsent || oldValue == null) 64 | e.value = value; 65 | //空实现,为LinkedHashMap预留 66 | afterNodeAccess(e); 67 | return oldValue; 68 | } 69 | } 70 | ++modCount; 71 | //4. 72 | if (++size > threshold) 73 | resize(); 74 | //空实现,为LinkedHashMap预留 75 | afterNodeInsertion(evict); 76 | return null; 77 | } 78 | ``` 79 | 80 | onlyIfAbsent参数如果为true,那么对于已经存在的key,将不替换其值 。table即HashMap进行数据存储的核心变量: 81 | 82 | ```java 83 | transient Node[] table; 84 | ``` 85 | 86 | Node代表了table中的一项,类图: 87 | 88 | ![Node](images/Node.jpg) 89 | 90 | ## 哈希算法 91 | 92 | ```java 93 | static final int hash(Object key) { 94 | int h; 95 | return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); 96 | } 97 | ``` 98 | 99 | 这里在原生hashCode的基础上做了一次与高16位相异或的处理,这样做的目的是将哈希值的高位纳入到取余运算中来,防止由于低位相同造成的频繁冲突的情况。 100 | 101 | ## 表头还是表尾 102 | 103 | 当bin中已含有节点链表,且要插入新的元素时从表头还是表尾插入? 104 | 105 | 从源码(3)中很明显可以看出是从表尾插入,因为HashMap需要判断链表中元素的个数以决定是否将其转为红黑树。 106 | 107 | ## size 108 | 109 | HashMap中维护有一个字段size记录当前元素的个数: 110 | 111 | ```java 112 | transient int size; 113 | ``` 114 | 115 | 从上面putVal方法源码(4)中可以看到其改变方式。 116 | 117 | # get 118 | 119 | ```java 120 | public V get(Object key) { 121 | Node e; 122 | return (e = getNode(hash(key), key)) == null ? null : e.value; 123 | } 124 | ``` 125 | 126 | 剩下的脑补即可。 127 | 128 | # resize 129 | 130 | ```java 131 | final Node[] resize() { 132 | Node[] oldTab = table; 133 | int oldCap = (oldTab == null) ? 0 : oldTab.length; 134 | int oldThr = threshold; 135 | int newCap, newThr = 0; 136 | //原table不为null, 137 | if (oldCap > 0) { 138 | //MAXIMUM_CAPACITY取1 << 30,即table数组的大小,如果已到达此值,那么无需扩容 139 | if (oldCap >= MAXIMUM_CAPACITY) { 140 | //threshold,CAPACITY乘以负载因子即扩容的临界值 141 | threshold = Integer.MAX_VALUE; 142 | return oldTab; 143 | } 144 | //没有达到最大值,两倍扩容 145 | else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && 146 | oldCap >= DEFAULT_INITIAL_CAPACITY) 147 | newThr = oldThr << 1; // double threshold 148 | } 149 | else if (oldThr > 0) // initial capacity was placed in threshold 150 | newCap = oldThr; 151 | else { 152 | //初始化,默认大小为16 153 | newCap = DEFAULT_INITIAL_CAPACITY; 154 | newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); 155 | } 156 | if (newThr == 0) { 157 | float ft = (float)newCap * loadFactor; 158 | newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? 159 | (int)ft : Integer.MAX_VALUE); 160 | } 161 | threshold = newThr; 162 | Node[] newTab = (Node[])new Node[newCap]; 163 | table = newTab; 164 | if (oldTab != null) { 165 | for (int j = 0; j < oldCap; ++j) { 166 | Node e; 167 | if ((e = oldTab[j]) != null) { 168 | //数组的此位置含有元素 169 | oldTab[j] = null; 170 | if (e.next == null) 171 | //如果桶位中只有一个元素=>直接设置 172 | newTab[e.hash & (newCap - 1)] = e; 173 | else if (e instanceof TreeNode) 174 | ((TreeNode)e).split(this, newTab, j, oldCap); 175 | else { // preserve order 176 | Node loHead = null, loTail = null; 177 | Node hiHead = null, hiTail = null; 178 | Node next; 179 | do { 180 | next = e.next; 181 | if ((e.hash & oldCap) == 0) { 182 | if (loTail == null) 183 | loHead = e; 184 | else 185 | loTail.next = e; 186 | loTail = e; 187 | } 188 | else { 189 | if (hiTail == null) 190 | hiHead = e; 191 | else 192 | hiTail.next = e; 193 | hiTail = e; 194 | } 195 | } while ((e = next) != null); 196 | if (loTail != null) { 197 | loTail.next = null; 198 | newTab[j] = loHead; 199 | } 200 | if (hiTail != null) { 201 | hiTail.next = null; 202 | newTab[j + oldCap] = hiHead; 203 | } 204 | } 205 | } 206 | } 207 | } 208 | return newTab; 209 | } 210 | ``` 211 | 212 | ## 移动 213 | 214 | 我们在guava-cache中已经见识过了,假设map的最初容量为8,现要扩容到16,实际上对于每一个桶位(bin),只有两种情况: 215 | 216 | - 无需移动(bin下标不变),比如hashCode为7的情况。 217 | - 移动到原先下标位置 + 最初容量的位置,比如对于hashCode 12,原本为4,现在要移动至12,移动了8. 218 | 219 | 那么如何判断是否需要移动呢? 220 | 221 | 因为我们的容量都是2的整次幂,对8取余我们只要& (8 - 1)即可,所以8和16的mask分别为: 222 | 223 | 0111 224 | 225 | 1111 226 | 227 | 我们只需hashCode & 8即可,这便是源码中preserve order部分所做的。 228 | 229 | 那么为什么要对一个bin中的每一个元素都要进行判断呢?因为比如对于bin 4,在容量为8的情况下,hashCode为4和12都会进入到这个位置,而扩容后就不一定了。 230 | 231 | # 红黑树 232 | 233 | 其时间复杂度为O(logn),不再详细探究其细节,可参考: 234 | 235 | [教你初步了解红黑树](http://blog.csdn.net/v_july_v/article/details/6105630) 236 | 237 | # containsValue 238 | 239 | 查询是否包含特定的key较为简单,等同于一次get操作,而查询value则不是: 240 | 241 | ```java 242 | public boolean containsValue(Object value) { 243 | Node[] tab; V v; 244 | if ((tab = table) != null && size > 0) { 245 | for (int i = 0; i < tab.length; ++i) { 246 | for (Node e = tab[i]; e != null; e = e.next) { 247 | if ((v = e.value) == value || 248 | (value != null && value.equals(v))) 249 | return true; 250 | } 251 | } 252 | } 253 | return false; 254 | } 255 | ``` 256 | 257 | 这是一个遍历所有bin + 链表/红黑树的过程,所以有过有根据value查找key的需求我们可以使用双向Map,比如Guava的BiMap。 258 | 259 | -------------------------------------------------------------------------------- /note/HashMap/images/HashMap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/HashMap/images/HashMap.jpg -------------------------------------------------------------------------------- /note/HashMap/images/Node.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/HashMap/images/Node.jpg -------------------------------------------------------------------------------- /note/IO/images/File.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/IO/images/File.jpg -------------------------------------------------------------------------------- /note/IO/images/FileOutputStream.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/IO/images/FileOutputStream.jpg -------------------------------------------------------------------------------- /note/IO/images/FileSystem.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/IO/images/FileSystem.jpg -------------------------------------------------------------------------------- /note/IO/images/FilterInputStream.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/IO/images/FilterInputStream.jpg -------------------------------------------------------------------------------- /note/IO/images/RandomAccessFile.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/IO/images/RandomAccessFile.jpg -------------------------------------------------------------------------------- /note/IO/images/Reader.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/IO/images/Reader.jpg -------------------------------------------------------------------------------- /note/IO/io.md: -------------------------------------------------------------------------------- 1 | # File 2 | 3 | 类图: 4 | 5 | ![File类图](images/File.jpg) 6 | 7 | 其实观察File里面多数方法的实现,其实是通过委托给属性fs完成的。 8 | 9 | ## FileSystem 10 | 11 | 在 File中的初始化代码: 12 | 13 | ```java 14 | private static final FileSystem fs = DefaultFileSystem.getFileSystem(); 15 | ``` 16 | 17 | 在Windows下的类图: 18 | 19 | ![FileSystem类图](images/FileSystem.jpg) 20 | 21 | 那DefaultFileSystem又是个什么东西呢?这东西是从JDK8加入的 ,全部的源码如下: 22 | 23 | ```java 24 | class DefaultFileSystem { 25 | public static FileSystem getFileSystem() { 26 | return new WinNTFileSystem(); 27 | } 28 | } 29 | ``` 30 | 31 | 可以想象,在Linux上一定是不同的实现。 32 | 33 | ## 文件创建 34 | 35 | 我们来看看创建文件时到底发生了什么,以代码: 36 | 37 | ```java 38 | File file = new File("test"); 39 | System.out.println(file.getAbsolutePath()); 40 | ``` 41 | 42 | File构造器: 43 | 44 | ```java 45 | public File(String pathname) { 46 | this.path = fs.normalize(pathname); 47 | this.prefixLength = fs.prefixLength(this.path); 48 | } 49 | ``` 50 | 51 | ### normalize 52 | 53 | 这一步是将文件分隔符、路径分隔符转换成系统特定的,比如对于windows平台,就将/转为\。转换的过程不再赘述,来看一下系统特定的分隔符是从哪来的,WinNTFileSystem构造器: 54 | 55 | ```java 56 | public WinNTFileSystem() { 57 | slash = AccessController.doPrivileged(new GetPropertyAction("file.separator")).charAt(0); 58 | semicolon = AccessController.doPrivileged(new GetPropertyAction("path.separator")).charAt(0); 59 | altSlash = (this.slash == '\\') ? '/' : '\\'; 60 | } 61 | ``` 62 | 63 | 其实就是System.getProperty。 64 | 65 | ## 绝对路径获取 66 | 67 | File.getAbsolutePath: 68 | 69 | ```java 70 | public String getAbsolutePath() { 71 | return fs.resolve(this); 72 | } 73 | ``` 74 | 75 | resolve遵从下面的逻辑: 76 | 77 | - 如果给定的路径已经是绝对路径,那么直接返回。 78 | - 获取系统变量user.dir,将文件指向此目录,而user.dir默认便是java程序执行的路径。 79 | 80 | 到这里并未涉及到任何系统层面的API操作。 81 | 82 | ## 文件/目录判断 83 | 84 | 其实是一样的套路: 85 | 86 | ```java 87 | public boolean isDirectory() { 88 | return ((fs.getBooleanAttributes(this) & FileSystem.BA_DIRECTORY != 0); 89 | } 90 | 91 | public boolean isFile() { 92 | return ((fs.getBooleanAttributes(this) & FileSystem.BA_REGULAR) != 0); 93 | } 94 | ``` 95 | 96 | getBooleanAttributes为native实现。位于WinNTFileSystem_md.c,核心源码: 97 | 98 | ```java 99 | JNIEXPORT jint JNICALL 100 | Java_java_io_WinNTFileSystem_getBooleanAttributes(JNIEnv *env, jobject this, jobject file) { 101 | jint rv = 0; 102 | jint pathlen; 103 | WCHAR *pathbuf = fileToNTPath(env, file, ids.path); 104 | DWORD a = getFinalAttributes(pathbuf); 105 | rv = (java_io_FileSystem_BA_EXISTS 106 | | ((a & FILE_ATTRIBUTE_DIRECTORY) 107 | ? java_io_FileSystem_BA_DIRECTORY : java_io_FileSystem_BA_REGULAR) 108 | | ((a & FILE_ATTRIBUTE_HIDDEN) ? java_io_FileSystem_BA_HIDDEN : 0)); 109 | return rv; 110 | } 111 | ``` 112 | 113 | getFinalAttributes即Windows API,其实此函数可以返回更多的属性信息,而源码中的一系列与或操作将很多属性屏蔽了,只保留以下: 114 | 115 | - 是否存在 116 | - 文件还是目录 117 | - 是否是隐藏文件/目录 118 | 119 | FileSystem.BA_REGULAR等属性定义在抽象类FileSystem中: 120 | 121 | ```java 122 | @Native public static final int ACCESS_READ = 0x04; 123 | @Native public static final int ACCESS_WRITE = 0x02; 124 | @Native public static final int ACCESS_EXECUTE = 0x01; 125 | ``` 126 | 127 | 即:每一个属性用int中的一位来存储。 128 | 129 | ## 可读/可写/可执行 130 | 131 | File.canRead: 132 | 133 | ```java 134 | public boolean canRead() { 135 | return fs.checkAccess(this, FileSystem.ACCESS_READ); 136 | } 137 | ``` 138 | 139 | 写也是一样的。checkAccess为native实现: 140 | 141 | ```c++ 142 | JNIEXPORT jboolean 143 | JNICALL Java_java_io_WinNTFileSystem_checkAccess(JNIEnv *env, jobject this, jobject file, jint access) { 144 | DWORD attr; 145 | WCHAR *pathbuf = fileToNTPath(env, file, ids.path); 146 | // Windows函数 147 | attr = GetFileAttributesW(pathbuf); 148 | attr = getFinalAttributesIfReparsePoint(pathbuf, attr); 149 | switch (access) { 150 | case java_io_FileSystem_ACCESS_READ: 151 | case java_io_FileSystem_ACCESS_EXECUTE: 152 | return JNI_TRUE; 153 | case java_io_FileSystem_ACCESS_WRITE: 154 | /* Read-only attribute ignored on directories */ 155 | if ((attr & FILE_ATTRIBUTE_DIRECTORY) || 156 | (attr & FILE_ATTRIBUTE_READONLY) == 0) 157 | return JNI_TRUE; 158 | else 159 | return JNI_FALSE; 160 | default: 161 | assert(0); 162 | return JNI_FALSE; 163 | } 164 | } 165 | ``` 166 | 167 | 从这里可以看出,对于读和执行权限,只要路径存在且合法,直接返回true,而可写的条件是: 路径是一个目录或非只读文件。 168 | 169 | ## list 170 | 171 | 此方法用以列出一个目录下的所有子文件(夹)。使用WinNTFileSystem的同名native方法实现,源码较长,在此不再贴出,实现的原理便是利用Windows的FindFirstFileW和FindNextFileW,两个函数的W结尾表示Unicode编码,参考: 172 | 173 | [FindNextFile function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa364428(v=vs.85).aspx) 174 | 175 | [使用FindFirstFile,FindNextFile遍历一个文件夹](http://www.cnblogs.com/chenkunyun/archive/2012/03/24/2415727.html) 176 | 177 | ## mkdir(s) 178 | 179 | mkdir实现: 180 | 181 | ```java 182 | public boolean mkdir() { 183 | return fs.createDirectory(this); 184 | } 185 | ``` 186 | 187 | 而mkdirs使用mkdir实现: 188 | 189 | ```java 190 | public boolean mkdirs() { 191 | File parent = getCanonicalFile().getParentFile(); 192 | return (parent != null && (parent.mkdirs() || parent.exists()) && 193 | canonFile.mkdir()); 194 | } 195 | ``` 196 | 197 | createDirectory为native实现,对应Windows CreateDirectoryW函数。 198 | 199 | ## 文件删除 200 | 201 | native由removeFileOrDirectory函数完成,源码: 202 | 203 | ```c 204 | static int removeFileOrDirectory(const jchar *path) { 205 | /* Returns 0 on success */ 206 | DWORD a; 207 | SetFileAttributesW(path, FILE_ATTRIBUTE_NORMAL); 208 | a = GetFileAttributesW(path); 209 | if (a == INVALID_FILE_ATTRIBUTES) { 210 | return 1; 211 | } else if (a & FILE_ATTRIBUTE_DIRECTORY) { 212 | //删除目录 213 | return !RemoveDirectoryW(path); 214 | } else { 215 | return !DeleteFileW(path); 216 | } 217 | } 218 | ``` 219 | 220 | ## 移动/重命名 221 | 222 | 由Windows函数_wrename实现。 223 | 224 | ## length 225 | 226 | 用以获取文件的大小,Linux实现由stat函数完成。 227 | 228 | # FileOutputStream 229 | 230 | 类图: 231 | 232 | ![FileOutputStream类图](images/FileOutputStream.jpg) 233 | 234 | ## 构造器 235 | 236 | 以File参数构造器为例: 237 | 238 | ```java 239 | public FileOutputStream(File file, boolean append) { 240 | String name = (file != null ? file.getPath() : null); 241 | this.fd = new FileDescriptor(); 242 | fd.attach(this); 243 | this.append = append; 244 | this.path = name; 245 | open(name, append); 246 | } 247 | ``` 248 | 249 | 第二个参数为是否追加写,默认false。 250 | 251 | open调用了native实现的open0: 252 | 253 | ```c 254 | JNIEXPORT void JNICALL Java_java_io_FileOutputStream_open0(JNIEnv *env, jobject this, jstring path, jboolean append) { 255 | fileOpen(env, this, path, fos_fd, 256 | O_WRONLY | O_CREAT | (append ? O_APPEND : O_TRUNC)); 257 | } 258 | ``` 259 | 260 | fileOpen最终调用Windows API CreateFileW函数,我们在这里只关注一下参数: 261 | 262 | ```c 263 | O_WRONLY | O_CREAT | (append ? O_APPEND : O_TRUNC) 264 | ``` 265 | 266 | O_WRONLY表示写文件,O_CREAT为创建文件,O_TRUNC 表示若文件存在,则长度被截为0,属性不变,参考: 267 | 268 | [open()参数宏的意义 O_TRUNC](http://www.cnblogs.com/leaven/archive/2010/05/26/1744274.html) 269 | 270 | ## 写文件 271 | 272 | 实际上,所有的写操作都是通过此方法实现的: 273 | 274 | ```java 275 | private native void writeBytes(byte b[], int off, int len, boolean append); 276 | ``` 277 | 278 | 最终的写入由Windows函数WriteFile完成,io_util_md.c,writeInternal函数部分源码: 279 | 280 | ```c 281 | static jint writeInternal(FD fd, const void *buf, jint len, jboolean append) { 282 | result = WriteFile(h, /* File handle to write */ 283 | buf, /* pointers to the buffers */ 284 | len, /* number of bytes to write */ 285 | &written, /* receives number of bytes written */ 286 | lpOv); /* overlapped struct */ 287 | return (jint)written; 288 | } 289 | ``` 290 | 291 | ## 通道获取 292 | 293 | 隔壁 FileChannel。 294 | 295 | ## 关闭 296 | 297 | FileOutputStream.close: 298 | 299 | ```java 300 | public void close() throws IOException { 301 | //设置状态 302 | synchronized (closeLock) { 303 | if (closed) { 304 | return; 305 | } 306 | closed = true; 307 | } 308 | //关闭通道 309 | if (channel != null) { 310 | channel.close(); 311 | } 312 | fd.closeAll(new Closeable() { 313 | public void close() throws IOException { 314 | close0(); 315 | } 316 | }); 317 | } 318 | ``` 319 | 320 | close0为native实现,关闭其对应的文件句柄。 321 | 322 | # RandomAccessFile 323 | 324 | 老规矩,类图: 325 | 326 | ![RandomAccessFile类图](images/RandomAccessFile.jpg) 327 | 328 | 可见,这货与File, 输入输出流半毛钱的关系都没有。 329 | 330 | ## 模式 331 | 332 | 支持的读写模式整理如下表: 333 | 334 | | Mode | 意义 | 335 | | ---- | ------------------------ | 336 | | r | 只读 | 337 | | rw | 读写 | 338 | | rws | 任何对文件内容或元信息的写会被立即同步刷新至磁盘 | 339 | | rwd | 对文件内容的写会被立即同步刷新至磁盘 | 340 | 341 | 可以看出,rws和rwd两个模式的作用和FileChannel的force方法是一样的。 342 | 343 | ## 构造器 344 | 345 | 简略版源码: 346 | 347 | ```java 348 | public RandomAccessFile(File file, String mode) { 349 | String name = (file != null ? file.getPath() : null); 350 | int imode = -1; 351 | fd = new FileDescriptor(); 352 | fd.attach(this); 353 | path = name; 354 | open(name, imode); 355 | } 356 | ``` 357 | 358 | open方法调用了native方法open0,此方法的实现位于src\share\native\java\io\RandomAccessFile.c: 359 | 360 | ```c 361 | JNIEXPORT void JNICALL 362 | Java_java_io_RandomAccessFile_open0(JNIEnv *env, 363 | jobject this, jstring path, jint mode) { 364 | int flags = 0; 365 | if (mode & java_io_RandomAccessFile_O_RDONLY) 366 | flags = O_RDONLY; 367 | else if (mode & java_io_RandomAccessFile_O_RDWR) { 368 | flags = O_RDWR | O_CREAT; 369 | if (mode & java_io_RandomAccessFile_O_SYNC) 370 | flags |= O_SYNC; 371 | else if (mode & java_io_RandomAccessFile_O_DSYNC) 372 | flags |= O_DSYNC; 373 | } 374 | fileOpen(env, this, path, raf_fd, flags); 375 | } 376 | ``` 377 | 378 | 结合FileOutputStream的open方法便可以发现,**两者的open操作其实使用了相同的系统级实现,只不过RandomAccessFile支持更多的参数**。 379 | 380 | ## seek 381 | 382 | seek允许我们自己设定当前文件的指针(偏移)。注意,系统允许设置的偏移大于文件的真实长度,但这并不改变文件的大小,只有当在新的偏移写入数据之后才会改变。 383 | 384 | 由native方法seek0调用Windows函数SetFilePointerEx实现,与之比对,FileChannel的position方法采用的是SetFilePointer函数,两者的区别是**SetFilePointer将新的文件指针存放在两个long中,而SetFilePointerEX只需要一个long**,至于两者为什么要采用不同的实现,不得而知。 385 | 386 | ## skipBytes 387 | 388 | 用seek方法实现。 389 | 390 | ## length 391 | 392 | 获取文件的大小,不同于File的length方法,此处使用seek来实现,RandomAccessFile.c相关源码(简略): 393 | 394 | ```c 395 | JNIEXPORT jlong JNICALL 396 | Java_java_io_RandomAccessFile_length(JNIEnv *env, jobject this) { 397 | FD fd; 398 | jlong cur = jlong_zero; 399 | jlong end = jlong_zero; 400 | fd = GET_FD(this, raf_fd); 401 | end = IO_Lseek(fd, 0L, SEEK_END); 402 | return end; 403 | } 404 | ``` 405 | 406 | IO_Lseek对应linux上的lseek,windows上的SetFilePointer,关于为什么可以利用seek取得文件大小,参考: 407 | 408 | [Linux 通过lseek()来实现文件大小的设置](http://blog.csdn.net/xiaobai1593/article/details/7419784) 409 | 410 | ## setLength 411 | 412 | 此方法可以实现文件裁剪的效果,和FileChannel的truncate方法效果相同。native实现: 413 | 414 | ```c 415 | JNIEXPORT void JNICALL 416 | Java_java_io_RandomAccessFile_setLength(JNIEnv *env, jobject this, jlong newLength) { 417 | FD fd; 418 | jlong cur; 419 | fd = GET_FD(this, raf_fd); 420 | if ((cur = IO_Lseek(fd, 0L, SEEK_CUR)) == -1) goto fail; 421 | //here 422 | if (IO_SetLength(fd, newLength) == -1) goto fail; 423 | //重设指针 424 | if (cur > newLength) { 425 | if (IO_Lseek(fd, 0L, SEEK_END) == -1) goto fail; 426 | } else { 427 | if (IO_Lseek(fd, cur, SEEK_SET) == -1) goto fail; 428 | } 429 | return; 430 | } 431 | ``` 432 | 433 | IO_SetLength在Windows上的真正函数是SetFilePointer,这和FileChannel的truncate是一样的,在Linux上是ftruncate函数。 434 | 435 | ## 读方法 436 | 437 | 虽然RandomAccessFile和InputStream在继承上没有关系,但很多API是一样的。 438 | 439 | ### 读取一个字节 440 | 441 | 方法声明: 442 | 443 | ```java 444 | private native int read0(); 445 | ``` 446 | 447 | 注意返回值是int,即范围为0-255,。如果我们将byte值-1写入到文件,再读出来就成了255,如要转为-1强转为byte就行了。 448 | 449 | 底层对应Windows的ReadFile,Linux的read函数。 450 | 451 | ### readFully 452 | 453 | ```java 454 | public final void readFully(byte b[]) throws IOException { 455 | readFully(b, 0, b.length); 456 | } 457 | ``` 458 | 459 | 顾名思义,**此方法会在所有要求的字节读完之前阻塞**,其实就是替我们做了循环判断的过程: 460 | 461 | ```java 462 | public final void readFully(byte b[], int off, int len) { 463 | int n = 0; 464 | do { 465 | int count = this.read(b, off + n, len - n); 466 | if (count < 0) 467 | throw new EOFException(); 468 | n += count; 469 | } while (n < len); 470 | } 471 | ``` 472 | 473 | ### "语法糖"读 474 | 475 | 指各种readInt,readChar等方法,其实就是读指定的字节然后给你拼起来。 476 | 477 | #### 浮点数读取/写入 478 | 479 | 我们以float为例: 480 | 481 | ```java 482 | public final void writeFloat(float v) { 483 | writeInt(Float.floatToIntBits(v)); 484 | } 485 | public final float readFloat() throws IOException { 486 | return Float.intBitsToFloat(readInt()); 487 | } 488 | ``` 489 | 490 | 从根本上来说,写入/读取到的只不过是一组二进制数字,关键在于我们如何解读它,所以写入/读取浮点数的关键在于如何进行浮点数和int值得转换(两者的二进制形式是一样的),上面的两个native Float方法巧妙地利用了C语言的共用体实现这一转换: 491 | 492 | ```c 493 | JNIEXPORT jint JNICALL 494 | Java_java_lang_Float_floatToRawIntBits(JNIEnv *env, jclass unused, jfloat v) { 495 | union { 496 | int i; 497 | float f; 498 | } u; 499 | u.f = (float)v; 500 | return (jint)u.i; 501 | } 502 | ``` 503 | 504 | ### readLine 505 | 506 | 源码: 507 | 508 | ```java 509 | public final String readLine() throws IOException { 510 | StringBuffer input = new StringBuffer(); 511 | int c = -1; 512 | boolean eol = false; 513 | while (!eol) { 514 | switch (c = read()) { 515 | case -1: 516 | case '\n': 517 | eol = true; 518 | break; 519 | case '\r': 520 | eol = true; 521 | long cur = getFilePointer(); 522 | if ((read()) != '\n') { 523 | seek(cur); 524 | } 525 | break; 526 | default: 527 | input.append((char)c); 528 | break; 529 | } 530 | } 531 | if ((c == -1) && (input.length() == 0)) { 532 | return null; 533 | } 534 | return input.toString(); 535 | } 536 | ``` 537 | 538 | 可以看出,方法将byte转为了char,但是这样有一个问题,char是由byte转换而来,所以这里只支持ASCII字符,如果真的想要读一行不应使用此方法,而应该使用字符流。 539 | 540 | ### readUnsignedByte 541 | 542 | 不是很理解这个方法的用意,源码: 543 | 544 | ```java 545 | public final int readUnsignedByte() throws IOException { 546 | int ch = this.read(); 547 | if (ch < 0) 548 | throw new EOFException(); 549 | return ch; 550 | } 551 | ``` 552 | 553 | 奇怪的地方在于这里还是只读了一个字节,之后转为int,ch怎么可能是负值?这样的话和read方法又有什么区别? 554 | 555 | ### readUTF 556 | 557 | 应和writeUTF配合食用。writeUTF用于写入一个UTF-8编码的字符串,注意**前两个字节代表后面有多少个字节(字符串)**。所以一个10字节的字符串需要12字节存储。 558 | 559 | 感觉此方法应该是用在字符串、int等多种类型混合存储的场景下。 560 | 561 | # 过滤流 562 | 563 | 或者说是包装流,类图(只列出常用的): 564 | 565 | ![FilterInputStream类图](images/FilterInputStream.jpg) 566 | 567 | DataInputStream与RandomAccessFile的API基本一致(实现了同一个接口),InflaterInputStream用于zip压缩包的读取。 568 | 569 | # 字符流 570 | 571 | "字符"是一个文化上的概念,字符流基于字节流,只不过是按照特定的编码规则进行解码罢了。我们以Reader为例,看一下其类图即可: 572 | 573 | ![Reader类图](images/Reader.jpg) 574 | 575 | -------------------------------------------------------------------------------- /note/LinkedHashMap/images/Entry.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/LinkedHashMap/images/Entry.jpg -------------------------------------------------------------------------------- /note/LinkedHashMap/linkedhashmap.md: -------------------------------------------------------------------------------- 1 | LinkedHashMap是HashMap的子类,内部使用双链表进行顺序的维护,内部类Entry为HashMap的Node的子类,类图: 2 | 3 | ![Entry](images/Entry.jpg) 4 | 5 | # 构造器 6 | 7 | 三参数构造器: 8 | 9 | ```java 10 | public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) { 11 | super(initialCapacity, loadFactor); 12 | this.accessOrder = accessOrder; 13 | } 14 | ``` 15 | 16 | 如果accessOrder为true,那么表示将顺序记录为访问顺序,否则为插入顺序,默认为false。而正是这一参数并对removeEldestEntry方法进行覆盖便可以快速实现一个简单的LRU缓存。 17 | 18 | # put 19 | 20 | put操作其实和父类HashMap采用相同的实现,在HashMap部分也提到了afterNodeAccess和afterNodeInsertion方法其实是空实现,而在LinkedHashMap中对其进行了实现,Linked的特性也是在这里进行了体现。 21 | 22 | ## newNode 23 | 24 | LinkedhHashMap对此方法进行了覆盖: 25 | 26 | ```java 27 | Node newNode(int hash, K key, V value, Node e) { 28 | LinkedHashMap.Entry p = new LinkedHashMap.Entry(hash, key, value, e); 29 | linkNodeLast(p); 30 | return p; 31 | } 32 | ``` 33 | 34 | linkNodeLast方法负责双链表的连接: 35 | 36 | ```java 37 | private void linkNodeLast(LinkedHashMap.Entry p) { 38 | LinkedHashMap.Entry last = tail; 39 | tail = p; 40 | if (last == null) 41 | head = p; 42 | else { 43 | p.before = last; 44 | last.after = p; 45 | } 46 | } 47 | ``` 48 | 49 | ## afterNodeAccess 50 | 51 | 顾名思义,此方法应该在一次访问之后被调用,那么什么算是一次访问呢?LInkedHashMap对此做出了定义: 52 | 53 | - put/putAll/putIfAbsent 54 | - get/getOrDefault 55 | - compute/computeIfAbsent/computeIfPresent 56 | - merge 57 | 58 | ```java 59 | void afterNodeAccess(Node e) { // move node to last 60 | LinkedHashMap.Entry last; 61 | if (accessOrder && (last = tail) != e) { 62 | LinkedHashMap.Entry p = 63 | (LinkedHashMap.Entry)e, b = p.before, a = p.after; 64 | p.after = null; 65 | if (b == null) 66 | head = a; 67 | else 68 | b.after = a; 69 | if (a != null) 70 | a.before = b; 71 | else 72 | last = b; 73 | if (last == null) 74 | head = p; 75 | else { 76 | p.before = last; 77 | last.after = p; 78 | } 79 | tail = p; 80 | ++modCount; 81 | } 82 | } 83 | ``` 84 | 85 | 双链表的节点位置交换过程。 86 | 87 | ## afterNodeInsertion 88 | 89 | ```java 90 | void afterNodeInsertion(boolean evict) { // possibly remove eldest 91 | LinkedHashMap.Entry first; 92 | if (evict && (first = head) != null && removeEldestEntry(first)) { 93 | K key = first.key; 94 | removeNode(hash(key), key, null, false, true); 95 | } 96 | } 97 | ``` 98 | 99 | evict参数如果为false,表示处于创建模式,在反序列化时才会处于创建模式,**而反序列化时当然不能进行节点淘汰**,这便是此参数的意义。 100 | 101 | 正如前面提到过的,removeEldestEntry是实现LRU的关键: 102 | 103 | ```java 104 | protected boolean removeEldestEntry(Map.Entry eldest) { 105 | return false; 106 | } 107 | ``` 108 | 109 | 即默认不进行移除,可以看出,传给removeEldestEntry方法的是每个bin的第一个元素,因为无论参数accessOrder为true与否,**第一个必定是最老(最先被插入或最久未被使用)的**。 110 | 111 | # get 112 | 113 | ```java 114 | public V get(Object key) { 115 | Node e; 116 | if ((e = getNode(hash(key), key)) == null) 117 | return null; 118 | if (accessOrder) 119 | afterNodeAccess(e); 120 | return e.value; 121 | } 122 | ``` 123 | 124 | HashMap的get加afterNodeAccess触发的过程。 125 | 126 | # 性能 127 | 128 | 与HashMap相比,LinkedHashMap由于在插入是需要进行额外的双链表链接工作,所以在插入性能上必定不如HashMap,但在遍历时,LinkedHashMap的性能反而更高,因为只需遍历链表即可,而HashMap需要遍历bin和链表(或红黑树)。 -------------------------------------------------------------------------------- /note/LinkedHashMap/linkedhashmap.uml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | linkedhashmap 12 | 1 13 | 14 | classes 15 | HFO2b8/zJEK5l8uPGP7ABAAA 16 | 1 17 | 18 | Entry 19 | AcTxq7C800G13oD3Dr5ZMwAA 20 | 21 | 7U34Yqhu+EKuyPwDB3GGSQAA 22 | 5 23 | 24 | clMaroon 25 | $00B9FFFF 26 | 372 27 | 340 28 | 111 29 | 69 30 | cjWdhNGwMkWX3TZ5192GDwAA 31 | 32 | 33 | 1 34 | Entry 35 | 36 | 37 | False 38 | 39 | 40 | False 41 | 42 | 43 | 44 | cjWdhNGwMkWX3TZ5192GDwAA 45 | 46 | 47 | cjWdhNGwMkWX3TZ5192GDwAA 48 | 49 | 50 | False 51 | cjWdhNGwMkWX3TZ5192GDwAA 52 | 53 | 54 | 55 | clMaroon 56 | $00B9FFFF 57 | 377 58 | 191 59 | 100 60 | 95 61 | llx8/5BTDUqfOLWc+m0n8gAA 62 | 63 | 64 | 1 65 | Node 66 | 67 | 68 | False 69 | 70 | 71 | False 72 | 73 | 74 | 75 | llx8/5BTDUqfOLWc+m0n8gAA 76 | 77 | 78 | llx8/5BTDUqfOLWc+m0n8gAA 79 | 80 | 81 | False 82 | llx8/5BTDUqfOLWc+m0n8gAA 83 | 84 | 85 | 86 | clMaroon 87 | $00B9FFFF 88 | 385 89 | 92 90 | 85 91 | 45 92 | sdkNone 93 | wPhc9IM1hEut1Vsv6UKpIwAA 94 | 95 | 96 | 1 97 | Map.Entry 98 | 99 | 100 | <<interface>> 101 | 102 | 103 | False 104 | 105 | 106 | 107 | False 108 | wPhc9IM1hEut1Vsv6UKpIwAA 109 | 110 | 111 | False 112 | wPhc9IM1hEut1Vsv6UKpIwAA 113 | 114 | 115 | 116 | clMaroon 117 | $00B9FFFF 118 | 426,191;427,136 119 | GStYrnfgGkSa9PWVwu8zJgAA 120 | 5Cb7DUptLkOYJgtctNxDHAAA 121 | GFYT0fMbqEuTEIRpqHpFywAA 122 | 123 | False 124 | 1.5707963267949 125 | 15 126 | GStYrnfgGkSa9PWVwu8zJgAA 127 | 128 | 129 | False 130 | 1.5707963267949 131 | 30 132 | GStYrnfgGkSa9PWVwu8zJgAA 133 | 134 | 135 | False 136 | -1.5707963267949 137 | 15 138 | GStYrnfgGkSa9PWVwu8zJgAA 139 | 140 | 141 | 142 | clMaroon 143 | $00B9FFFF 144 | 427,340;426,285 145 | WF9iC9eYF0qK5nKRtRmSMAAA 146 | GFYT0fMbqEuTEIRpqHpFywAA 147 | g5fRLx8TrECgCsWUYKPXVQAA 148 | 149 | False 150 | 1.5707963267949 151 | 15 152 | WF9iC9eYF0qK5nKRtRmSMAAA 153 | 154 | 155 | False 156 | 1.5707963267949 157 | 30 158 | WF9iC9eYF0qK5nKRtRmSMAAA 159 | 160 | 161 | False 162 | -1.5707963267949 163 | 15 164 | WF9iC9eYF0qK5nKRtRmSMAAA 165 | 166 | 167 | 168 | 169 | 5 170 | 171 | Entry 172 | AcTxq7C800G13oD3Dr5ZMwAA 173 | 4 174 | g5fRLx8TrECgCsWUYKPXVQAA 175 | hVDPmXs8YUqC2hrUrkvpWQAA 176 | 2o89/szJlkmD8cylGOwWxwAA 177 | RxDG1XeCX0eFDnMgyd8U9gAA 178 | 1 179 | WF9iC9eYF0qK5nKRtRmSMAAA 180 | 2 181 | 182 | Entry<K,V> before 183 | vkPackage 184 | cjWdhNGwMkWX3TZ5192GDwAA 185 | 186 | 187 | Entry<K,V> after 188 | vkPackage 189 | cjWdhNGwMkWX3TZ5192GDwAA 190 | 191 | 192 | 193 | Node 194 | AcTxq7C800G13oD3Dr5ZMwAA 195 | 4 196 | GFYT0fMbqEuTEIRpqHpFywAA 197 | Xsm3mfEaWUONgOJKWlUbDwAA 198 | tlxnWUgKNk6bYOMG/jugtQAA 199 | 9bnF1Xd7CkukQOp+aVv2WQAA 200 | 1 201 | GStYrnfgGkSa9PWVwu8zJgAA 202 | 1 203 | WF9iC9eYF0qK5nKRtRmSMAAA 204 | 4 205 | 206 | int hash 207 | vkPackage 208 | llx8/5BTDUqfOLWc+m0n8gAA 209 | 210 | 211 | K key 212 | vkPackage 213 | llx8/5BTDUqfOLWc+m0n8gAA 214 | 215 | 216 | V value 217 | vkPackage 218 | llx8/5BTDUqfOLWc+m0n8gAA 219 | 220 | 221 | Node<K,V> next 222 | vkPackage 223 | llx8/5BTDUqfOLWc+m0n8gAA 224 | 225 | 226 | 227 | Map.Entry 228 | AcTxq7C800G13oD3Dr5ZMwAA 229 | 3 230 | 5Cb7DUptLkOYJgtctNxDHAAA 231 | kslEBz1PUkaERIP1pIfoBgAA 232 | mD48kJLEWki+Uaw1jHiuCQAA 233 | 1 234 | GStYrnfgGkSa9PWVwu8zJgAA 235 | 236 | 237 | AcTxq7C800G13oD3Dr5ZMwAA 238 | llx8/5BTDUqfOLWc+m0n8gAA 239 | wPhc9IM1hEut1Vsv6UKpIwAA 240 | 4 241 | c/j92D52M0qyGvzpjIGiRgAA 242 | wt91msY1PkS+TmSYeEdzrgAA 243 | 1KpFflU3Hk+uOmiZdbP+JQAA 244 | 8NEIKfZj6ECPap8hKXVtFAAA 245 | 246 | 247 | AcTxq7C800G13oD3Dr5ZMwAA 248 | cjWdhNGwMkWX3TZ5192GDwAA 249 | llx8/5BTDUqfOLWc+m0n8gAA 250 | 4 251 | fAvmQN5UtUy2U4Jzv+7+KAAA 252 | 8LgQIkalmUy3MMKuqQRifAAA 253 | c9YMVTzARE+TViWPJOevKgAA 254 | sitYnXNMNUKijc/4t240RQAA 255 | 256 | 257 | 258 | 259 | 260 | -------------------------------------------------------------------------------- /note/NIO/images/AbstractInterruptibleChannel.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/NIO/images/AbstractInterruptibleChannel.jpg -------------------------------------------------------------------------------- /note/NIO/images/Channels.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/NIO/images/Channels.jpg -------------------------------------------------------------------------------- /note/NIO/images/SelectionKey.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/NIO/images/SelectionKey.jpg -------------------------------------------------------------------------------- /note/NIO/images/Selector.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/NIO/images/Selector.jpg -------------------------------------------------------------------------------- /note/NIO/images/SelectorProvider.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/NIO/images/SelectorProvider.jpg -------------------------------------------------------------------------------- /note/NIO/images/Selector_full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/NIO/images/Selector_full.png -------------------------------------------------------------------------------- /note/NIO/images/channel_close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/NIO/images/channel_close.png -------------------------------------------------------------------------------- /note/NIO/images/fd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/NIO/images/fd.png -------------------------------------------------------------------------------- /note/NIO/images/fd_inode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/NIO/images/fd_inode.png -------------------------------------------------------------------------------- /note/NIO/images/provider_full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/NIO/images/provider_full.png -------------------------------------------------------------------------------- /note/NIO/images/tcp_meaning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/NIO/images/tcp_meaning.png -------------------------------------------------------------------------------- /note/NIO/images/wmem_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/NIO/images/wmem_default.png -------------------------------------------------------------------------------- /note/NIO/images/wmem_max.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/NIO/images/wmem_max.png -------------------------------------------------------------------------------- /note/Process/images/Process.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/Process/images/Process.jpg -------------------------------------------------------------------------------- /note/Process/images/ProcessBuilder.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/Process/images/ProcessBuilder.jpg -------------------------------------------------------------------------------- /note/Process/images/Redirect.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/Process/images/Redirect.jpg -------------------------------------------------------------------------------- /note/Process/images/Thread.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/Process/images/Thread.jpg -------------------------------------------------------------------------------- /note/Process/images/pipe_size.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/Process/images/pipe_size.png -------------------------------------------------------------------------------- /note/Process/images/redirectErrorStream.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/Process/images/redirectErrorStream.png -------------------------------------------------------------------------------- /note/Process/images/redirect_group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/Process/images/redirect_group.png -------------------------------------------------------------------------------- /note/Process/images/stream.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/Process/images/stream.PNG -------------------------------------------------------------------------------- /note/Process/process.md: -------------------------------------------------------------------------------- 1 | # 命令执行 2 | 3 | 我们以以下代码为例: 4 | 5 | ```java 6 | Runtime runtime = Runtime.getRuntime(); 7 | String cmd = "ls -l"; 8 | Process process = runtime.exec(cmd); 9 | ``` 10 | 11 | 实际调用了下面的exec方法: 12 | 13 | ```java 14 | public Process exec(String command, String[] envp, File dir) { 15 | StringTokenizer st = new StringTokenizer(command); 16 | String[] cmdarray = new String[st.countTokens()]; 17 | for (int i = 0; st.hasMoreTokens(); i++) 18 | cmdarray[i] = st.nextToken(); 19 | return exec(cmdarray, envp, dir); 20 | } 21 | ``` 22 | 23 | 第二个参数为环境变量数组,其格式为key=value,第三个参数为命令的执行路径,如果为null,那么便为当前Java进程的工作路径,即user.dir。 24 | 25 | StringTokenizer默认以空格、换行符、制表符,换页符(\f)为分隔单位,所以这里将命令`ls -l`分割为了ls和-l两部分。 26 | 27 | ```java 28 | public Process exec(String[] cmdarray, String[] envp, File dir) { 29 | return new ProcessBuilder(cmdarray) 30 | .environment(envp) 31 | .directory(dir) 32 | .start(); 33 | } 34 | ``` 35 | 36 | 其实是用ProcessBuilder实现,此类在JDK1.5时加入。 37 | 38 | # ProcessBuilder 39 | 40 | ![ProcessBuilder](images/ProcessBuilder.jpg) 41 | 42 | ## start 43 | 44 | start方法简略版源码: 45 | 46 | ```java 47 | public Process start() throws IOException { 48 | String[] cmdarray = command.toArray(new String[command.size()]); 49 | cmdarray = cmdarray.clone(); 50 | String dir = directory == null ? null : directory.toString(); 51 | return ProcessImpl.start(cmdarray, 52 | environment, 53 | dir, 54 | redirects, 55 | redirectErrorStream); 56 | } 57 | ``` 58 | 59 | ## Redirect 60 | 61 | redirects是一个ProcessBuilder内部的Redirect数组,Redirect为ProcessBuilder的嵌套类,定义了系统的进程的输入或输出: 62 | 63 | ![Redirect](images/Redirect.jpg) 64 | 65 | 且redirects数组的大小必定为3,分别代表输入、输出和错误,此数组是lazy-init的,只有当调用其redirects()方法获取整个数组或要设置重定向时才会进行初始化,redirects()源码: 66 | 67 | ```java 68 | private Redirect[] redirects() { 69 | if (redirects == null) 70 | redirects = new Redirect[] {Redirect.PIPE, Redirect.PIPE, Redirect.PIPE}; 71 | return redirects; 72 | } 73 | ``` 74 | 75 | 可以看到,默认都是管道类型,这其实很容易理解,每个命令的执行都会导致一个系统进程的创建,我们从进程获得输出,输入必定要通过管道的方式。 76 | 77 | ## 重定向 78 | 79 | 其实共有三组方法: 80 | 81 | ![重定向方法](images/redirect_group.png) 82 | 83 | 没有参数的表示获取。 84 | 85 | ### 获取 86 | 87 | 我们以redirectError为例: 88 | 89 | ```java 90 | public Redirect redirectError() { 91 | return (redirects == null) ? Redirect.PIPE : redirects[2]; 92 | } 93 | ``` 94 | 95 | ### 设置 96 | 97 | 以redirectInput为例: 98 | 99 | ```java 100 | public ProcessBuilder redirectInput(File file) { 101 | //对redirects数组赋值 102 | return redirectInput(Redirect.from(file)); 103 | } 104 | ``` 105 | 106 | from方法其实是对File的包装: 107 | 108 | ```java 109 | public static Redirect from(final File file) { 110 | return new Redirect() { 111 | public Type type() { return Type.READ; } 112 | public File file() { return file; } 113 | public String toString() { 114 | return "redirect to read from file \"" + file + "\""; 115 | } 116 | }; 117 | } 118 | ``` 119 | 120 | ## 输出整合 121 | 122 | 下面两个方法分别对redirectErrorStream属性进行读取和设置: 123 | 124 | ![redirectErrorStream](images/redirectErrorStream.png) 125 | 126 | 如果被设置为true,那么错误输出将会被merge到标准输出。 127 | 128 | ## 继承 129 | 130 | 即Redirect.Type.INHERIT类型。那什么是继承呢?其实就是将启动的进程的输入或输出或错误输出设置为当前Java虚拟机的输入、输出与错误输出。 131 | 132 | inheritIO方法源码: 133 | 134 | ```java 135 | public ProcessBuilder inheritIO() { 136 | Arrays.fill(redirects(), Redirect.INHERIT); 137 | return this; 138 | } 139 | ``` 140 | 141 | 其实相当于这样: 142 | 143 | ```java 144 | pb.redirectInput(Redirect.INHERIT).redirectOutput(Redirect.INHERIT).redirectError(Redirect.INHERIT); 145 | ``` 146 | 147 | # Process 148 | 149 | 代表系统的一个进程。 150 | 151 | ![Process](images/Process.jpg) 152 | 153 | ## start 154 | 155 | ProcessImpl在不同的系统上有不同的实现,我们以Linux为准,所做的是将命令、参数以及环境转为byte数组,并初始化文件描述符数组,初始化的逻辑如下: 156 | 157 | ```java 158 | int[] std_fds; 159 | if (redirects == null) { 160 | std_fds = new int[] { -1, -1, -1 }; 161 | } else { 162 | std_fds = new int[3]; 163 | if (redirects[0] == Redirect.PIPE) 164 | std_fds[0] = -1; 165 | else if (redirects[0] == Redirect.INHERIT) 166 | //在nio部分提到过,在Unix下0为标准输入,1为标准输出,2为错误输出 167 | std_fds[0] = 0; 168 | else { 169 | f0 = new FileInputStream(redirects[0].file()); 170 | std_fds[0] = fdAccess.get(f0.getFD()); 171 | } 172 | } 173 | ``` 174 | 175 | 最后构造了一个UNIXProcess对象,UNIXProcess构造器源码: 176 | 177 | ```java 178 | UNIXProcess(final byte[] prog, 179 | final byte[] argBlock, final int argc, 180 | final byte[] envBlock, final int envc, 181 | final byte[] dir, 182 | final int[] fds, 183 | final boolean redirectErrorStream) { 184 | pid = forkAndExec(launchMechanism.value, 185 | helperpath, 186 | prog, 187 | argBlock, argc, 188 | envBlock, envc, 189 | dir, 190 | fds, 191 | redirectErrorStream); 192 | doPrivileged(new PrivilegedExceptionAction() { 193 | public Void run() throws IOException { 194 | initStreams(fds); 195 | return null; 196 | }}); 197 | } 198 | ``` 199 | 200 | ### 运行模式 201 | 202 | 枚举LaunchMechanism的定义为: 203 | 204 | ```java 205 | private static enum LaunchMechanism { 206 | FORK(1), 207 | VFORK(3); 208 | private int value; 209 | LaunchMechanism(int x) {value = x;} 210 | }; 211 | ``` 212 | 213 | fork和vfork其实是Linux中两种子进程的启动方式,关于它们的区别可参考: 214 | 215 | [fork vfork函数区别](http://blog.csdn.net/buaalei/article/details/5348382) 216 | 217 | 字段launchMechanism在静态代码块中初始化: 218 | 219 | ```java 220 | static { 221 | launchMechanism = AccessController.doPrivileged( 222 | new PrivilegedAction() { 223 | public LaunchMechanism run() { 224 | String s = System.getProperty( 225 | "jdk.lang.Process.launchMechanism", "vfork"); 226 | return LaunchMechanism.valueOf(s.toUpperCase()); 227 | } 228 | }); 229 | } 230 | ``` 231 | 232 | 在Linux上默认使用vfork,即无需拷贝拷贝父进程的数据,子进程执行时父进程必须等待。 233 | 234 | forkAndExec方法为native实现,由vfork系统调用实现。 235 | 236 | ### 流初始化 237 | 238 | 共可以获得三种流,方法名如下图: 239 | 240 | ![stream.PNG](images/stream.PNG) 241 | 242 | 其中errorStream对应子进程的错误输出流,inputStream对应子进程的标准输出流(未重定向的前提下),outputStream对应子进程的标准输入流(未重定向的前提下)。 243 | 244 | UNIXProcess.initStreams: 245 | 246 | ```java 247 | void initStreams(int[] fds) throws IOException { 248 | stdin = (fds[0] == -1) ? 249 | ProcessBuilder.NullOutputStream.INSTANCE : 250 | new ProcessPipeOutputStream(fds[0]); 251 | stdout = (fds[1] == -1) ? 252 | ProcessBuilder.NullInputStream.INSTANCE : 253 | new ProcessPipeInputStream(fds[1]); 254 | stderr = (fds[2] == -1) ? 255 | ProcessBuilder.NullInputStream.INSTANCE : 256 | new ProcessPipeInputStream(fds[2]); 257 | processReaperExecutor.execute(new Runnable() { 258 | public void run() { 259 | int exitcode = waitForProcessExit(pid); 260 | UNIXProcess.this.processExited(exitcode); 261 | }}); 262 | } 263 | ``` 264 | 265 | 上面提到的三个方法实际上就是返回这里的三个字段中的一个。注意forkAndExec方法会改变fds的值,在这里-1表示在ProcessBuilder中我们指定了重定向,比如我们将子进程的输出指定到一个文件,那么这里的stdin就是NullOutputStream。ProcessBuilder.NullOutputStream: 266 | 267 | ```java 268 | static class NullOutputStream extends OutputStream { 269 | static final NullOutputStream INSTANCE = new NullOutputStream(); 270 | private NullOutputStream() {} 271 | public void write(int b) throws IOException { 272 | throw new IOException("Stream closed"); 273 | } 274 | } 275 | ``` 276 | 277 | ### 等待结束 278 | 279 | 即initStreams方法中的: 280 | 281 | ```java 282 | processReaperExecutor.execute(new Runnable() { 283 | public void run() { 284 | int exitcode = waitForProcessExit(pid); 285 | UNIXProcess.this.processExited(exitcode); 286 | } 287 | }); 288 | ``` 289 | 290 | processReaperExecutor为Java线程池,所以这里使用了单独的线程来进行等待。waitForProcessExit为native实现,通过Linux waitpid及其相关系统调用实现,可参考Linux man或: 291 | 292 | [Linux中waitpid()函数的用法](http://blog.csdn.net/roland_sun/article/details/32084825) 293 | 294 | UNIXProcess.processExited: 295 | 296 | ```java 297 | void processExited(int exitcode) { 298 | synchronized (this) { 299 | this.exitcode = exitcode; 300 | hasExited = true; 301 | //唤醒所有正在等待的线程 302 | notifyAll(); 303 | } 304 | if (stdout instanceof ProcessPipeInputStream) 305 | ((ProcessPipeInputStream) stdout).processExited(); 306 | if (stderr instanceof ProcessPipeInputStream) 307 | ((ProcessPipeInputStream) stderr).processExited(); 308 | if (stdin instanceof ProcessPipeOutputStream) 309 | ((ProcessPipeOutputStream) stdin).processExited(); 310 | } 311 | ``` 312 | 313 | 我们来看一下processExited方法做了什么,以ProcessPipeInputStream为例: 314 | 315 | ```java 316 | synchronized void processExited() { 317 | try { 318 | InputStream in = this.in; 319 | if (in != null) { 320 | InputStream stragglers = drainInputStream(in); 321 | in.close(); 322 | this.in = stragglers; 323 | } 324 | } catch (IOException ignored) { } 325 | } 326 | ``` 327 | 328 | drainInputStream方法的作用检测输入流(即子进程的输出)中是否还有数据未被读取,如果有将其读取出来并包装为ByteArrayInputStream返回,否则 返回NullInputStream。 329 | 330 | ### 为什么会阻塞 331 | 332 | 回想之前在实际项目中遇到的问题: **如果我们不读取输入流(子进程的输出)的数据,那么waitFor方法将一直不能返回**。 333 | 334 | 原因就在于在没有重定向的情况下进程和子进程之间使用管道进行通信,而管道的大小也是有一定的限制的,写满之后write调用便会阻塞。 335 | 336 | 管道的大小可通过命令`ulimit -a`查看,如下图: 337 | 338 | ![PipeSize](images/pipe_size.png) 339 | 340 | 512字节。 341 | 342 | ## waitFor 343 | 344 | UNIXProcess.waitFor: 345 | 346 | ```java 347 | public synchronized int waitFor() { 348 | while (!hasExited) { 349 | wait(); 350 | } 351 | return exitcode; 352 | } 353 | ``` 354 | 355 | wait就是Object的wait方法,结合Process-start-等待结束即可 。 356 | 357 | ## destroy 358 | 359 | destroy()和destroyForcibly()方法其实都是对私有方法destroy(boolean force)的调用,只不过前者参数为false,后者为 true: 360 | 361 | ```java 362 | private void destroy(boolean force) { 363 | synchronized (this) { 364 | if (!hasExited) 365 | destroyProcess(pid, force); 366 | } 367 | try { stdin.close(); } catch (IOException ignored) {} 368 | try { stdout.close(); } catch (IOException ignored) {} 369 | try { stderr.close(); } catch (IOException ignored) {} 370 | } 371 | ``` 372 | 373 | 正如jdk的注释中所说,这里其实有潜在的竞争条件,即在!hasExited检查通过之后子进程执行完毕,同时有可能Linux会对进程号进行回收,从而导致进程误杀的情况,只不过概率相当小。 374 | 375 | destroyProcess为native方法: 376 | 377 | ```c 378 | JNIEXPORT void JNICALL 379 | Java_java_lang_UNIXProcess_destroyProcess(JNIEnv *env, 380 | jobject junk, 381 | jint pid, 382 | jboolean force) { 383 | int sig = (force == JNI_TRUE) ? SIGKILL : SIGTERM; 384 | kill(pid, sig); 385 | } 386 | ``` 387 | 388 | kill即Linux系统调用。 389 | 390 | ## exitValue 391 | 392 | 获取返回值: 393 | 394 | ```java 395 | public synchronized int exitValue() { 396 | if (!hasExited) { 397 | throw new IllegalThreadStateException("process hasn't exited"); 398 | } 399 | return exitcode; 400 | } 401 | ``` 402 | 403 | ## isAlive 404 | 405 | jdk1.8加入的方法: 406 | 407 | ```java 408 | @Override 409 | public synchronized boolean isAlive() { 410 | return !hasExited; 411 | } 412 | ``` 413 | 414 | -------------------------------------------------------------------------------- /note/ReadWriteLock/ReadWriteLock.md: -------------------------------------------------------------------------------- 1 | # 类图 2 | 3 | ## ReadWriteLock 4 | 5 | ![ReadWriteLock类图](images/ReadWriteLock.jpg) 6 | 7 | ## Sync 8 | 9 | ReentrantReadWriteLock内部的Sync的类图如下: 10 | 11 | ![Sync类图](images/Sync.jpg) 12 | 13 | # 构造 14 | 15 | 默认构造器: 16 | 17 | ```java 18 | public ReentrantReadWriteLock() { 19 | this(false); 20 | } 21 | ``` 22 | 23 | 含参构造器: 24 | 25 | ```java 26 | public ReentrantReadWriteLock(boolean fair) { 27 | sync = fair ? new FairSync() : new NonfairSync(); 28 | readerLock = new ReadLock(this); 29 | writerLock = new WriteLock(this); 30 | } 31 | ``` 32 | 33 | 写锁和读锁的构造器是一个套路,以读锁为例: 34 | 35 | ```java 36 | protected ReadLock(ReentrantReadWriteLock lock) { 37 | sync = lock.sync; 38 | } 39 | ``` 40 | 41 | 可以看出,**读写锁内部的sync其实就是ReentrantReadWriteLock的sync**。 42 | 43 | # 读锁 44 | 45 | ## lock 46 | 47 | ReadLock.lock: 48 | 49 | ```java 50 | public void lock() { 51 | sync.acquireShared(1); 52 | } 53 | ``` 54 | 55 | AbstractQueuedSynchronizer.acquireShared: 56 | 57 | ```java 58 | public final void acquireShared(int arg) { 59 | if (tryAcquireShared(arg) < 0) 60 | doAcquireShared(arg); 61 | } 62 | ``` 63 | 64 | tryAcquireShared方法的实现位于Sync: 65 | 66 | ```java 67 | protected final int tryAcquireShared(int unused) { 68 | Thread current = Thread.currentThread(); 69 | int c = getState(); 70 | if (exclusiveCount(c) != 0 && 71 | getExclusiveOwnerThread() != current) 72 | return -1; 73 | int r = sharedCount(c); 74 | if (!readerShouldBlock() && 75 | r < MAX_COUNT && 76 | compareAndSetState(c, c + SHARED_UNIT)) { 77 | if (r == 0) { 78 | firstReader = current; 79 | firstReaderHoldCount = 1; 80 | } else if (firstReader == current) { 81 | firstReaderHoldCount++; 82 | } else { 83 | HoldCounter rh = cachedHoldCounter; 84 | if (rh == null || rh.tid != getThreadId(current)) 85 | cachedHoldCounter = rh = readHolds.get(); 86 | else if (rh.count == 0) 87 | readHolds.set(rh); 88 | rh.count++; 89 | } 90 | return 1; 91 | } 92 | return fullTryAcquireShared(current); 93 | } 94 | ``` 95 | 96 | 以下进行分部分说明。 97 | 98 | ### 排它锁/写锁检测 99 | 100 | 如果另一个线程已经持有写锁/排它锁,那么读锁的获得将会马上失败。此部分源码: 101 | 102 | ```java 103 | Thread current = Thread.currentThread(); 104 | int c = getState(); 105 | if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) 106 | return -1; 107 | ``` 108 | 109 | getState值在此处被当做两个short来使用,高16位值代表读锁的持有次数,低16位代表写锁的的持有次数。 110 | 111 | ```java 112 | static final int SHARED_SHIFT = 16; 113 | static final int SHARED_UNIT = (1 << SHARED_SHIFT); 114 | static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1; 115 | static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; 116 | 117 | static int sharedCount(int c) { return c >>> SHARED_SHIFT; } 118 | 119 | static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; } 120 | ``` 121 | 122 | 这就说明,**读锁会被写锁阻塞**。 123 | 124 | ### 应该阻塞? 125 | 126 | 对应于readerShouldBlock方法,对于公平锁和非公平锁有两种不同的语义。 127 | 128 | #### 非公平锁 129 | 130 | NonfairSync.readerShouldBlock: 131 | 132 | ```java 133 | final boolean readerShouldBlock() { 134 | return apparentlyFirstQueuedIsExclusive(); 135 | } 136 | ``` 137 | 138 | AbstractQueuedSynchronizer.apparentlyFirstQueuedIsExclusive: 139 | 140 | ```java 141 | final boolean apparentlyFirstQueuedIsExclusive() { 142 | Node h, s; 143 | return (h = head) != null && (s = h.next) != null && 144 | !s.isShared() && s.thread != null; 145 | } 146 | ``` 147 | 148 | 可以看出,此方法在非公平锁的情况下主要是**检测当前锁队列中第一个元素是不是写锁(排它锁),如果是,那么当前线程主动放弃竞争锁的机会,这样做是为了防止出现写锁饥饿的现象**。 149 | 150 | #### 公平锁 151 | 152 | FairSync.readerShouldBlock: 153 | 154 | ```java 155 | final boolean readerShouldBlock() { 156 | return hasQueuedPredecessors(); 157 | } 158 | ``` 159 | 160 | AbstractQueuedSynchronizer.hasQueuedPredecessors: 161 | 162 | ```java 163 | public final boolean hasQueuedPredecessors() { 164 | Node t = tail; // Read fields in reverse initialization order 165 | Node h = head; 166 | Node s; 167 | return h != t && 168 | ((s = h.next) == null || s.thread != Thread.currentThread()); 169 | } 170 | ``` 171 | 172 | 这个就很好理解了,锁队列中前面如果还有其它等待锁的线程,那么就应该阻塞。 173 | 174 | ### 快速尝试 175 | 176 | 如果满足所有的条件(没有写锁、不应该阻塞,没有达到读锁可重入次数的上限),那么便会进行一次快速尝试,如果失败,再进行入队(锁队列)的复杂操作。 177 | 178 | 快速尝试其实就是一个CAS操作,源码见上面,再次不再 赘述。 179 | 180 | ### 完全尝试 181 | 182 | 所谓的完全尝试便是在死循环里执行快速尝试,直到成功为止。 183 | 184 | ### 入队等待 185 | 186 | 上面提到的快速尝试和完全尝试都是在当前没有其它线程持有写锁的情况下,如果写锁被其它线程持有,那么只能将当前线程加入到锁队列排队。 187 | 188 | ## unlock 189 | 190 | ReadLock.unlock: 191 | 192 | ```java 193 | public void unlock() { 194 | sync.releaseShared(1); 195 | } 196 | ``` 197 | 198 | AbstractQueuedSynchronizer.releaseShared: 199 | 200 | ```java 201 | public final boolean releaseShared(int arg) { 202 | if (tryReleaseShared(arg)) { 203 | doReleaseShared(); 204 | return true; 205 | } 206 | return false; 207 | } 208 | ``` 209 | 210 | Sync.tryReleaseShared: 211 | 212 | ```java 213 | protected final boolean tryReleaseShared(int unused) { 214 | Thread current = Thread.currentThread(); 215 | if (firstReader == current) { 216 | // assert firstReaderHoldCount > 0; 217 | if (firstReaderHoldCount == 1) 218 | firstReader = null; 219 | else 220 | firstReaderHoldCount--; 221 | } else { 222 | HoldCounter rh = cachedHoldCounter; 223 | if (rh == null || rh.tid != getThreadId(current)) 224 | rh = readHolds.get(); 225 | int count = rh.count; 226 | if (count <= 1) { 227 | readHolds.remove(); 228 | if (count <= 0) 229 | throw unmatchedUnlockException(); 230 | } 231 | --rh.count; 232 | } 233 | for (;;) { 234 | int c = getState(); 235 | int nextc = c - SHARED_UNIT; 236 | if (compareAndSetState(c, nextc)) 237 | return nextc == 0; 238 | } 239 | } 240 | ``` 241 | 242 | ReadWriteLock采用了ThreadLocal来记录线程重入读锁的次数,这么做的原因是允许多个线程同时拥有读锁。 243 | 244 | # 写锁 245 | 246 | ## lock 247 | 248 | 源码: 249 | 250 | ```java 251 | public void lock() { 252 | sync.acquire(1); 253 | } 254 | ``` 255 | 256 | AbstractQueuedSynchronizer.acquire: 257 | 258 | ```java 259 | public final void acquire(int arg) { 260 | if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 261 | selfInterrupt(); 262 | } 263 | ``` 264 | 265 | 此方法在说明ReentrantLock的时候已经见过了。 266 | 267 | Sync.tryAcquire: 268 | 269 | ```java 270 | protected final boolean tryAcquire(int acquires) { 271 | Thread current = Thread.currentThread(); 272 | int c = getState(); 273 | int w = exclusiveCount(c); 274 | if (c != 0) { 275 | // (Note: if c != 0 and w == 0 then shared count != 0) 276 | if (w == 0 || current != getExclusiveOwnerThread()) 277 | return false; 278 | if (w + exclusiveCount(acquires) > MAX_COUNT) 279 | throw new Error("Maximum lock count exceeded"); 280 | // Reentrant acquire 281 | setState(c + acquires); 282 | return true; 283 | } 284 | if (writerShouldBlock() || 285 | !compareAndSetState(c, c + acquires)) 286 | return false; 287 | setExclusiveOwnerThread(current); 288 | return true; 289 | } 290 | ``` 291 | 292 | 很明显,申请写锁的套路是这样的: **如果有其它线程持有读锁或写锁,那么失败,否则尝试进行写锁获取**。 293 | 294 | writerShouldBlock方法对应读锁里的readerShouldBlock方法,后者可以参见前面读锁-应该阻塞?一节。而对于writerShouldBlock来说同样分为两种情况,即公平锁与非公平锁。公平锁的实现目的与readerShouldBlock相同,即判断等待队列中是否有先到的等待者。 295 | 296 | 而readerShouldBlock的非公平锁实现的目的在于防止写锁出现饥饿的情况,对于writerShouldBlock来说就不需要作此考量,所以NonfairSync.writerShouldBlock直接返回false。 297 | 298 | ## unlock 299 | 300 | 写锁的释放无非是一个减少重入次数、更改锁拥有线程以及通知后继的过程,不再赘述。 -------------------------------------------------------------------------------- /note/ReadWriteLock/images/ReadWriteLock.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/ReadWriteLock/images/ReadWriteLock.jpg -------------------------------------------------------------------------------- /note/ReadWriteLock/images/Sync.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/ReadWriteLock/images/Sync.jpg -------------------------------------------------------------------------------- /note/ReentrantLock/ReentrantLock.md: -------------------------------------------------------------------------------- 1 | # ReentrantLock 2 | 3 | ## 类图 4 | 5 | 如下: 6 | 7 | ![ReentrantLock类图](images/ReentrantLock.jpg) 8 | 9 | ## Sync 10 | 11 | 观察ReentrantLock关键方法的源码可以发现,其实现是委托给内部类Sync实现的,所以逻辑的关键便在于此类。例子: 12 | 13 | ```java 14 | public void lock() { 15 | sync.lock(); 16 | } 17 | ``` 18 | 19 | ### 类图 20 | 21 | ![Sync类图](images/Sync.jpg) 22 | 23 | 很明显,锁的公平性便是由NonFairSync和FairSync实现的,从ReentrantLock的构造器可以印证这一点: 24 | 25 | ```java 26 | public ReentrantLock(boolean fair) { 27 | sync = fair ? new FairSync() : new NonfairSync(); 28 | } 29 | ``` 30 | 31 | 锁的公平性指的是**等待时间最长的线程最有机会获得锁,但是这样会导致性能的下降,因为此时对于线程的调度和操作系统的调度是矛盾的**。参考: 32 | 33 | [ReentrantLock(重入锁)以及公平性](http://ifeve.com/reentrantlock-and-fairness/) 34 | 35 | ## 非公平锁 36 | 37 | ### lock 38 | 39 | NonfairSync.lock: 40 | 41 | ```java 42 | final void lock() { 43 | if (compareAndSetState(0, 1)) 44 | setExclusiveOwnerThread(Thread.currentThread()); 45 | else 46 | acquire(1); 47 | } 48 | ``` 49 | 50 | #### 快速尝试 51 | 52 | AbstractQueuedSynchronizer.compareAndSetState: 53 | 54 | ```java 55 | protected final boolean compareAndSetState(int expect, int update) { 56 | return unsafe.compareAndSwapInt(this, stateOffset, expect, update); 57 | } 58 | ``` 59 | 60 | AbstractQueuedSynchronizer内部有一个state变量: 61 | 62 | ```java 63 | /** 64 | * The synchronization state. 65 | */ 66 | private volatile int state; 67 | ``` 68 | 69 | 其记录了**线程重入此锁的次数**,如果为0,那么表示现在没有线程持有此锁,此时使用一个CAS操作即可快速完成锁的申请,这便是*快速尝试*。 70 | 71 | setExclusiveOwnerThread方法用以记录是哪个线程当前持有锁。 72 | 73 | 如果快速尝试失败(即锁已被某个线程持有),那么将调用AbstractQueuedSynchronizer.acquire()方法: 74 | 75 | ```java 76 | public final void acquire(int arg) { 77 | if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 78 | selfInterrupt(); 79 | } 80 | ``` 81 | 82 | #### 是否重入 83 | 84 | NonfairSync.tryAcquire: 85 | 86 | ```java 87 | protected final boolean tryAcquire(int acquires) { 88 | return nonfairTryAcquire(acquires); 89 | } 90 | ``` 91 | 92 | Sync.nonfairTryAcquire: 93 | 94 | ```java 95 | final boolean nonfairTryAcquire(int acquires) { 96 | final Thread current = Thread.currentThread(); 97 | //获取重入次数 98 | int c = getState(); 99 | //再次检查是否没有线程持有当前锁 100 | if (c == 0) { 101 | if (compareAndSetState(0, acquires)) { 102 | setExclusiveOwnerThread(current); 103 | return true; 104 | } 105 | } 106 | //是否当前线程正在重入 107 | else if (current == getExclusiveOwnerThread()) { 108 | int nextc = c + acquires; 109 | //支持的最大重入次数: Ingteger.MAX_VALUE 110 | if (nextc < 0) // overflow 111 | throw new Error("Maximum lock count exceeded"); 112 | setState(nextc); 113 | return true; 114 | } 115 | return false; 116 | } 117 | ``` 118 | 119 | 很明显了,这里体现的便是**重入**。 120 | 121 | #### 锁队列 122 | 123 | 如果锁正在被其它线程持有,那么只能进入队列等待锁。 124 | 125 | ##### 原理 126 | 127 | JDK使用的是CLH队列的一种变种。什么是CLH队列?可以参考: 128 | 129 | [JAVA并发编程学习笔记之CLH队列锁](http://blog.csdn.net/aesop_wubo/article/details/7533186) 130 | 131 | 队列的节点在AbstractQueuedSynchronizer的嵌套类Node中定义,其类图: 132 | 133 | ![Node类图](images/Node.jpg) 134 | 135 | 注: 大写的表示常量。 136 | 137 | Node节点形成这样的结构: 138 | 139 | ![锁队列](images/queue.jpg) 140 | 141 | nextWaiter用于组成Condition等待队列。众所周知,**Condition只能用在已经获得锁的情况下**,所以Condition等待队列不同于锁队列,Condition队列结构如下图: 142 | 143 | ![Condition队列](images/condition_queue.jpg) 144 | 145 | ##### 源码 146 | 147 | ###### 入队 148 | 149 | 入队实际上就是创建一个新的节点并将其设为tail的过程。 150 | 151 | AbstractQueuedSynchronizer.addWaiter(): 152 | 153 | ```java 154 | private Node addWaiter(Node mode) { 155 | Node node = new Node(Thread.currentThread(), mode); 156 | // Try the fast path of enq; backup to full enq on failure 157 | Node pred = tail; 158 | if (pred != null) { 159 | node.prev = pred; 160 | if (compareAndSetTail(pred, node)) { 161 | pred.next = node; 162 | return node; 163 | } 164 | } 165 | enq(node); 166 | return node; 167 | } 168 | ``` 169 | 170 | 可见,此处同样使用了快速尝试的思想,如果CAS尝试失败,那么再调用enq方法,源码: 171 | 172 | ```java 173 | private Node enq(final Node node) { 174 | for (;;) { 175 | Node t = tail; 176 | if (t == null) { // Must initialize 177 | //可见,head其实是一个"空的Node" 178 | if (compareAndSetHead(new Node())) 179 | tail = head; 180 | } else { 181 | node.prev = t; 182 | if (compareAndSetTail(t, node)) { 183 | t.next = node; 184 | return t; 185 | } 186 | } 187 | } 188 | } 189 | ``` 190 | 191 | 其实还是CAS操作,加了一个死循环,直到成功为止。 192 | 193 | ###### 锁获取/等待 194 | 195 | 当前线程被加入到锁队列之后,整下的便是排队等候了。锁队列中当前节点可以获得锁的条件便是**上一个节点(prev)释放了锁**。 196 | 197 | AbstractQueuedSynchronizer.acquireQueued: 198 | 199 | ```java 200 | final boolean acquireQueued(final Node node, int arg) { 201 | boolean failed = true; 202 | try { 203 | boolean interrupted = false; 204 | for (;;) { 205 | final Node p = node.predecessor(); 206 | //前一个是head,那么表示当前节点即是等待时间最长的线程,并立即尝试获得锁 207 | if (p == head && tryAcquire(arg)) { 208 | setHead(node); 209 | p.next = null; // help GC 210 | failed = false; 211 | return interrupted; 212 | } 213 | //执行到这里说明当前节点不是等待时间最长的节点或者锁竞争失败 214 | if (shouldParkAfterFailedAcquire(p, node) && 215 | parkAndCheckInterrupt()) 216 | interrupted = true; 217 | } 218 | } finally { 219 | if (failed) 220 | cancelAcquire(node); 221 | } 222 | } 223 | ``` 224 | 225 | 从这里可以看出,当当前节点就是等待时间最长的节点(队首节点)时,仍然需要调用tryAcquire竞争锁,而与此同时新来的线程有可能同时调用tryAcquire方法与之竞争,这便是非公平性的体现。 226 | 227 | shouldParkAfterFailedAcquire方法用于检测当前线程是否应该休眠,源码: 228 | 229 | ```java 230 | private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { 231 | int ws = pred.waitStatus; 232 | if (ws == Node.SIGNAL) 233 | /* 234 | * This node has already set status asking a release 235 | * to signal it, so it can safely park. 236 | */ 237 | return true; 238 | //上一个节点已经被取消,所以需要"跳过"前面所有已经被取消的节点 239 | if (ws > 0) { 240 | do { 241 | node.prev = pred = pred.prev; 242 | } while (pred.waitStatus > 0); 243 | pred.next = node; 244 | } else { 245 | /* 246 | * waitStatus must be 0 or PROPAGATE. Indicate that we 247 | * need a signal, but don't park yet. Caller will need to 248 | * retry to make sure it cannot acquire before parking. 249 | */ 250 | compareAndSetWaitStatus(pred, ws, Node.SIGNAL); 251 | } 252 | return false; 253 | } 254 | ``` 255 | 256 | 从这里可以看出,**当前一个节点的status为Signal时,当前节点(线程)便进入挂起状态,等待前一个节点释放锁,前一个节点释放锁时,会唤醒当前节点,那时当前节点会再次进行锁竞争**。 257 | 258 | 再来看一下是如何挂起线程的,AbstractQueuedSynchronizer.parkAndCheckInterrupt: 259 | 260 | ```java 261 | private final boolean parkAndCheckInterrupt() { 262 | LockSupport.park(this); 263 | return Thread.interrupted(); 264 | } 265 | ``` 266 | 267 | LockSupport相当于一个工具类,只有静态方法,私有构造器。 268 | 269 | LockSupport.park: 270 | 271 | ```java 272 | public static void park(Object blocker) { 273 | Thread t = Thread.currentThread(); 274 | setBlocker(t, blocker); 275 | //native 276 | UNSAFE.park(false, 0L); 277 | setBlocker(t, null); 278 | } 279 | ``` 280 | 281 | 此时,线程便会阻塞(休眠)在UNSAFE.park(false, 0L);这一句,直到有以下三种情形发生: 282 | 283 | - unpark方法被调用 284 | 285 | - interrupt方法被调用 286 | 287 | - The call spuriously (that is, for no reason) returns. 288 | 289 | 其实说的就是Spurious wakeup: 虚假唤醒。虚假唤醒指的便是阻塞的线程被"莫名其妙"的唤醒,这是多核处理器中不可避免的问题,参见: 290 | 291 | [Spurious wakeup](https://en.wikipedia.org/wiki/Spurious_wakeup) 292 | 293 | 其实经常写的: 294 | 295 | ```java 296 | while (!condition) 297 | await(); 298 | ``` 299 | 300 | 便是为了防止此问题。 301 | 302 | 从acquireQueued方法的源码中可以看出,即使发生了上面所说的三个条件,也只是将变量interrupted设为了true而已,这也就是为什么lock方法会"义无反顾"地在这里等待锁的原因了。 303 | 304 | ###### 自中断 305 | 306 | 当获得锁成功后,会将自己中断: 307 | 308 | AbstractQueuedSynchronizer.selfInterrupt: 309 | 310 | ```java 311 | static void selfInterrupt() { 312 | Thread.currentThread().interrupt(); 313 | } 314 | ``` 315 | 316 | ### lockInterruptibly 317 | 318 | 此方法将在以下两种情况下返回: 319 | 320 | - 获得锁 321 | - 被中断 322 | 323 | AbstractQueuedSynchronizer.acquireInterruptibly: 324 | 325 | ```java 326 | public final void acquireInterruptibly(int arg) throws InterruptedException { 327 | if (Thread.interrupted()) 328 | throw new InterruptedException(); 329 | if (!tryAcquire(arg)) 330 | doAcquireInterruptibly(arg); 331 | } 332 | ``` 333 | 334 | tryAcquire方法前面已经说过了,这里不再赘述。 335 | 336 | AbstractQueuedSynchronizer.doAcquireInterruptibly: 337 | 338 | ```java 339 | private void doAcquireInterruptibly(int arg) throws InterruptedException { 340 | final Node node = addWaiter(Node.EXCLUSIVE); 341 | boolean failed = true; 342 | try { 343 | for (;;) { 344 | final Node p = node.predecessor(); 345 | if (p == head && tryAcquire(arg)) { 346 | setHead(node); 347 | p.next = null; // help GC 348 | failed = false; 349 | return; 350 | } 351 | if (shouldParkAfterFailedAcquire(p, node) && 352 | parkAndCheckInterrupt()) 353 | //看这里! 354 | throw new InterruptedException(); 355 | } 356 | } finally { 357 | if (failed) 358 | cancelAcquire(node); 359 | } 360 | } 361 | ``` 362 | 363 | 和lock方法相比其实只有一行不一样,这便是lockInterruptibly可以相应中断的原因了。 364 | 365 | ### tryLock 366 | 367 | 此方法只是尝试一下现在能不能获得锁,不管结果怎样马上返回。 368 | 369 | 源码就是Sync.nonfairTryAcquire方法,前面已经说过了。 370 | 371 | ### tryLock带时间参数 372 | 373 | AbstractQueuedSynchronizer.tryAcquireNanos: 374 | 375 | ```java 376 | public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException { 377 | if (Thread.interrupted()) 378 | throw new InterruptedException(); 379 | return tryAcquire(arg) || doAcquireNanos(arg, nanosTimeout); 380 | } 381 | ``` 382 | 383 | doAcquireNanos: 384 | 385 | ```java 386 | private boolean doAcquireNanos(int arg, long nanosTimeout) throws InterruptedException { 387 | if (nanosTimeout <= 0L) 388 | return false; 389 | final long deadline = System.nanoTime() + nanosTimeout; 390 | final Node node = addWaiter(Node.EXCLUSIVE); 391 | boolean failed = true; 392 | try { 393 | for (;;) { 394 | final Node p = node.predecessor(); 395 | if (p == head && tryAcquire(arg)) { 396 | setHead(node); 397 | p.next = null; // help GC 398 | failed = false; 399 | return true; 400 | } 401 | nanosTimeout = deadline - System.nanoTime(); 402 | if (nanosTimeout <= 0L) 403 | return false; 404 | if (shouldParkAfterFailedAcquire(p, node) && nanosTimeout > spinForTimeoutThreshold) 405 | //挂起指定的时间 406 | LockSupport.parkNanos(this, nanosTimeout); 407 | if (Thread.interrupted()) 408 | throw new InterruptedException(); 409 | } 410 | } finally { 411 | if (failed) 412 | cancelAcquire(node); 413 | } 414 | } 415 | ``` 416 | 417 | spinForTimeoutThreshold为1000纳秒,可以看出,如果给定的等待时间大于1000纳秒,才会进行线程休眠,否则将会一直轮询。 418 | 419 | ### unlock 420 | 421 | 调用了AbstractQueuedSynchronizer.release: 422 | 423 | ```java 424 | public final boolean release(int arg) { 425 | if (tryRelease(arg)) { 426 | Node h = head; 427 | if (h != null && h.waitStatus != 0) 428 | unparkSuccessor(h); 429 | return true; 430 | } 431 | return false; 432 | } 433 | ``` 434 | 435 | #### 锁释放 436 | 437 | Sync.tryRelease: 438 | 439 | ```java 440 | protected final boolean tryRelease(int releases) { 441 | int c = getState() - releases; 442 | if (Thread.currentThread() != getExclusiveOwnerThread()) 443 | throw new IllegalMonitorStateException(); 444 | boolean free = false; 445 | if (c == 0) { 446 | free = true; 447 | setExclusiveOwnerThread(null); 448 | } 449 | setState(c); 450 | return free; 451 | } 452 | ``` 453 | 454 | 由于锁释放的时候必定拥有锁,所以可以放心大胆的搞。如果当前线程已经不再持有此锁,那么返回true。 455 | 456 | #### 节点唤醒 457 | 458 | 如果当前线程已经不再持有此锁(即tryRelease返回true),那么将会唤醒锁队列中的下一个或多个节点。 459 | 460 | unparkSuccessor: 461 | 462 | ```java 463 | private void unparkSuccessor(Node node) { 464 | int ws = node.waitStatus; 465 | if (ws < 0) 466 | compareAndSetWaitStatus(node, ws, 0); 467 | 468 | Node s = node.next; 469 | if (s == null || s.waitStatus > 0) { 470 | s = null; 471 | for (Node t = tail; t != null && t != node; t = t.prev) 472 | if (t.waitStatus <= 0) 473 | s = t; 474 | } 475 | if (s != null) 476 | LockSupport.unpark(s.thread); 477 | } 478 | ``` 479 | 480 | 可以看出,先是检查下一个节点(next),如果没有被取消,那么唤醒它即可,如果已经被取消,那么将倒着从后面查找。 481 | 482 | ## 公平锁 483 | 484 | 原理和非公平锁大同小异,在这里只说下是如何体现其公平性的。 485 | 486 | FairSync.lock: 487 | 488 | ```java 489 | final void lock() { 490 | acquire(1); 491 | } 492 | ``` 493 | 494 | 最终执行的是FairSync.tryAcquire: 495 | 496 | ```java 497 | protected final boolean tryAcquire(int acquires) { 498 | final Thread current = Thread.currentThread(); 499 | int c = getState(); 500 | if (c == 0) { 501 | //这里 502 | if (!hasQueuedPredecessors() && 503 | compareAndSetState(0, acquires)) { 504 | setExclusiveOwnerThread(current); 505 | return true; 506 | } 507 | } 508 | else if (current == getExclusiveOwnerThread()) { 509 | int nextc = c + acquires; 510 | if (nextc < 0) 511 | throw new Error("Maximum lock count exceeded"); 512 | setState(nextc); 513 | return true; 514 | } 515 | return false; 516 | } 517 | ``` 518 | 519 | 关键便是!hasQueuedPredecessors这个条件,这就保证了**队列前面的节点一定会先获得锁**。 -------------------------------------------------------------------------------- /note/ReentrantLock/images/Node.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/ReentrantLock/images/Node.jpg -------------------------------------------------------------------------------- /note/ReentrantLock/images/ReentrantLock.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/ReentrantLock/images/ReentrantLock.jpg -------------------------------------------------------------------------------- /note/ReentrantLock/images/Sync.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/ReentrantLock/images/Sync.jpg -------------------------------------------------------------------------------- /note/ReentrantLock/images/condition_queue.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/ReentrantLock/images/condition_queue.jpg -------------------------------------------------------------------------------- /note/ReentrantLock/images/queue.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/ReentrantLock/images/queue.jpg -------------------------------------------------------------------------------- /note/Reflection/images/Class.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/Reflection/images/Class.jpg -------------------------------------------------------------------------------- /note/Reflection/reflection.md: -------------------------------------------------------------------------------- 1 | 核心便是Class类,类图如下: 2 | 3 | ![Class](images/Class.jpg) 4 | 5 | # 方法获取 6 | 7 | 我们以getDeclaredMethods为例: 8 | 9 | ```java 10 | @CallerSensitive 11 | public Method[] getDeclaredMethods() throws SecurityException { 12 | return copyMethods(privateGetDeclaredMethods(false)); 13 | } 14 | ``` 15 | 16 | privateGetDeclaredMethods: 17 | 18 | ```java 19 | private Method[] privateGetDeclaredMethods(boolean publicOnly) { 20 | Method[] res; 21 | ReflectionData rd = reflectionData(); 22 | if (rd != null) { 23 | res = publicOnly ? rd.declaredPublicMethods : rd.declaredMethods; 24 | if (res != null) return res; 25 | } 26 | // No cached value available; request value from VM 27 | res = Reflection.filterMethods(this, getDeclaredMethods0(publicOnly)); 28 | if (rd != null) { 29 | if (publicOnly) { 30 | rd.declaredPublicMethods = res; 31 | } else { 32 | rd.declaredMethods = res; 33 | } 34 | } 35 | return res; 36 | } 37 | ``` 38 | 39 | publicOnly为false便说明需要获取各种访问级别的方法。 40 | 41 | ## 缓存 42 | 43 | 为加快方法获取的性能,这里会对反射的结果进行缓存。缓存以Class内部类ReflectionData对象的形式进行保存: 44 | 45 | ```java 46 | private static class ReflectionData { 47 | volatile Field[] declaredFields; 48 | volatile Field[] publicFields; 49 | volatile Method[] declaredMethods; 50 | volatile Method[] publicMethods; 51 | volatile Constructor[] declaredConstructors; 52 | volatile Constructor[] publicConstructors; 53 | // Intermediate results for getFields and getMethods 54 | volatile Field[] declaredPublicFields; 55 | volatile Method[] declaredPublicMethods; 56 | volatile Class[] interfaces; 57 | // Value of classRedefinedCount when we created this ReflectionData instance 58 | final int redefinedCount; 59 | ReflectionData(int redefinedCount) { 60 | this.redefinedCount = redefinedCount; 61 | } 62 | } 63 | ``` 64 | 65 | reflectionData方法: 66 | 67 | ```java 68 | private ReflectionData reflectionData() { 69 | SoftReference> reflectionData = this.reflectionData; 70 | int classRedefinedCount = this.classRedefinedCount; 71 | ReflectionData rd; 72 | if (useCaches && reflectionData != null && (rd = reflectionData.get()) != null && 73 | rd.redefinedCount == classRedefinedCount) { 74 | return rd; 75 | } 76 | // else no SoftReference or cleared SoftReference or stale ReflectionData 77 | // -> create and replace new instance 78 | return newReflectionData(reflectionData, classRedefinedCount); 79 | } 80 | ``` 81 | 82 | 与之相关的两个属性定义: 83 | 84 | ```java 85 | private volatile transient SoftReference> reflectionData; 86 | // Incremented by the VM on each call to JVM TI RedefineClasses() 87 | // that redefines this class or a superclass. 88 | private volatile transient int classRedefinedCount = 0; 89 | ``` 90 | 91 | 从这里可以看出,每次JVM对当前类或其父类重新加载时都会导致classRedefinedCount的增加。Class使用了软引用进行缓存,**只要虚拟机进行Full GC,便会对软引用指向的对象进行回收**,所以软引用的对象的生存周期是当前至下一次Full GC。 92 | 93 | 变量useCaches决定了是否对反射结果进行缓存,其取值由方法checkInitted决定,相关源码: 94 | 95 | ```java 96 | String val = System.getProperty("sun.reflect.noCaches"); 97 | if (val != null && val.equals("true")) { 98 | useCaches = false; 99 | } 100 | ``` 101 | 102 | 有意思的细节: 这里使用字符串比较判断是否为true,而不是使用Boolean.getBoolean方法,这样是为了**避免Boolean类的初始化**,因为JVM规范定义了对类的静态方法的调用将导致类的初始化。 103 | 104 | 注意reflectionData方法判断缓存是否有效的条件里的这一个: 105 | 106 | ```java 107 | rd.redefinedCount == classRedefinedCount 108 | ``` 109 | 110 | 这就是说,只有缓存保存的类加载次数与类保存的相一致时缓存才是有效的。 111 | 112 | ## 过滤 113 | 114 | 当没有缓存或缓存已失效或被回收时,便需要向JVM请求获得相关信息,这里是通过native方法getDeclaredMethods0实现,类Reflection位于sun.reflect包下,JDK这样解释其功能: 将敏感的或是JVM内部的对象(属性或方法)过滤出去。 115 | 116 | ## 拷贝 117 | 118 | copyMethods实现: 119 | 120 | ```java 121 | private static Method[] copyMethods(Method[] arg) { 122 | Method[] out = new Method[arg.length]; 123 | ReflectionFactory fact = getReflectionFactory(); 124 | for (int i = 0; i < arg.length; i++) { 125 | out[i] = fact.copyMethod(arg[i]); 126 | } 127 | return out; 128 | } 129 | ``` 130 | 131 | 跳过复杂的调用关系,真正进行拷贝的其实就是Method的copy方法: 132 | 133 | ```java 134 | Method copy() { 135 | if (this.root != null) 136 | throw new IllegalArgumentException("Can not copy a non-root Method"); 137 | Method res = new Method(clazz, name, parameterTypes, returnType, 138 | exceptionTypes, modifiers, slot, signature, 139 | annotations, parameterAnnotations, annotationDefault); 140 | res.root = this; 141 | // Might as well eagerly propagate this if already present 142 | res.methodAccessor = methodAccessor; 143 | return res; 144 | } 145 | ``` 146 | 147 | 可以看出,每次返回的都是一个全新的Method对象,新对象的root属性指向原对象,一个Method对象及其副本共享一个methodAccessor,methodAccessor对象可以看做是对JVM相应方法的引用。 148 | 149 | 那这里为什么要进行拷贝呢? -------------------------------------------------------------------------------- /note/ScheduledThreadPool/images/DelayedWorkQueue.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/ScheduledThreadPool/images/DelayedWorkQueue.jpg -------------------------------------------------------------------------------- /note/ScheduledThreadPool/images/ScheduledFutureTask.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/ScheduledThreadPool/images/ScheduledFutureTask.jpg -------------------------------------------------------------------------------- /note/ScheduledThreadPool/images/ScheduledThreadPoolExecutor.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/ScheduledThreadPool/images/ScheduledThreadPoolExecutor.jpg -------------------------------------------------------------------------------- /note/ScheduledThreadPool/images/heap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/ScheduledThreadPool/images/heap.png -------------------------------------------------------------------------------- /note/ScheduledThreadPool/scheduledthreadpool.md: -------------------------------------------------------------------------------- 1 | # 创建 2 | 3 | 我们一般使用下面的方式进行创建: 4 | 5 | ```java 6 | public static ScheduledExecutorService newSingleThreadScheduledExecutor() { 7 | return new DelegatedScheduledExecutorService(new ScheduledThreadPoolExecutor(1)); 8 | } 9 | ``` 10 | 11 | DelegatedScheduledExecutorService实际上是对ScheduledExecutorService接口方法的转发,目的是只将ScheduledExecutorService接口的public方法暴露出来,这其实就是门面模式。 12 | 13 | 显然这里的核心便是ScheduledThreadPoolExecutor了: 14 | 15 | ![ScheduledThreadPoolExecutor](images/ScheduledThreadPoolExecutor.jpg) 16 | 17 | ScheduledThreadPoolExecutor构造器: 18 | 19 | ```java 20 | public ScheduledThreadPoolExecutor(int corePoolSize) { 21 | super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue()); 22 | } 23 | ``` 24 | 25 | 所以,默认情况下创建的是corePoolSize为1的线程池,而maximumPoolSize却为int最大值! 26 | 27 | 其工作队列DelayedWorkQueue是ScheduledThreadPoolExecutor的嵌套类: 28 | 29 | ![DelayedWorkQueue](images/DelayedWorkQueue.jpg) 30 | 31 | # 单次调度 32 | 33 | ```java 34 | public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { 35 | RunnableScheduledFuture t = decorateTask(callable, 36 | new ScheduledFutureTask(callable, triggerTime(delay, unit))); 37 | delayedExecute(t); 38 | return t; 39 | } 40 | ``` 41 | 42 | ## 触发时间计算 43 | 44 | ```java 45 | private long triggerTime(long delay, TimeUnit unit) { 46 | return triggerTime(unit.toNanos((delay < 0) ? 0 : delay)); 47 | } 48 | long triggerTime(long delay) { 49 | return now() + ((delay < (Long.MAX_VALUE >> 1)) ? delay : overflowFree(delay)); 50 | } 51 | ``` 52 | 53 | now方法即返回当前纳秒表示的时间,所以触发时间就是当前时间加延时。 54 | 55 | ## 任务包装 56 | 57 | Callable任务被包装成了ScheduledFutureTask对象,其是ScheduledThreadPoolExecutor的内部类: 58 | 59 | ![ScheduledFutureTask](images/ScheduledFutureTask.jpg) 60 | 61 | decorateTask是一个模板方法,空实现。 62 | 63 | ## 调度 64 | 65 | 核心便是delayedExecute方法: 66 | 67 | ```java 68 | private void delayedExecute(RunnableScheduledFuture task) { 69 | if (isShutdown()) 70 | reject(task); 71 | else { 72 | super.getQueue().add(task); 73 | if (isShutdown() && 74 | !canRunInCurrentRunState(task.isPeriodic()) && 75 | remove(task)) 76 | task.cancel(false); 77 | else 78 | ensurePrestart(); 79 | } 80 | } 81 | ``` 82 | 83 | isShutdown方法在父类ThreadPoolExecutor中实现,利用的便是我们已经提到过的状态记录的方法。 84 | 85 | ### 工作队列 86 | 87 | DelayedWorkQueue的类图位于上面创建一节中,其实此队列便是调度实现的核心,此队列实际上用数组实现了一个小顶堆,其add方法实际上通过offer方法实现: 88 | 89 | ```java 90 | public boolean offer(Runnable x) { 91 | RunnableScheduledFuture e = (RunnableScheduledFuture)x; 92 | final ReentrantLock lock = this.lock; 93 | lock.lock(); 94 | try { 95 | int i = size; 96 | //扩容 97 | if (i >= queue.length) 98 | grow(); 99 | size = i + 1; 100 | //队列为empty 101 | if (i == 0) { 102 | queue[0] = e; 103 | setIndex(e, 0); 104 | } else { 105 | siftUp(i, e); 106 | } 107 | if (queue[0] == e) { 108 | leader = null; 109 | available.signal(); 110 | } 111 | } finally { 112 | lock.unlock(); 113 | } 114 | return true; 115 | } 116 | ``` 117 | 118 | queue便是用以实现小顶堆的数组: 119 | 120 | ```java 121 | private RunnableScheduledFuture[] queue = new RunnableScheduledFuture[INITIAL_CAPACITY]; 122 | ``` 123 | 124 | 初始大小为16. 125 | 126 | 堆首先是一棵完全二叉树,按照如下的顺序将其节点存储到数组中: 127 | 128 | ![堆节点顺序](images/heap.png) 129 | 130 | 满足以下的性质: 131 | 132 | - 任一节点的父节点的数组下标为i / 2. 133 | - 节点的左子节点的下标为i * 2, 右子节点的下标为i * 2 + 1. 134 | - 添加节点时将节点放在数组的最后一个位置,然后不断的将此节点的值与其父节点比较,如果不满足堆的条件,交换之. 135 | - 堆排序的时间复杂度: O(NlongN). 136 | 137 | #### 扩容 138 | 139 | ```java 140 | private void grow() { 141 | int oldCapacity = queue.length; 142 | int newCapacity = oldCapacity + (oldCapacity >> 1); // grow 50% 143 | if (newCapacity < 0) // overflow 144 | newCapacity = Integer.MAX_VALUE; 145 | queue = Arrays.copyOf(queue, newCapacity); 146 | } 147 | ``` 148 | 149 | 1.5倍扩容,最大取int最大值。 150 | 151 | #### 上移 152 | 153 | 即使堆再次平衡的过程: 154 | 155 | ```java 156 | private void siftUp(int k, RunnableScheduledFuture key) { 157 | while (k > 0) { 158 | int parent = (k - 1) >>> 1; 159 | RunnableScheduledFuture e = queue[parent]; 160 | if (key.compareTo(e) >= 0) 161 | break; 162 | queue[k] = e; 163 | setIndex(e, k); 164 | k = parent; 165 | } 166 | queue[k] = key; 167 | setIndex(key, k); 168 | } 169 | ``` 170 | 171 | 很容易理解,就是一个和父节点交换,直到父节点的延时时间小于当前任务。 172 | 173 | ### 状态检查 174 | 175 | 将任务提交到队列后会再次对线程池当前的状态进行检查,相关源码: 176 | 177 | ```java 178 | super.getQueue().add(task); 179 | if (isShutdown() && !canRunInCurrentRunState(task.isPeriodic()) && remove(task)) { 180 | task.cancel(false); 181 | } 182 | ``` 183 | 184 | isShutdown成立的条件是当前状态不是运行状态,isPeriodic方法用以判断任务是否是持续任务: 185 | 186 | ```java 187 | public boolean isPeriodic() { 188 | return period != 0; 189 | } 190 | ``` 191 | 192 | period是ScheduledFutureTask的属性,其不同的取值意义如下: 193 | 194 | - 正值: 按固定的时间间隔调度 195 | - 负值: 按固定的时间延迟进行调度 196 | - 零: 单次任务 197 | 198 | canRunInCurrentRunState: 199 | 200 | ```java 201 | boolean canRunInCurrentRunState(boolean periodic) { 202 | return isRunningOrShutdown(periodic ? 203 | continueExistingPeriodicTasksAfterShutdown : 204 | executeExistingDelayedTasksAfterShutdown); 205 | } 206 | ``` 207 | 208 | ThreadPoolExecutor.isRunningOrShutdown: 209 | 210 | ```java 211 | final boolean isRunningOrShutdown(boolean shutdownOK) { 212 | int rs = runStateOf(ctl.get()); 213 | return rs == RUNNING || (rs == SHUTDOWN && shutdownOK); 214 | } 215 | ``` 216 | 217 | 这里判断的是在当前的状态下是否可以执行任务,SHUTDOWN态是由于shutdown方法被调用所致,不是shutdownNow。 218 | 219 | continueExistingPeriodicTasksAfterShutdown和executeExistingDelayedTasksAfterShutdown属性ScheduledThreadPoolExecutor为我们留下了setter方法,你懂的。 220 | 221 | ### 任务移除 222 | 223 | 如果当前已不能进行任务执行,那么便将刚提交的任务从堆中移除,核心的实现为DelayedWorkQueue的同名方法: 224 | 225 | ```java 226 | public boolean remove(Object x) { 227 | final ReentrantLock lock = this.lock; 228 | lock.lock(); 229 | try { 230 | int i = indexOf(x); 231 | if (i < 0) 232 | return false; 233 | setIndex(queue[i], -1); 234 | int s = --size; 235 | RunnableScheduledFuture replacement = queue[s]; 236 | queue[s] = null; 237 | if (s != i) { 238 | siftDown(i, replacement); 239 | if (queue[i] == replacement) 240 | //不能进行下移,再试试上移? 241 | siftUp(i, replacement); 242 | } 243 | return true; 244 | } finally { 245 | lock.unlock(); 246 | } 247 | } 248 | ``` 249 | 250 | 关键在于条件判断`if (s != i)`,即被移除的节点不是最后(数组的最后)一个节点,在这种情况下会导致数组i处出现一个空位,所以在这里进行了先下移再上移的尝试,以使用最末节点或其它节点填补此空位,同时数组大小减一。 251 | 252 | ### 任务取消 253 | 254 | ScheduledFutureTask.cancel: 255 | 256 | ```java 257 | public boolean cancel(boolean mayInterruptIfRunning) { 258 | boolean cancelled = super.cancel(mayInterruptIfRunning); 259 | if (cancelled && removeOnCancel && heapIndex >= 0) 260 | remove(this); 261 | return cancelled; 262 | } 263 | ``` 264 | 265 | 父类FutureTask的cancel方法已经见过了,removeOnCancel为ScheduledThreadPoolExecutor的属性,默认为false,其实这里调用remove是不必要的,因为已经被调用过了。 266 | 267 | ### Worker启动 268 | 269 | ThreadPoolExecutor.ensurePrestart: 270 | 271 | ```java 272 | void ensurePrestart() { 273 | int wc = workerCountOf(ctl.get()); 274 | if (wc < corePoolSize) 275 | addWorker(null, true); 276 | else if (wc == 0) 277 | addWorker(null, false); 278 | } 279 | ``` 280 | 281 | 即使corePoolSize为0,也要保证有一个Worker线程。 282 | 283 | # 任务获取 284 | 285 | 在ThreadPoolExecutor我们已经见过了,Worker线程通过调用任务队列的take方法进行获取: 286 | 287 | ```java 288 | public RunnableScheduledFuture take() throws InterruptedException { 289 | final ReentrantLock lock = this.lock; 290 | lock.lockInterruptibly(); 291 | try { 292 | for (;;) { 293 | RunnableScheduledFuture first = queue[0]; 294 | //堆为空 295 | if (first == null) 296 | available.await(); 297 | else { 298 | long delay = first.getDelay(NANOSECONDS); 299 | //getDelay返回的是延时执行时间和当前时间的差,非正值说明此任务可以执行了 300 | if (delay <= 0) 301 | return finishPoll(first); 302 | first = null; 303 | if (leader != null) 304 | //已存在leader,所以当前线程为follower,永久等待 305 | available.await(); 306 | else { 307 | Thread thisThread = Thread.currentThread(); 308 | leader = thisThread; 309 | try { 310 | //当前线程成为leader,等待至下一次任务执行时间 311 | available.awaitNanos(delay); 312 | } finally { 313 | if (leader == thisThread) 314 | leader = null; 315 | } 316 | } 317 | } 318 | } 319 | } finally { 320 | if (leader == null && queue[0] != null) 321 | //当前线程接下来要去执行定时任务逻辑,所以唤醒一个follower(如果有),使之成为新的leader 322 | available.signal(); 323 | lock.unlock(); 324 | } 325 | } 326 | ``` 327 | 328 | 这里其实应用了Leader/Follower模式,参考: 329 | 330 | [Leader/Follower多线程网络模型介绍 ](http://blog.csdn.net/goldlevi/article/details/7705180) 331 | 332 | 使用这种模式的原因猜想应该是这样: 由于定时任务的特殊性,在某一时刻应该只有一个任务等开始时间最短,这样的话只让一个线程阻塞至既定时间即可,其它线程及时醒来也不能立即执行任务,从而造成了性能的浪费。 333 | 334 | 如果堆为空,那么等待的Worker何时被唤醒呢?玄机就在offer方法,相关源码: 335 | 336 | ```java 337 | if (queue[0] == e) { 338 | leader = null; 339 | available.signal(); 340 | } 341 | ``` 342 | 343 | 为什么新任务被至于堆顶时需要唤醒Worker呢,因为这就意味着之前堆为空或最近需要执行任务的时间已经改变,需要重新调整leader的睡眠时间。 344 | 345 | finishPoll方法很容易猜到,就是填补堆顶的空缺: 346 | 347 | ```java 348 | private RunnableScheduledFuture finishPoll(RunnableScheduledFuture f) { 349 | int s = --size; 350 | RunnableScheduledFuture x = queue[s]; 351 | queue[s] = null; 352 | if (s != 0) 353 | siftDown(0, x); 354 | setIndex(f, -1); 355 | return f; 356 | } 357 | ``` 358 | 359 | 将最后 一个元素从堆顶使其"沉沦"。 360 | 361 | # 重生 362 | 363 | 对于持续执行的任务,在一次执行完成后应该将其再次放入到堆中,以待下次执行,这一步是在ScheduledFutureTask的run方法中完成: 364 | 365 | ```java 366 | public void run() { 367 | boolean periodic = isPeriodic(); 368 | if (!canRunInCurrentRunState(periodic)) 369 | cancel(false); 370 | //单次任务 371 | else if (!periodic) 372 | ScheduledFutureTask.super.run(); 373 | //持续任务 374 | else if (ScheduledFutureTask.super.runAndReset()) { 375 | //设置下次执行的时间 376 | setNextRunTime(); 377 | //重新加入到堆中 378 | reExecutePeriodic(outerTask); 379 | } 380 | } 381 | ``` 382 | 383 | FutureTask.runAndReset方法便是调用任务逻辑的地方,不同于我们已经见过的run方法,这里**不会设置任务执行的结果(即outcome属性),也不会改变Future的状态**,所以即使一次执行完毕,Future看到的状态仍是未完成。 384 | 385 | # shutdown 386 | 387 | 主要逻辑由父类ThreadPoolExecutor实现,唯一的区别便在于ScheduledThreadPoolExecutor实现了父类的模板方法onShutdown(简略版源码): 388 | 389 | ```java 390 | @Override void onShutdown() { 391 | BlockingQueue q = super.getQueue(); 392 | boolean keepDelayed = getExecuteExistingDelayedTasksAfterShutdownPolicy(); 393 | boolean keepPeriodic = getContinueExistingPeriodicTasksAfterShutdownPolicy(); 394 | if (!keepDelayed && !keepPeriodic) { 395 | for (Object e : q.toArray()) 396 | if (e instanceof RunnableScheduledFuture) 397 | ((RunnableScheduledFuture) e).cancel(false); 398 | q.clear(); 399 | } 400 | } 401 | ``` 402 | 403 | 这里所做的就是将堆中所有未执行的任务取消,所以如果有线程阻塞在等待任务的结果上最终可以返回。 404 | 405 | # shutdownNow 406 | 407 | 直接调用父类的方法实现,可以想象,这便会导致线程池已关闭但`Future.get`无法返回。 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | -------------------------------------------------------------------------------- /note/Socket/images/InetAddress.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/Socket/images/InetAddress.jpg -------------------------------------------------------------------------------- /note/Socket/images/InetAddressImpl.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/Socket/images/InetAddressImpl.jpg -------------------------------------------------------------------------------- /note/Socket/images/InetSocketAddress.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/Socket/images/InetSocketAddress.jpg -------------------------------------------------------------------------------- /note/Socket/images/SocketInputStream.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/Socket/images/SocketInputStream.jpg -------------------------------------------------------------------------------- /note/Socket/images/SocksSocketImpl.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/Socket/images/SocksSocketImpl.jpg -------------------------------------------------------------------------------- /note/Thread/images/Thread.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/Thread/images/Thread.jpg -------------------------------------------------------------------------------- /note/Thread/thread.md: -------------------------------------------------------------------------------- 1 | ![Thread](images/Thread.jpg) 2 | 3 | # 构造器 4 | 5 | 我们以喜闻乐见的为例: 6 | 7 | ```java 8 | public Thread(Runnable target) { 9 | init(null, target, "Thread-" + nextThreadNum(), 0); 10 | } 11 | ``` 12 | 13 | 第一个参数为线程组,最后一个为栈大小。init方法就是一些内部属性的赋值操作。 14 | 15 | # 启动 16 | 17 | ```java 18 | public synchronized void start() { 19 | if (threadStatus != 0) 20 | throw new IllegalThreadStateException(); 21 | boolean started = false; 22 | start0(); 23 | started = true; 24 | } 25 | ``` 26 | 27 | 核心 便在于native方法start0. -------------------------------------------------------------------------------- /note/Thread/thread.uml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | thread 12 | 1 13 | 14 | classes 15 | xblXT7ikZ0CJMsHmJSAB1gAA 16 | 1 17 | 18 | Thread 19 | bhQuVGDozkeyAcd4k0Ag9AAA 20 | 21 | eD6HxgDTe0i3GrH/LraOwwAA 22 | 3 23 | 24 | clMaroon 25 | $00B9FFFF 26 | 316 27 | 52 28 | 85 29 | 59 30 | sdkNone 31 | pvesutYpsk24+1l0yK9ChgAA 32 | 33 | 34 | 1 35 | Runnable 36 | 37 | 38 | <<interface>> 39 | 40 | 41 | False 42 | 43 | 44 | 45 | False 46 | pvesutYpsk24+1l0yK9ChgAA 47 | 48 | 49 | pvesutYpsk24+1l0yK9ChgAA 50 | 51 | 52 | 53 | clMaroon 54 | $00B9FFFF 55 | 271 56 | 160 57 | 174 58 | 121 59 | CMaPWOvZ40OK3ImNWktJpwAA 60 | 61 | 62 | 1 63 | Thread 64 | 65 | 66 | False 67 | 68 | 69 | False 70 | 71 | 72 | 73 | CMaPWOvZ40OK3ImNWktJpwAA 74 | 75 | 76 | CMaPWOvZ40OK3ImNWktJpwAA 77 | 78 | 79 | False 80 | CMaPWOvZ40OK3ImNWktJpwAA 81 | 82 | 83 | 84 | clMaroon 85 | $00B9FFFF 86 | 357,160;358,110 87 | ARHpnJOH50asjm53edtivwAA 88 | iwv2swiMbEOtHw6T3EKW9wAA 89 | b1k6IISQXk6X+HhJf5RZEAAA 90 | 91 | False 92 | 1.5707963267949 93 | 15 94 | ARHpnJOH50asjm53edtivwAA 95 | 96 | 97 | False 98 | 1.5707963267949 99 | 30 100 | ARHpnJOH50asjm53edtivwAA 101 | 102 | 103 | False 104 | -1.5707963267949 105 | 15 106 | ARHpnJOH50asjm53edtivwAA 107 | 108 | 109 | 110 | 111 | 3 112 | 113 | Runnable 114 | bhQuVGDozkeyAcd4k0Ag9AAA 115 | 3 116 | iwv2swiMbEOtHw6T3EKW9wAA 117 | Ca2+zkdz5E6L5T5+T4E0pAAA 118 | jK+lM1RFwkyuHMPxW6p0sQAA 119 | 1 120 | ARHpnJOH50asjm53edtivwAA 121 | 1 122 | 123 | void run 124 | pvesutYpsk24+1l0yK9ChgAA 125 | 126 | 127 | 128 | Thread 129 | bhQuVGDozkeyAcd4k0Ag9AAA 130 | 4 131 | b1k6IISQXk6X+HhJf5RZEAAA 132 | so4HebnX+0eoIGCGd7ZOzAAA 133 | c2Q+p1cVHEeTvBaRD3bv/wAA 134 | qfz2xmUbsEyfMtY0mNGc2AAA 135 | 1 136 | ARHpnJOH50asjm53edtivwAA 137 | 6 138 | 139 | volatile String name 140 | vkPrivate 141 | CMaPWOvZ40OK3ImNWktJpwAA 142 | 143 | 144 | int priority 145 | vkPrivate 146 | CMaPWOvZ40OK3ImNWktJpwAA 147 | 148 | 149 | boolean daemon 150 | vkPrivate 151 | CMaPWOvZ40OK3ImNWktJpwAA 152 | 153 | 154 | Runnable target 155 | vkPrivate 156 | CMaPWOvZ40OK3ImNWktJpwAA 157 | 158 | 159 | ClassLoader contextClassLoader 160 | vkPrivate 161 | CMaPWOvZ40OK3ImNWktJpwAA 162 | 163 | 164 | long threadSeqNumber 165 | vkPrivate 166 | skClassifier 167 | CMaPWOvZ40OK3ImNWktJpwAA 168 | 169 | 170 | 171 | bhQuVGDozkeyAcd4k0Ag9AAA 172 | CMaPWOvZ40OK3ImNWktJpwAA 173 | pvesutYpsk24+1l0yK9ChgAA 174 | 4 175 | rFw2HAMJO06au/keU7XjkwAA 176 | f/zXXARRqkSI6tvkE76NxQAA 177 | ujsm2gEJIUmCvJ+9j912vgAA 178 | PMlg193L/0iytoecmPYnPwAA 179 | 180 | 181 | 182 | 183 | 184 | -------------------------------------------------------------------------------- /note/ThreadLocal/images/ThreadLocal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/ThreadLocal/images/ThreadLocal.jpg -------------------------------------------------------------------------------- /note/ThreadLocal/threadlocal.md: -------------------------------------------------------------------------------- 1 | ![ThreadLocal](images/ThreadLocal.jpg) 2 | 3 | # get 4 | 5 | ```java 6 | public T get() { 7 | Thread t = Thread.currentThread(); 8 | ThreadLocalMap map = getMap(t); 9 | if (map != null) { 10 | ThreadLocalMap.Entry e = map.getEntry(this); 11 | if (e != null) { 12 | @SuppressWarnings("unchecked") 13 | T result = (T)e.value; 14 | return result; 15 | } 16 | } 17 | return setInitialValue(); 18 | } 19 | ``` 20 | 21 | getMap实现: 22 | 23 | ```java 24 | ThreadLocalMap getMap(Thread t) { 25 | return t.threadLocals; 26 | } 27 | ``` 28 | 29 | 可以看出,其实ThreadLocalMap作为线程Thread的属性而存在: 30 | 31 | ```java 32 | ThreadLocal.ThreadLocalMap threadLocals = null; 33 | ``` 34 | 35 | ## Map创建 36 | 37 | 先来看一下线程的ThreadLocalMap属性不存在的情况,setInitialValue方法: 38 | 39 | ```java 40 | private T setInitialValue() { 41 | T value = initialValue(); 42 | Thread t = Thread.currentThread(); 43 | ThreadLocalMap map = getMap(t); 44 | if (map != null) 45 | map.set(this, value); 46 | else 47 | createMap(t, value); 48 | return value; 49 | } 50 | ``` 51 | 52 | initialValue方法便是我们第一次访问用以获得初始化值的方法: 53 | 54 | ```java 55 | protected T initialValue() { 56 | return null; 57 | } 58 | ``` 59 | 60 | 所以,这便解释了我们在使用ThreadLocal时为什么要创建一个ThreadLocal的子类并覆盖此方法。 61 | 62 | ```java 63 | void createMap(Thread t, T firstValue) { 64 | t.threadLocals = new ThreadLocalMap(this, firstValue); 65 | } 66 | ``` 67 | 68 | 构造参数为初始增加的一个键值对,从这里可以看出,**ThreadLocalMap以ThreadLocal对象为键**。 69 | 70 | ### ThreadLocalMap 71 | 72 | 其声明如下: 73 | 74 | ```java 75 | static class ThreadLocalMap {} 76 | ``` 77 | 78 | 那么问题来了,这里为什么要重新实现一个Map,而不用已有的HashMap等类呢?基于以下几点考虑: 79 | 80 | - 所有方法均为private。 81 | - 内部类Entry继承自WeakReference,当内存紧张时可以对ThreadLocal变量进行回收,注意这里并没有结合ReferenceQueue使用。 82 | 83 | 构造器源码: 84 | 85 | ```java 86 | ThreadLocalMap(ThreadLocal firstKey, Object firstValue) { 87 | table = new Entry[INITIAL_CAPACITY]; 88 | int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); 89 | table[i] = new Entry(firstKey, firstValue); 90 | size = 1; 91 | setThreshold(INITIAL_CAPACITY); 92 | } 93 | ``` 94 | 95 | setThreshold: 96 | 97 | ```java 98 | private void setThreshold(int len) { 99 | threshold = len * 2 / 3; 100 | } 101 | ``` 102 | 103 | 和HashMap的套路一样,只不过这里负载因子写死了,2 / 3,强调一下,**不是3 / 4 !!!** 104 | 105 | # set 106 | 107 | ```java 108 | public void set(T value) { 109 | Thread t = Thread.currentThread(); 110 | ThreadLocalMap map = getMap(t); 111 | if (map != null) 112 | map.set(this, value); 113 | else 114 | createMap(t, value); 115 | } 116 | ``` 117 | 118 | 正如注释中所说,我们在使用ThreadLocal时应该去覆盖initialValue方法,而不是set。显然这里的核心便是ThreadLocalMap的set方法: 119 | 120 | ```java 121 | private void set(ThreadLocal key, Object value) { 122 | Entry[] tab = table; 123 | int len = tab.length; 124 | int i = key.threadLocalHashCode & (len-1); 125 | for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { 126 | ThreadLocal k = e.get(); 127 | //bin里的第一个节点即为所需key,更新value 128 | if (k == key) { 129 | e.value = value; 130 | return; 131 | } 132 | if (k == null) { 133 | replaceStaleEntry(key, value, i); 134 | return; 135 | } 136 | } 137 | tab[i] = new Entry(key, value); 138 | int sz = ++size; 139 | if (!cleanSomeSlots(i, sz) && sz >= threshold) 140 | rehash(); 141 | } 142 | ``` 143 | 144 | # 注意 145 | 146 | ThreadLocalMap的底层实现貌似是基于一个叫做"Knuth Algorithm"的算法,在这里不再细究其实现细节,但有几个地方值得注意。 147 | 148 | ## 扩容 149 | 150 | 不同于Map接口的实现,ThreadLocalMap的扩容似乎没有上限限制,resize方法部分源码可以证明: 151 | 152 | ```java 153 | private void resize() { 154 | Entry[] oldTab = table; 155 | int oldLen = oldTab.length; 156 | int newLen = oldLen * 2; 157 | Entry[] newTab = new Entry[newLen]; 158 | //... 159 | } 160 | ``` 161 | 162 | ## 哈希冲突 163 | 164 | 不同于喜闻乐见的HashMap用链表 + 红黑树的方式解决哈希冲突,这里用的应该是线性探查法,即如果根据哈希值计算得来的位置不为空,那么将继续尝试下一个位置。 165 | 166 | 这一点可以从resize方法的下列源码得到证明: 167 | 168 | ```java 169 | int h = k.threadLocalHashCode & (newLen - 1); 170 | while (newTab[h] != null) 171 | h = nextIndex(h, newLen); 172 | newTab[h] = e; 173 | ``` 174 | 175 | ## 哈希值 176 | 177 | ThreadLocalMap使用的哈希值源自ThreadLocal的下列属性: 178 | 179 | ```java 180 | private final int threadLocalHashCode = nextHashCode(); 181 | private static int nextHashCode() { 182 | return nextHashCode.getAndAdd(HASH_INCREMENT); 183 | } 184 | ``` 185 | 186 | 而nextHashCode属性则是AtomicInteger类型,HASH_INCREMENT定义: 187 | 188 | ```java 189 | private static final int HASH_INCREMENT = 0x61c88647; 190 | ``` 191 | 192 | ## 清除 193 | 194 | 由于ThreadLocalMap的key(即ThreadLocal)为弱引用,所以当其被回收时,势必需要将value置为null以便于进行垃圾回收。那么这个清除的时机又是什么呢? 195 | 196 | 答案是get, set, remove都有可能。 197 | 198 | # Lambda支持 199 | 200 | jdk8支持使用以下方式进行初始化: 201 | 202 | ```java 203 | ThreadLocal local = ThreadLocal.withInitial(() -> "hello"); 204 | ``` 205 | 206 | withInitial源码: 207 | 208 | ```java 209 | public static ThreadLocal withInitial(Supplier supplier) { 210 | return new SuppliedThreadLocal<>(supplier); 211 | } 212 | ``` 213 | 214 | SuppliedThreadLocal是ThreadLocal的内部类,也是其子类: 215 | 216 | ```java 217 | static final class SuppliedThreadLocal extends ThreadLocal { 218 | private final Supplier supplier; 219 | SuppliedThreadLocal(Supplier supplier) { 220 | this.supplier = Objects.requireNonNull(supplier); 221 | } 222 | @Override 223 | protected T initialValue() { 224 | return supplier.get(); 225 | } 226 | } 227 | ``` 228 | 229 | 一目了然。 230 | 231 | # 内存泄漏 232 | 233 | 以下两篇博客足矣: 234 | 235 | [深入分析 ThreadLocal 内存泄漏问题](http://www.importnew.com/22039.html) 236 | 237 | [ThreadLocal 内存泄露的实例分析](http://www.importnew.com/22046.html) 238 | 239 | # 继承性问题 240 | 241 | 子线程中是否可以获得父线程设置的ThreadLocal变量? 答案是不可以,如以下测试代码: 242 | 243 | ```java 244 | public class Test { 245 | 246 | private ThreadLocal threadLocal = new InheritableThreadLocal<>(); 247 | 248 | public void test() throws InterruptedException { 249 | threadLocal.set("parent"); 250 | 251 | Thread thread = new Thread(() -> { 252 | System.out.println(threadLocal.get()); 253 | threadLocal.set("child"); 254 | System.out.println(threadLocal.get()); 255 | }); 256 | 257 | thread.start(); 258 | 259 | thread.join(); 260 | 261 | System.out.println(threadLocal.get()); 262 | } 263 | 264 | public static void main(String[] args) throws InterruptedException { 265 | new Test().test(); 266 | } 267 | 268 | } 269 | ``` 270 | 271 | 执行结果是: 272 | 273 | ```plaintext 274 | null 275 | child 276 | parent 277 | ``` 278 | 279 | 从中可以得出两个结论: 280 | 281 | - 子线程无法获得父线程设置的ThreadLocal。 282 | - 父子线程的ThreadLocal是相互独立的。 283 | 284 | 解决方法是使用java.lang.InheritableThreadLocal类。原因其实很容易理解: **ThreadLocal数据以线程为单位进行保存**,**InheritableThreadLocal的原理是在子线程创建的时候 285 | 将父线程的变量(浅)拷贝到自身中**。 286 | 287 | 从源码的角度进行原理的说明,Thread中其实有两个ThreadLocalMap: 288 | 289 | ```java 290 | ThreadLocal.ThreadLocalMap threadLocals = null; 291 | ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; 292 | ``` 293 | 294 | **普通的ThreadLocal被保存在threadLocals中,InheritableThreadLocal被保存在inheritableThreadLocal中**,注意这里是并列的关系,即两者可以同时存在且不为空。另外一个关键的问题便是 295 | 父线程的变量是何时被复制到子线程中的,答案是在子线程创建时,init方法: 296 | 297 | ```java 298 | private void init(ThreadGroup g, Runnable target, String name, 299 | long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { 300 | if (inheritThreadLocals && parent.inheritableThreadLocals != null) 301 | this.inheritableThreadLocals = 302 | ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); 303 | } 304 | ``` 305 | 306 | inheritThreadLocals除非我们使用了带AccessControlContext参数的构造器,默认都是true。 307 | 308 | 然而到了这里仍有问题存在:那就是线程池场景。一个线程**只会在创建时从其父线程中拷贝一次属性**,而线程池中的线程需要动态地执行从不同的上级线程提交地任务,在此种情形下逻辑上的 309 | 父线程也就不再存在了,阿里巴巴的[transmittable-thread-local](https://github.com/alibaba/transmittable-thread-local)解决了这一问题,核心原理其实是实现了一个Runnable的包装, 310 | 伪代码如下: 311 | 312 | ```java 313 | public class Wrapper implements Runnable { 314 | 315 | private final Runnable target; 316 | 317 | @Override 318 | public final void run() { 319 | //1.拷贝父变量 320 | try { 321 | target.run(); 322 | } finally { 323 | //2.还原... 324 | } 325 | } 326 | } 327 | ``` 328 | 329 | 参考: [ThreadLocal父子线程传递实现方案](https://zhuanlan.zhihu.com/p/28501035) -------------------------------------------------------------------------------- /note/ThreadPool/images/FutureTask.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/ThreadPool/images/FutureTask.jpg -------------------------------------------------------------------------------- /note/ThreadPool/images/RejectedExecutionHandler.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/ThreadPool/images/RejectedExecutionHandler.jpg -------------------------------------------------------------------------------- /note/ThreadPool/images/ThreadFactory.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/ThreadPool/images/ThreadFactory.jpg -------------------------------------------------------------------------------- /note/ThreadPool/images/ThreadPoolExecutor.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/ThreadPool/images/ThreadPoolExecutor.jpg -------------------------------------------------------------------------------- /note/ThreadPool/images/WaitNode.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/ThreadPool/images/WaitNode.jpg -------------------------------------------------------------------------------- /note/ThreadPool/images/Worker.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/ThreadPool/images/Worker.jpg -------------------------------------------------------------------------------- /note/TreeMap/images/TreeMap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/TreeMap/images/TreeMap.jpg -------------------------------------------------------------------------------- /note/TreeMap/treemap.md: -------------------------------------------------------------------------------- 1 | 类图: 2 | 3 | ![TreeMap](images/TreeMap.jpg) 4 | 5 | TreeMap的构造器可以接受一个Comparator参数用以决定key的顺序,如果没有指定,那么尝试将key强转为Comparable,如果转换失败那就抛出异常了。 6 | 7 | # put 8 | 9 | ```java 10 | public V put(K key, V value) { 11 | Entry t = root; 12 | if (t == null) { 13 | compare(key, key); // type (and possibly null) check 14 | root = new Entry<>(key, value, null); 15 | size = 1; 16 | modCount++; 17 | return null; 18 | } 19 | int cmp; 20 | Entry parent; 21 | // split comparator and comparable paths 22 | Comparator cpr = comparator; 23 | //按Comparator查找 24 | if (cpr != null) { 25 | do { 26 | parent = t; 27 | cmp = cpr.compare(key, t.key); 28 | if (cmp < 0) 29 | t = t.left; 30 | else if (cmp > 0) 31 | t = t.right; 32 | else 33 | return t.setValue(value); 34 | } while (t != null); 35 | } 36 | else { 37 | if (key == null) 38 | throw new NullPointerException(); 39 | //强转为Comparable 40 | Comparable k = (Comparable) key; 41 | do { 42 | parent = t; 43 | cmp = k.compareTo(t.key); 44 | if (cmp < 0) 45 | t = t.left; 46 | else if (cmp > 0) 47 | t = t.right; 48 | else 49 | return t.setValue(value); 50 | } while (t != null); 51 | } 52 | Entry e = new Entry<>(key, value, parent); 53 | if (cmp < 0) 54 | parent.left = e; 55 | else 56 | parent.right = e; 57 | //修复红黑树 58 | fixAfterInsertion(e); 59 | size++; 60 | modCount++; 61 | return null; 62 | } 63 | ``` 64 | 65 | 红黑树首先是一棵二叉查找树,put方法首先便是在树中进行搜索寻找合适的插入位置,然后修复红黑树。 66 | 67 | # get 68 | 69 | 即put的反过程,参考put即可。 70 | 71 | # entrySet 72 | 73 | 默认按照key的升序进行迭代: 74 | 75 | ```java 76 | public Set> entrySet() { 77 | EntrySet es = entrySet; 78 | return (es != null) ? es : (entrySet = new EntrySet()); 79 | } 80 | ``` 81 | 82 | 我们重点关注EntrySet是如何获得下一个节点的: 83 | 84 | ```java 85 | static TreeMap.Entry successor(Entry t) { 86 | if (t == null) 87 | return null; 88 | else if (t.right != null) { 89 | Entry p = t.right; 90 | while (p.left != null) 91 | p = p.left; 92 | return p; 93 | } else { 94 | Entry p = t.parent; 95 | Entry ch = t; 96 | while (p != null && ch == p.right) { 97 | ch = p; 98 | p = p.parent; 99 | } 100 | return p; 101 | } 102 | } 103 | ``` 104 | 105 | 很明显。 -------------------------------------------------------------------------------- /note/UDP/images/DatagramPacket.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/UDP/images/DatagramPacket.jpg -------------------------------------------------------------------------------- /note/UDP/images/DatagramSocket.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/UDP/images/DatagramSocket.jpg -------------------------------------------------------------------------------- /note/UDP/images/DatagramSocketImpl.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/UDP/images/DatagramSocketImpl.jpg -------------------------------------------------------------------------------- /note/UDP/udp.md: -------------------------------------------------------------------------------- 1 | 一个简单的Server/Client交互过程参见udp包。从中可以看出其实对于UDP来说,服务器和客户端代码其实是一样的。在此我们将"服务器"简单的理解为数据的接收方。 2 | 3 | # 类图 4 | 5 | ## DatagramSocket 6 | 7 | ![DatagramSocket类图](images/DatagramSocket.jpg) 8 | 9 | ## DatagramSocketImpl 10 | 11 | ![DatagramSocketImpl类图](images/DatagramSocketImpl.jpg) 12 | 13 | 14 | 15 | # 数据接收 16 | 17 | 示例代码: 18 | 19 | ```java 20 | DatagramSocket server = new DatagramSocket(8080); 21 | byte[] buffer = new byte[1024]; 22 | DatagramPacket packet = new DatagramPacket(buffer, 1024); 23 | server.receive(packet); 24 | System.out.println("Server接收到: " + new String(buffer, 0, packet.getLength())); 25 | ``` 26 | 27 | ## Socket创建 28 | 29 | 创建和TCP相似,真正的实现位于DualStackPlainDatagramSocketImpl.datagramSocketCreate,简略版源码: 30 | 31 | ```java 32 | protected void datagramSocketCreate() { 33 | int newfd = socketCreate(false /* v6Only */); 34 | fdAccess.set(fd, newfd); 35 | } 36 | ``` 37 | 38 | socketCreate为native方法,Windows实现位于DualStackPlainDatagramSocketImpl.c的Java_java_net_DualStackPlainDatagramSocketImpl_socketCreate方法,简略版源码: 39 | 40 | ```java 41 | fd = (int) socket(AF_INET6, SOCK_DGRAM, 0); 42 | rv = setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (char *) &opt, sizeof(opt)); 43 | ``` 44 | 45 | 结合TCP的创建过程可以发现,两者的创建都是通过Windows API socket方法完成,区别在于第二个参数,TCP是SOCK_STREAM而UDP是SOCK_DGRAM。 46 | 47 | ## 端口绑定 48 | 49 | 和TCP一个套路。 50 | 51 | ## 数据包 52 | 53 | Java使用DatagramPacket作为UDP数据的载体,而**UDP正是根据数据包中保存的源端口和目的端口在网络中进行传输**。源IP和目的IP由IP层提供。其类图如下(仅包含属性): 54 | ![DatagramPacket类图](images/DatagramPacket.jpg) 55 | 56 | 而包含的方法便是这些属性的getter/setter。 57 | 58 | 数据包有两个非常重要的用法: 59 | 60 | - length属性,它表示**需要发送或实际接收到的数据大小**。这在进行数据接收的时候非常关键。 61 | - 当进行数据接收时,对方客户端的IP和端口会被设置到其address和port属性,这样便可以获得客户端的地址了。 62 | 63 | ## 接收 64 | 65 | DualStackPlainDatagramSocketImpl.receive0: 66 | 67 | ```java 68 | protected synchronized void receive0(DatagramPacket p) throws IOException { 69 | int nativefd = checkAndReturnNativeFD(); 70 | socketReceiveOrPeekData(nativefd, p, timeout, connected, false /*receive*/); 71 | } 72 | ``` 73 | 74 | 不用想,socketReceiveOrPeekData是native方法,对应Windows/Linux上的recvfrom函数,相比于recv,recvfrom多了两个参数: 远程IP和端口。 75 | 76 | 为什么方法名叫ReceiveOrPeek呢?猜测和系统的UDP实现有关: 77 | 78 | > 收到一个UDP包后,验证没有错误后,放入一个包队列中,队列中的每一个元素就是一个完整的UDP包。当应用程序通过recvfrom()读取时,OS把相应的一个完整UDP包取出,然后拷贝到用户提供的内存中,物理用户提供的内存大小是多少,OS都会完整取出一个UDP包。如果用户提供的内存小于这个UDP包的大小,那么在填充慢内存后,UDP包剩余的部分就会被丢弃,以后再也无法取回。 79 | 80 | # 数据发送 81 | 82 | 由DualStackPlainDatagramSocketImpl的send方法完成对native方法 socketSend的调用,最终由Windows API sendto()完成数据的真正发送。 83 | 84 | # 广播消息 85 | 86 | 即MulticastSocket,与DatagramSocket相比重点在于增加的join/leaveGroup,get/setTimeToLive方法。所谓的加入或是离开组中的组其实是一个特殊的IP地址,参考: [组播地址](http://baike.baidu.com/link?url=8BT8unPjpaEj_Pyx63809zbFHhAL1kiYOSVa6ZrWlqf84YdFUGN0DBBxevDS_eGUDE1Xknp5rIjqAS5ecZ8OsihmR1uQfH_NwABi9NqJXhbyuPb3Ji5TMeO92LMiZEmv) 87 | 88 | 而TTL即设置数据包可以跨过多少个网络: 89 | 90 | > 当ttl的值为0时,指定数据报应停留在本地主机;当ttl的值为1时,指定数据报发送到本地局域网;当ttl的值为32时,意味着只能发送到本站点的网络上;当ttl的值为64时,意味着数据报应保留在本地区;当ttl的值为128时,意味着数据报应保留在本大洲;当ttl的值为255时,意味着数据报可发送到所有地方;在默认情况下,该ttl的值为1. 91 | 92 | 从源码可以看出,加入/离开组其实是对Windows setsockopt方法IP_ADD_MEMBERSHIP和IP_DROP_MEMBERSHIP两个选项的设置。 -------------------------------------------------------------------------------- /note/URLConnection/images/CookieHandler.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/URLConnection/images/CookieHandler.jpg -------------------------------------------------------------------------------- /note/URLConnection/images/HttpCapture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/URLConnection/images/HttpCapture.jpg -------------------------------------------------------------------------------- /note/URLConnection/images/HttpClient.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/URLConnection/images/HttpClient.jpg -------------------------------------------------------------------------------- /note/URLConnection/images/MessageHeader.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/URLConnection/images/MessageHeader.jpg -------------------------------------------------------------------------------- /note/URLConnection/images/PrintStream.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/URLConnection/images/PrintStream.jpg -------------------------------------------------------------------------------- /note/URLConnection/images/ResponseCache.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/URLConnection/images/ResponseCache.jpg -------------------------------------------------------------------------------- /note/URLConnection/images/URLConnection.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/URLConnection/images/URLConnection.jpg -------------------------------------------------------------------------------- /note/URLConnection/images/URLStreamHandler.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/URLConnection/images/URLStreamHandler.jpg -------------------------------------------------------------------------------- /note/URLConnection/images/URLStreamHandler子类.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaswalker/jdk-sourcecode-analysis/b830a57a67c7fbdf22bff345ca6ad5e5555000a9/note/URLConnection/images/URLStreamHandler子类.png -------------------------------------------------------------------------------- /note/URLConnection/urlconnection.md: -------------------------------------------------------------------------------- 1 | 类图: 2 | 3 | ![URLConnection类图](images/URLConnection.jpg) 4 | 5 | 我们以urlconnection包的简单对百度首页的读取来开启我们的源码阅读旅程。 6 | 7 | # 连接建立 8 | 9 | 相关源码: 10 | 11 | ```java 12 | //http://www.baidu.com 13 | URL realUrl = new URL(urlName); 14 | URLConnection conn = realUrl.openConnection(); 15 | conn.setRequestProperty("accept", "*/*"); 16 | conn.setRequestProperty("connection", "Keep-Alive"); 17 | conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)"); 18 | // 建立实际的连接 19 | conn.connect(); 20 | ``` 21 | 22 | ## URL构建 23 | 24 | URL负责协议的解析以及相应的协议处理器的创建,协议解析的过程其实就是一个字符串的处理过程。协议处理器即URLStreamHandler,类图: 25 | 26 | ![URLStreamHandler类图](images/URLStreamHandler.jpg) 27 | 28 | 其子类如下图所示: 29 | 30 | ![URLStreamHandler子类](images/URLStreamHandler子类.png) 31 | 32 | 每种协议的处理器便在sun.net.www.protocol.xxx中,URL获取处理器就是一个手动拼接类名,用反射生成实例的过程。 33 | 34 | ## 创建连接对象 35 | 36 | URL.openConnection: 37 | 38 | ```java 39 | public URLConnection openConnection() { 40 | return handler.openConnection(this); 41 | } 42 | ``` 43 | 44 | 注意此处并未执行真正的连接操作,从上一节可以看出,这里的Handler应该是sun.net.www.protocol.http.Handler: 45 | 46 | ```java 47 | protected java.net.URLConnection openConnection(URL u, Proxy p) { 48 | //p为空 49 | return new HttpURLConnection(u, p, this); 50 | } 51 | ``` 52 | 53 | 这里的HttpURLConnection同样位于sun.net.www.protocol.http包下,构造器源码: 54 | 55 | ```java 56 | protected HttpURLConnection(URL u, Proxy p, Handler handler) { 57 | super(u); 58 | requests = new MessageHeader(); 59 | responses = new MessageHeader(); 60 | userHeaders = new MessageHeader(); 61 | this.handler = handler; 62 | instProxy = p; 63 | if (instProxy instanceof sun.net.ApplicationProxy) { 64 | /* Application set Proxies should not have access to cookies 65 | * in a secure environment unless explicitly allowed. */ 66 | try { 67 | cookieHandler = CookieHandler.getDefault(); 68 | } catch (SecurityException se) { /* swallow exception */ } 69 | } else { 70 | cookieHandler = java.security.AccessController.doPrivileged( 71 | new java.security.PrivilegedAction() { 72 | public CookieHandler run() { 73 | return CookieHandler.getDefault(); 74 | } 75 | }); 76 | } 77 | cacheHandler = java.security.AccessController.doPrivileged( 78 | new java.security.PrivilegedAction() { 79 | public ResponseCache run() { 80 | return ResponseCache.getDefault(); 81 | } 82 | }); 83 | } 84 | ``` 85 | 86 | ### 消息头 87 | 88 | 可以看出,这里使用MessageHeader作为请求响应头的保存,解析载体,其类图: 89 | 90 | ![MessageHeader类图](images/MessageHeader.jpg) 91 | 92 | ### Cookie处理器 93 | 94 | CookieHandler实现了Cookie语义,类图: 95 | 96 | ![CookieHandler类图](images/CookieHandler.jpg) 97 | 98 | 不过因为默认并没有默认的CookieHandler可用,所以构造器里的cookieHandler为null。 99 | 100 | ### 缓存 101 | 102 | ResponseCache实现了缓存的语义,类图: 103 | 104 | ![ResponseCache类图](images/ResponseCache.jpg) 105 | 106 | cacheHandler属性同样为空。 107 | 108 | ## 请求头设置 109 | 110 | URLConnection.setRequestProperty: 111 | 112 | ```java 113 | public void setRequestProperty(String key, String value) { 114 | if (connected) 115 | throw new IllegalStateException("Already connected"); 116 | if (key == null) 117 | throw new NullPointerException ("key is null"); 118 | if (requests == null) 119 | requests = new MessageHeader(); 120 | requests.set(key, value); 121 | } 122 | ``` 123 | 124 | 很明显是委托给MessageHeader实现的,从其类图可以看出,MessageHeader内部其实由key和value数组组成,这里的添加便是有则更新,没有则添加的过程。 125 | 126 | ## 连接 127 | 128 | 连接的过程为创建一个HttpClient对象,在其构造器中完成连接,类图: 129 | 130 | ![HttpClient类图](images/HttpClient.jpg) 131 | 132 | HttpClient由HttpURLConnection的getNewHttpClient完成构造: 133 | 134 | ```java 135 | protected HttpClient getNewHttpClient(URL url, Proxy p, int connectTimeout) { 136 | return HttpClient.New(url, p, connectTimeout, this); 137 | } 138 | ``` 139 | 140 | 如果没有设置代理,那么p自然是空的,connectTimeout默认为-1,即永不超时。HttpClient构造器源码: 141 | 142 | ```java 143 | protected HttpClient(URL url, Proxy p, int to) { 144 | proxy = (p == null) ? Proxy.NO_PROXY : p; 145 | this.host = url.getHost(); 146 | this.url = url; 147 | port = url.getPort(); 148 | if (port == -1) { 149 | //默认就是80 150 | port = getDefaultPort(); 151 | } 152 | setConnectTimeout(to); 153 | capture = HttpCapture.getCapture(url); 154 | openServer(); 155 | } 156 | ``` 157 | 158 | openServer方法实现: 159 | 160 | ```java 161 | @Override 162 | public void openServer(String server, int port) throws IOException { 163 | serverSocket = doConnect(server, port); 164 | try { 165 | OutputStream out = serverSocket.getOutputStream(); 166 | if (capture != null) { 167 | out = new HttpCaptureOutputStream(out, capture); 168 | } 169 | serverOutput = new PrintStream(new BufferedOutputStream(out),false, encoding); 170 | } catch (UnsupportedEncodingException e) { 171 | throw new InternalError(encoding+" encoding not found", e); 172 | } 173 | serverSocket.setTcpNoDelay(true); 174 | } 175 | ``` 176 | 177 | doConnect方法所做的便是建立Socket连接,如果设置启用网络抓包,那么将SocketOutputStream包装为HttpCaptureOutputStream,什么是Java的抓包呢? 178 | 179 | 其实就是将URLConnectiont通信的请求和相应原封不动的保存到文件中。抓包通过VM参数`-Dsun.net.http.captureRules=data/capture.rules`启用,参数指向的是一个规则文件,每条规则占据一行,规则示例 : 180 | 181 | ```tex 182 | www\.baidu\.com , baidu%d.log 183 | ``` 184 | 185 | 表示对域名baidu.com进行抓包,抓到的包保存在工程下的格式为baidu%d.log的文件中。 186 | 187 | Java中用类HttpCapture解析,存储抓包规则,类图: 188 | 189 | ![HttpCapture类图](images/HttpCapture.jpg) 190 | 191 | HttpClient构造器中的: 192 | 193 | ```java 194 | capture = HttpCapture.getCapture(url); 195 | ``` 196 | 197 | 便用于检测是否适用于当前域名的规则: 198 | 199 | ```java 200 | public static HttpCapture getCapture(java.net.URL url) { 201 | //读取sun.net.http.captureRules指向的文件并解析 202 | if (!isInitialized()) { 203 | init(); 204 | } 205 | if (patterns == null || patterns.isEmpty()) { 206 | return null; 207 | } 208 | String s = url.toString(); 209 | for (int i = 0; i < patterns.size(); i++) { 210 | Pattern p = patterns.get(i); 211 | if (p.matcher(s).find()) { 212 | String f = capFiles.get(i); 213 | File fi; 214 | if (f.indexOf("%d") >= 0) { 215 | java.util.Random rand = new java.util.Random(); 216 | do { 217 | //用随机数替代%d 218 | String f2 = f.replace("%d", Integer.toString(rand.nextInt())); 219 | fi = new File(f2); 220 | } while (fi.exists()); 221 | } else { 222 | fi = new File(f); 223 | } 224 | return new HttpCapture(fi, url); 225 | } 226 | } 227 | return null; 228 | } 229 | ``` 230 | 231 | HttpCaptureOutputStream继承自io包的FilterOutputStream,作用很简单,就是**将发送的每一个字节转发给HttpCapture,由后者完成到文件的写入**。 232 | 233 | ### PrintStream 234 | 235 | 输出流最终被包装成了PrintStream,此类在io包并未进行说明。类图: 236 | 237 | ![PrintStream类图](images/PrintStream.jpg) 238 | 239 | 此类的特点可总结如下: 240 | 241 | - 如果写入出错,并不会抛出异常,而是将内部的trouble属性设为true,我们可以通过checkError方法进行检测是否出错。 242 | 243 | - 从类图中可以看出,虽然这是一个输出流,但却是用Writer实现的!此类对要写入的数据进行了平台相关的编码工作,最终写出的其实是字符!之所以这么实现需要结合JDK的历史(jdk1.0加入)进行考量,那时候还没有Writer接口(jdk1.1加入,正是为了修正这个逻辑问题),这也是为什么System.out是个PrintStream。 244 | 245 | 关于其写的是字符这一点可从源码中得到证明: 246 | 247 | ```java 248 | public void print(int i) { 249 | write(String.valueOf(i)); 250 | } 251 | ``` 252 | 253 | - 自动刷新特性: 254 | 255 | - 当println方法被调用。 256 | - autoFlush设为true时,如果检测到字符串中含有'\n',刷新。 257 | - autoFlush设为true时,write(String str)方法也会导致刷新。 258 | 259 | PrintWriter和PrintStream的实现方式以及API几乎完全一致,除了不会自动检测换行符并刷新。关于两者的黑历史参考: 260 | 261 | [PrintStream vs PrintWriter](http://stackoverflow.com/questions/11372546/printstream-vs-printwriter) 262 | 263 | # 请求发送 264 | 265 | 当与远程URL的连接建立后并不会马上发送请求,而是**等到需要获取响应时**。我们以获取全部响应头为例,sun.net.www.protocol.http.HttpURLConnection.getHeaderFields: 266 | 267 | ```java 268 | @Override 269 | public Map> getHeaderFields() { 270 | try { 271 | getInputStream(); 272 | } catch (IOException e) {} 273 | return getFilteredHeaderFields(); 274 | } 275 | ``` 276 | 277 | getInputStream方法调用了写请求writeRequests方法。最终实现位于HttpClient.writeRequests: 278 | 279 | ```java 280 | public void writeRequests(MessageHeader head,PosterOutputStream pos) { 281 | requests = head; 282 | requests.print(serverOutput); 283 | serverOutput.flush(); 284 | } 285 | ``` 286 | 287 | MessageHeader.print: 288 | 289 | ```java 290 | public synchronized void print(PrintStream p) { 291 | for (int i = 0; i < nkeys; i++) 292 | if (keys[i] != null) { 293 | p.print(keys[i] + 294 | (values[i] != null ? ": "+values[i]: "") + "\r\n"); 295 | } 296 | p.print("\r\n"); 297 | p.flush(); 298 | } 299 | ``` 300 | 301 | 一目了然。 302 | 303 | # 响应解析 304 | 305 | 其实就是获得输入流逐行解析的过程,不再向下展开。 306 | 307 | # DNS解析 308 | 309 | 触发DNS解析的时机是HttpClient的New方法,默认的实现是Inet4AddressImpl的lookupAllHostAddr方法: 310 | 311 | ```java 312 | public native InetAddress[] 313 | lookupAllHostAddr(String hostname) throws UnknownHostException; 314 | ``` 315 | 316 | native实现其实调用的是Linux的**getaddrinfo系统**调用,当然JDK在java层面也有对解析结果的缓存。 317 | 318 | 如何查看Linux的DNS服务器地址呢? 319 | 320 | - 配置文件 321 | 322 | ```shell 323 | cat /etc/resolv.conf 324 | ``` 325 | 326 | 结果如下: 327 | 328 | ```html 329 | nameserver 10.0.0.2 330 | ``` 331 | 332 | - nslookup: 333 | 334 | ```shell 335 | nslookup baidu.com 336 | ``` 337 | 338 | 结果: 339 | 340 | ```html 341 | Server: 10.0.0.2 342 | Address: 10.0.0.2#53 343 | 344 | Non-authoritative answer: 345 | Name: baidu.com 346 | Address: 220.181.57.216 347 | Name: baidu.com 348 | Address: 123.125.115.110 349 | ``` 350 | 351 | 所以DNS便是10.0.0.2 352 | 353 | # keep-alive 354 | 355 | 在创建连接时,源码位于sun.net.www.http.New方法,省略版本: 356 | 357 | ```java 358 | public static HttpClient New(URL url, Proxy p, int to, boolean useCache, 359 | HttpURLConnection httpuc) throws IOException { 360 | HttpClient ret = null; 361 | /* see if one's already around */ 362 | if (useCache) { 363 | ret = kac.get(url, null); 364 | } 365 | // ... 366 | } 367 | ``` 368 | 369 | kac是connection的缓存,定义在HttpClient类中: 370 | 371 | ```java 372 | /* where we cache currently open, persistent connections */ 373 | protected static KeepAliveCache kac = new KeepAliveCache(); 374 | ``` 375 | 376 | KeepAliveCache的定义如下: 377 | 378 | ```java 379 | /** 380 | * A class that implements a cache of idle Http connections for keep-alive 381 | * 382 | * @author Stephen R. Pietrowicz (NCSA) 383 | * @author Dave Brown 384 | */ 385 | public class KeepAliveCache 386 | extends HashMap 387 | implements Runnable { 388 | 389 | static final int MAX_CONNECTIONS = 5; 390 | static int result = -1; 391 | static int getMaxConnections() { 392 | if (result == -1) { 393 | result = AccessController.doPrivileged( 394 | new GetIntegerAction("http.maxConnections", MAX_CONNECTIONS)) 395 | .intValue(); 396 | if (result <= 0) { 397 | result = MAX_CONNECTIONS; 398 | } 399 | } 400 | return result; 401 | } 402 | 403 | } 404 | ``` 405 | 406 | `getMaxConnections`指的是能够缓存的最大的连接数,如果不指定,默认是5. 那么缓存的链接的有效期是多少呢? 407 | 408 | 在KeepAliveCache内有一个静态变量: 409 | 410 | ```java 411 | static final int LIFETIME = 5000; 412 | ``` 413 | 414 | 这个是**过期检查线程运行的时间间隔(毫秒)**。此线程初始化: 415 | 416 | ```java 417 | AccessController.doPrivileged(new PrivilegedAction<>() { 418 | public Void run() { 419 | keepAliveTimer = InnocuousThread.newSystemThread("Keep-Alive-Timer", cache); 420 | keepAliveTimer.setDaemon(true); 421 | keepAliveTimer.setPriority(Thread.MAX_PRIORITY - 2); 422 | keepAliveTimer.start(); 423 | return null; 424 | } 425 | }); 426 | ``` 427 | 428 | 运行的核心逻辑: 429 | 430 | ```java 431 | @Override 432 | public void run() { 433 | do { 434 | try { 435 | Thread.sleep(LIFETIME); 436 | } catch (InterruptedException e) {} 437 | 438 | // Remove all outdated HttpClients. 439 | synchronized (this) { 440 | long currentTime = System.currentTimeMillis(); 441 | List keysToRemove = new ArrayList<>(); 442 | 443 | for (KeepAliveKey key : keySet()) { 444 | ClientVector v = get(key); 445 | synchronized (v) { 446 | KeepAliveEntry e = v.peek(); 447 | while (e != null) { 448 | if ((currentTime - e.idleStartTime) > v.nap) { 449 | v.poll(); 450 | e.hc.closeServer(); 451 | } else { 452 | break; 453 | } 454 | e = v.peek(); 455 | } 456 | 457 | if (v.isEmpty()) { 458 | keysToRemove.add(key); 459 | } 460 | } 461 | } 462 | 463 | for (KeepAliveKey key : keysToRemove) { 464 | removeVector(key); 465 | } 466 | } 467 | } while (!isEmpty()); 468 | } 469 | ``` 470 | 471 | 一个缓存的连接的有效期在KeepAliveCache.put时确定: 472 | 473 | ```java 474 | /** 475 | * Register this URL and HttpClient (that supports keep-alive) with the cache 476 | * @param url The URL contains info about the host and port 477 | * @param http The HttpClient to be cached 478 | */ 479 | public synchronized void put(final URL url, Object obj, HttpClient http) { 480 | if (v == null) { 481 | int keepAliveTimeout = http.getKeepAliveTimeout(); 482 | v = new ClientVector(keepAliveTimeout > 0 ? 483 | keepAliveTimeout * 1000 : LIFETIME); 484 | v.put(http); 485 | super.put(key, v); 486 | } else { 487 | v.put(http); 488 | } 489 | } 490 | ``` 491 | 492 | `http.getKeepAliveTimeout()`取的实际上是环境变量`http.keepAlive`的值。 493 | 494 | 最后还有一个问题,连接是在什么时机被放进缓存的? 495 | 496 | 在HttpURLConnection场景下是其`getInputStream`方法。 497 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | skywalker 8 | JDK 9 | 1.0-SNAPSHOT 10 | 11 | 12 | 13 | org.apache.maven.plugins 14 | maven-compiler-plugin 15 | 16 | 1.8 17 | 1.8 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | junit 26 | junit 27 | 4.13.1 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/main/java/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Main-Class: nio.Client 3 | 4 | -------------------------------------------------------------------------------- /src/main/java/buffer/BufferTest.java: -------------------------------------------------------------------------------- 1 | package buffer; 2 | 3 | import java.nio.ByteBuffer; 4 | 5 | /** 6 | * @author skywalker 7 | */ 8 | public class BufferTest { 9 | 10 | public static void main(String[] args) { 11 | ByteBuffer buffer = ByteBuffer.allocateDirect(4100); 12 | System.out.println(buffer.remaining()); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/condition/Demo.java: -------------------------------------------------------------------------------- 1 | package condition; 2 | 3 | import java.util.concurrent.locks.Condition; 4 | import java.util.concurrent.locks.Lock; 5 | import java.util.concurrent.locks.ReentrantLock; 6 | 7 | public class Demo { 8 | 9 | private final Lock lock = new ReentrantLock(); 10 | private final Condition condition = lock.newCondition(); 11 | 12 | private class T implements Runnable { 13 | @Override 14 | public void run() { 15 | try { 16 | lock.lock(); 17 | try { 18 | System.out.println("开始等待"); 19 | condition.await(); 20 | System.out.println(Thread.currentThread().isInterrupted()); 21 | } finally { 22 | lock.unlock(); 23 | } 24 | } catch (InterruptedException e) { 25 | e.printStackTrace(); 26 | } 27 | } 28 | 29 | } 30 | 31 | public void signal() { 32 | lock.lock(); 33 | try { 34 | System.out.println("获得锁"); 35 | condition.signalAll(); 36 | } finally { 37 | lock.unlock(); 38 | } 39 | } 40 | 41 | public static void main(String[] args) throws InterruptedException { 42 | Demo demo = new Demo(); 43 | new Thread(demo.new T()).start(); 44 | Thread.sleep(2000); 45 | demo.signal(); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/file/FileTest.java: -------------------------------------------------------------------------------- 1 | package file; 2 | 3 | import java.io.*; 4 | import java.nio.channels.FileChannel; 5 | import java.nio.channels.FileLock; 6 | 7 | /** 8 | * 文件相关测试. 9 | * 10 | * @author skywalker 11 | */ 12 | public class FileTest { 13 | 14 | public static void main(String[] args) throws IOException { 15 | File file = new File("test"); 16 | FileChannel channel = new RandomAccessFile(file, "rw").getChannel(); 17 | new Thread(new GETLock(channel, 0, 2)).start(); 18 | new Thread(new GETLock(channel, 1, 3)).start(); 19 | //channel.close(); 20 | } 21 | 22 | private static class GETLock implements Runnable { 23 | 24 | private final FileChannel channel; 25 | private final int start; 26 | private final int end; 27 | 28 | private GETLock(FileChannel channel, int start, int end) { 29 | this.channel = channel; 30 | this.start = start; 31 | this.end = end; 32 | } 33 | 34 | @Override 35 | public void run() { 36 | try { 37 | FileLock lock = channel.lock(start, end, true); 38 | System.out.println(Thread.currentThread().getName() + "获得锁"); 39 | Thread.sleep(2000); 40 | lock.release(); 41 | } catch (IOException e) { 42 | e.printStackTrace(); 43 | } catch (InterruptedException e) { 44 | e.printStackTrace(); 45 | } 46 | } 47 | 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/nio/Client.java: -------------------------------------------------------------------------------- 1 | package nio; 2 | 3 | import org.junit.Test; 4 | 5 | import java.io.IOException; 6 | import java.net.InetSocketAddress; 7 | import java.net.Socket; 8 | import java.nio.ByteBuffer; 9 | import java.nio.channels.SocketChannel; 10 | 11 | /** 12 | * NIO Client. 13 | * 14 | * @author skywalker 15 | */ 16 | public class Client { 17 | 18 | /** 19 | * 测试NIO客户端的阻塞表现. 20 | */ 21 | @Test 22 | public void nioRead() throws IOException { 23 | SocketChannel channel = SocketChannel.open(); 24 | channel.configureBlocking(true); 25 | 26 | channel.connect(new InetSocketAddress("192.168.80.128", 10010)); 27 | while (channel.isConnectionPending()) { 28 | channel.finishConnect(); 29 | } 30 | 31 | ByteBuffer buffer = ByteBuffer.allocate(10); 32 | int read = channel.read(buffer); 33 | 34 | System.out.println(read); 35 | } 36 | 37 | /** 38 | * 测试阻塞/非阻塞的写. 39 | */ 40 | public static void main(String[] args) throws IOException { 41 | /*SocketChannel channel = SocketChannel.open(); 42 | channel.configureBlocking(true); 43 | channel.connect(new InetSocketAddress("192.168.80.128", 10010)); 44 | while (channel.isConnectionPending()) { 45 | channel.finishConnect(); 46 | } 47 | byte[] dirty = new byte[163832]; 48 | byte[] data = new byte[10]; 49 | Arrays.fill(dirty, (byte) 'a'); 50 | Arrays.fill(data, (byte) 'a'); 51 | //脏数据 52 | channel.write(ByteBuffer.wrap(dirty)); 53 | int writed = channel.write(ByteBuffer.wrap(data)); 54 | System.out.println(writed);*/ 55 | Socket socket = new Socket(); 56 | socket.connect(new InetSocketAddress("192.168.80.128", 8080)); 57 | try { 58 | Thread.sleep(90000); 59 | } catch (InterruptedException e) { 60 | e.printStackTrace(); 61 | } 62 | socket.close(); 63 | } 64 | 65 | @Test 66 | public void connectLocalHost() throws IOException, InterruptedException { 67 | Socket socket = new Socket(); 68 | socket.connect(new InetSocketAddress("192.168.80.128", 8080)); 69 | Thread.sleep(90000); 70 | socket.close(); 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/nio/Server.java: -------------------------------------------------------------------------------- 1 | package nio; 2 | 3 | import java.io.IOException; 4 | import java.net.InetSocketAddress; 5 | import java.nio.channels.SelectionKey; 6 | import java.nio.channels.Selector; 7 | import java.nio.channels.ServerSocketChannel; 8 | import java.nio.channels.SocketChannel; 9 | import java.util.Iterator; 10 | import java.util.Set; 11 | 12 | /** 13 | * NIO server. 14 | * 15 | * @author skywalker 16 | */ 17 | public class Server { 18 | 19 | public static void main(String[] args) throws IOException { 20 | Selector selector = Selector.open(); 21 | ServerSocketChannel channel = ServerSocketChannel.open(); 22 | 23 | channel.socket().bind(new InetSocketAddress(8080)); 24 | // none-blocking mode 25 | channel.configureBlocking(false); 26 | // register accept event 27 | channel.register(selector, SelectionKey.OP_ACCEPT); 28 | 29 | while (selector.select() != 0) { 30 | 31 | Set keys = selector.selectedKeys(); 32 | Iterator iterator = keys.iterator(); 33 | 34 | SelectionKey key; 35 | while (iterator.hasNext()) { 36 | key = iterator.next(); 37 | 38 | if (key.isAcceptable()) { 39 | SocketChannel client = channel.accept(); 40 | System.out.println("Client connected: " + client.getRemoteAddress()); 41 | client.configureBlocking(false); 42 | client.register(selector, SelectionKey.OP_READ); 43 | } 44 | 45 | if (key.isWritable()) 46 | System.out.println("writable."); 47 | 48 | if (key.isReadable()) 49 | System.out.println("readable"); 50 | 51 | iterator.remove(); 52 | } 53 | } 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/process/ProcessTest.java: -------------------------------------------------------------------------------- 1 | package process; 2 | 3 | import org.junit.Test; 4 | 5 | import java.io.IOException; 6 | 7 | /** 8 | * {@link Process}测试. 9 | * 10 | * @author skywalker 11 | */ 12 | public class ProcessTest { 13 | 14 | @Test 15 | public void ls() throws IOException, InterruptedException { 16 | Runtime runtime = Runtime.getRuntime(); 17 | String cmd = "ls -l"; 18 | Process process = runtime.exec(cmd); 19 | int value = process.waitFor(); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/socket/Client.java: -------------------------------------------------------------------------------- 1 | package socket; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.net.InetSocketAddress; 6 | import java.net.Socket; 7 | 8 | /** 9 | * 客户端连接. 10 | * 11 | * @author skywalker 12 | */ 13 | public class Client { 14 | 15 | public static void main(String[] args) throws IOException { 16 | Socket socket = new Socket(); 17 | socket.setTcpNoDelay(true); 18 | socket.connect(new InetSocketAddress("www.baidu.com", 80)); 19 | InputStream is = socket.getInputStream(); 20 | byte[] data = new byte[8]; 21 | is.read(data, 0, 8); 22 | System.out.println(new String(data)); 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /src/main/java/socket/Server.java: -------------------------------------------------------------------------------- 1 | package socket; 2 | 3 | import java.io.IOException; 4 | import java.net.InetSocketAddress; 5 | import java.net.ServerSocket; 6 | import java.net.Socket; 7 | 8 | /** 9 | * Server. 10 | * 11 | * @author skywalker 12 | */ 13 | public class Server { 14 | 15 | public static void main(String[] args) throws IOException { 16 | ServerSocket ss = new ServerSocket(); 17 | ss.bind(new InetSocketAddress(8080)); 18 | Socket socket = ss.accept(); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/test/Test.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import java.text.ParseException; 4 | import java.util.concurrent.*; 5 | 6 | /** 7 | * Test something. 8 | * 9 | * @author skywalker 10 | */ 11 | public class Test { 12 | 13 | /** 14 | * Test {@link Integer#numberOfLeadingZeros(int)}. 15 | */ 16 | @org.junit.Test 17 | public void leadingZeroes() throws ParseException { 18 | System.out.println(Integer.numberOfLeadingZeros(16)); 19 | System.out.println(146 / 95.6666666667 >= 1.5); 20 | } 21 | 22 | @org.junit.Test 23 | public void linkedQueue() { 24 | SomeQueue queue = new SomeQueue<>(); 25 | queue.offer("a"); 26 | System.out.println(queue.poll()); 27 | } 28 | 29 | @org.junit.Test 30 | public void threadPool() throws InterruptedException { 31 | ThreadPoolExecutor service = (ThreadPoolExecutor) Executors.newFixedThreadPool(1); 32 | service.execute(() -> { 33 | throw new RuntimeException(); 34 | }); 35 | Thread.sleep(2000); 36 | System.out.println(service.getPoolSize()); 37 | } 38 | 39 | @org.junit.Test 40 | public void maxPoolSize() throws InterruptedException { 41 | ThreadPoolExecutor service = (ThreadPoolExecutor) new ThreadPoolExecutor(1, 3, 0, TimeUnit.SECONDS, new ArrayBlockingQueue(1)); 42 | service.execute(new StupidTask(1)); 43 | service.execute(new StupidTask(2)); 44 | service.execute(new StupidTask(3)); 45 | Thread.sleep(7000); 46 | System.out.println(service.getPoolSize()); 47 | System.out.println(service.getLargestPoolSize()); 48 | } 49 | 50 | @org.junit.Test 51 | public void threadLocal() { 52 | ThreadLocal local = ThreadLocal.withInitial(() -> "hello"); 53 | } 54 | 55 | private class StupidTask implements Runnable { 56 | 57 | private final int id; 58 | 59 | private StupidTask(int id) { 60 | this.id = id; 61 | } 62 | 63 | @Override 64 | public void run() { 65 | System.out.println("hello" + id + ": " + Thread.currentThread().getName()); 66 | try { 67 | Thread.sleep(5000); 68 | } catch (InterruptedException e) { 69 | e.printStackTrace(); 70 | } 71 | } 72 | } 73 | 74 | /** 75 | * 如果我们向线程池submit的任务尚未被执行,同时有线程阻塞在{@link FutureTask#get()}方法上,那么当 76 | * {@link ThreadPoolExecutor#shutdownNow()}方法调用时,阻塞的线程会被唤醒吗? 77 | *

答案是不能.

78 | */ 79 | @org.junit.Test 80 | public void canWakeUp() throws InterruptedException, ExecutionException { 81 | ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(1); 82 | executor.execute(new StupidTask(1)); 83 | Future future = executor.submit(() -> "hello"); 84 | Thread.sleep(3000); 85 | executor.shutdownNow(); 86 | future.get(); 87 | System.out.println("被唤醒"); 88 | } 89 | 90 | private boolean flag = true; 91 | 92 | @org.junit.Test 93 | public void testVolatile() { 94 | new Thread(() -> { 95 | try { 96 | System.out.println("子线程启动"); 97 | TimeUnit.SECONDS.sleep(3); 98 | flag = false; 99 | System.out.println("flag false"); 100 | } catch (InterruptedException ignore) { 101 | } 102 | }).start(); 103 | 104 | while (flag) { 105 | System.out.print(1); 106 | } 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/udp/Client.java: -------------------------------------------------------------------------------- 1 | package udp; 2 | 3 | import java.io.IOException; 4 | import java.net.*; 5 | 6 | /** 7 | * UDP客户端. 8 | * 9 | * @author skywalker 10 | */ 11 | public class Client { 12 | 13 | public static void main(String[] args) throws IOException { 14 | DatagramSocket client = new DatagramSocket(); 15 | 16 | String sendStr = "Hello! I'm Client"; 17 | byte[] sendBuf; 18 | sendBuf = sendStr.getBytes(); 19 | InetAddress addr = InetAddress.getByName("127.0.0.1"); 20 | int port = 8080; 21 | DatagramPacket sendPacket 22 | = new DatagramPacket(sendBuf ,sendBuf.length , addr , port); 23 | client.send(sendPacket); 24 | byte[] recvBuf = new byte[100]; 25 | DatagramPacket recvPacket 26 | = new DatagramPacket(recvBuf , recvBuf.length); 27 | client.receive(recvPacket); 28 | String recvStr = new String(recvPacket.getData() , 0 ,recvPacket.getLength()); 29 | System.out.println("收到:" + recvStr); 30 | client.close(); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/udp/Server.java: -------------------------------------------------------------------------------- 1 | package udp; 2 | 3 | import java.io.IOException; 4 | import java.net.DatagramPacket; 5 | import java.net.DatagramSocket; 6 | import java.net.InetAddress; 7 | import java.net.SocketException; 8 | 9 | /** 10 | * UDP服务器. 11 | * 12 | * @author skywalker 13 | */ 14 | public class Server { 15 | 16 | public static void main(String[] args) throws IOException { 17 | DatagramSocket server = new DatagramSocket(8080); 18 | 19 | byte[] buffer = new byte[1024]; 20 | DatagramPacket packet = new DatagramPacket(buffer, 1024); 21 | 22 | server.receive(packet); 23 | 24 | System.out.println("Server接收到: " + new String(buffer, 0, packet.getLength())); 25 | 26 | //向客户端返回 27 | int port = packet.getPort(); 28 | InetAddress address = packet.getAddress(); 29 | byte[] data = "hello client".getBytes(); 30 | DatagramPacket sendPacket = new DatagramPacket(data, data.length, address, port); 31 | server.send(sendPacket); 32 | 33 | server.close(); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/urlconnection/Test.java: -------------------------------------------------------------------------------- 1 | package urlconnection; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStreamReader; 6 | import java.net.URL; 7 | import java.net.URLConnection; 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | /** 12 | * {@link java.net.URLConnection}例子. 13 | * 14 | * @author skywalker 15 | */ 16 | public class Test { 17 | 18 | public static void main(String[] args) { 19 | System.out.println(sendGet("http://www.baidu.com", "")); 20 | } 21 | 22 | public static String sendGet(String url, String param) { 23 | String result = ""; 24 | BufferedReader in = null; 25 | try { 26 | String urlName = url + "?" + param; 27 | URL realUrl = new URL(urlName); 28 | // 打开和URL之间的连接 29 | URLConnection conn = realUrl.openConnection(); 30 | // 设置通用的请求属性 31 | conn.setRequestProperty("accept", "*/*"); 32 | conn.setRequestProperty("connection", "Keep-Alive"); 33 | conn.setRequestProperty("user-agent", 34 | "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)"); 35 | // 建立实际的连接 36 | conn.connect(); 37 | // 获取所有响应头字段 38 | Map> map = conn.getHeaderFields(); 39 | // 遍历所有的响应头字段 40 | for (String key : map.keySet()) { 41 | System.out.println(key + "--->" + map.get(key)); 42 | } 43 | // 定义BufferedReader输入流来读取URL的响应 44 | in = new BufferedReader( 45 | new InputStreamReader(conn.getInputStream())); 46 | String line; 47 | while ((line = in.readLine()) != null) { 48 | result += "\n" + line; 49 | } 50 | } catch (Exception e) { 51 | System.out.println("发送GET请求出现异常!" + e); 52 | e.printStackTrace(); 53 | } 54 | // 使用finally块来关闭输入流 55 | finally { 56 | try { 57 | if (in != null) { 58 | in.close(); 59 | } 60 | } catch (IOException ex) { 61 | ex.printStackTrace(); 62 | } 63 | } 64 | return result; 65 | } 66 | 67 | } 68 | --------------------------------------------------------------------------------