├── J.U.C_2.png ├── JVM数据存储介绍及性能优化.md ├── Java volatile 关键字.md ├── Java volatile 关键字.zip ├── Java8并发教程:Threads和Executors.md ├── Java中的ThreadLocal.md ├── Java中的读写锁.md ├── Java中的锁.md ├── Java内存模型.md ├── Java同步代码块.md ├── Java多线程详解.pdf ├── Java并发.md ├── Java并发编程教程.pdf ├── README.md ├── Slipped Conditions.md ├── jvm ├── Introduction to Class File Format & Byte Code.md ├── JVM Internals - Garbage Collection & Runtime Optimizations.md ├── JVM Internals - NEJUG Nov 2010.md ├── JVM Mechanics When Does the JVM JIT & Deoptimize.md ├── JVM线程池发展趋势.md ├── Java代码到字节码——第一部分.md └── Understanding Garbage Collection.md ├── 信号量.md ├── 内置监视器失效.md ├── 剖析同步器.md ├── 多线程能够给我们带来什么.md ├── 并发编程实战.pdfx.pdf ├── 数据库连接池 └── DBCP.md ├── 死锁.md ├── 每个程序员必读系列 ├── The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!).md ├── What Every Programmer Should Know about Memory.pdf ├── What Every Programmer Should know about Time.md └── What every web developer must know about URL encoding.md ├── 比较和替换.md ├── 线程信号.md ├── 线程池.md ├── 重入锁死.md ├── 阻塞队列.md ├── 阿姆达尔定律.md ├── 非阻塞算法.md ├── 预防死锁.md └── 饥饿和公平.md /J.U.C_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlankKelly/ConcurrencyNote/4596da2d8592a53c7d60e1df35f67dd0f65b7cae/J.U.C_2.png -------------------------------------------------------------------------------- /JVM数据存储介绍及性能优化.md: -------------------------------------------------------------------------------- 1 | ####JVM数据存储介绍及性能优化 2 | #####JVM内存模式介绍 3 | Java 虚拟机内存模型是 Java 程序运行的基础。为了能使 Java 应用程序正常运行,JVM 虚拟机将其内存数据分为程序计数器、虚拟机栈、本地方法栈、Java 堆和方法区等部分。 4 | 5 | 程序计数器 (Program Counter Register) 6 | 7 | 程序计数器 (Program Counter Register) 是一块很小内存空间,由于 Java 是支持线程的语言,当线程数量超过 CPU 数量时,线程之间根据时间片轮询抢夺 CPU 资源。对于单核 CPU 而言,每一时刻只能有一个线程在运行,而其他线程必须被切换出去。为此,每一个线程都必须用一个独立的程序计数器,用于记录下一条要运行的指令。各个线程之间的计数器互不影响,独立工作,是一块线程独有的内存空间。如果当前线程正在执行一个 Java 方法,则程序计数器记录正在执行的 Java 字节码地址,如果当前线程正在执行一个 Native 方法,则程序计数器为空。 8 | 9 | 虚拟机栈 10 | 11 | 虚拟机栈用于存放函数调用堆栈信息。Java 虚拟机栈也是线程私有的内存空间,它和 Java 线程在同一时间创建,它保存方法的局部变量、部分结果,并参与方法的调用和返回。 12 | Java 虚拟机规范允许 Java 栈的大小是动态的或者是固定的。在 Java 虚拟机规范中定义了两种异常与栈空间有关:StackOverflowError 和 OutOfMemoryError。如果线程在计算过程中,请求的栈深度大于最大可用的栈深度,则抛出 StackOverflowError;如果 Java 栈可以动态扩展,而在扩展栈的过程中没有足够的内存空间来支持栈的发展,则抛出 OutOfMemeoryError。可以使用-Xss 参数来设置栈的大小,栈的大小直接决定了函数调用的可达深度。 13 | 下面的例子展示了一个递归调用的应用。 14 | 15 | 计数器 count 16 | 17 | 记录了递归的层次,这个没有出口的递归函数一定会导致栈溢出。程序则在栈溢出时,打印出栈的当前深度。 18 | 19 | >递归调用显示栈的最大深度 20 | 21 | public class TestStack{ 22 | private int count = 0; 23 | //没有出口的递归函数 24 | public void recursion(){ 25 | count++;//每次调用深度加1 26 | recursion(); 27 | } 28 | 29 | public void testStack(){ 30 | try{ 31 | recursion(); 32 | }catch(Throwale e){ 33 | System.out.println("deep of stack is "+count);//打印栈溢出的深度 34 | e.printStackTrace(); 35 | } 36 | } 37 | 38 | public static void main(String[] args){ 39 | TestStack ts = new TestStack(); 40 | ts.testStack(); 41 | } 42 | 43 | } 44 | 45 | >上述代码运行结果 46 | 47 | java.lang.StackOverflowError 48 | at TestStack.recursion(TestStack.java:7) 49 | at TestStack.recursion(TestStack.java:7) 50 | at TestStack.recursion(TestStack.java:7) 51 | at TestStack.recursion(TestStack.java:7) 52 | at TestStack.recursion(TestStack.java:7) 53 | at TestStack.recursion(TestStack.java:7) 54 | at TestStack.recursion(TestStack.java:7)deep of stack is 9013 55 | 56 | 虚拟机栈在运行时使用一种叫做栈帧的数据结构保存上下文数据。在栈帧中,存放了方法的局部变量表、操作数栈、动态连接方法和返回地址等信息。每一个方法的调用都伴随着栈帧的入栈操作。相应地,方法的返回则表示栈帧的出栈操作。如果方法调用时,方法的参数和局部变量相对较多,那么栈帧中的局部变量表就会比较大,栈帧会膨胀以满足方法调用所需传递的信息。因此,单个方法调用所需的栈空间大小也会比较多。 57 | 函数嵌套调用的次数由栈的大小决定。栈越大,函数嵌套调用次数越多。对一个函数而言,它的参数越多,内部局部变量越多,它的栈帧就越大,其嵌套调用次数就会减少。 58 | 本地方法栈 59 | 本地方法栈和 Java 虚拟机栈的功能很相似,本地方法栈用于存放函数调用堆栈信息。Java 虚拟机栈用于管理 Java 函数的调用,而本地方法栈用于管理本地方法的调用。本地方法并不是用 Java 实现的,而是使用 C 实现的。在 SUN 的 HotSpot 虚拟机中,不区分本地方法栈和虚拟机栈。因此,和虚拟机栈一样,它也会抛出 StackOverflowError 和 OutofMemoryError。 60 | Java 堆 61 | 堆用于存放 Java 程序运行时所需的对象等数据。几乎所有的对象和数组都是在堆中分配空间的。Java 堆分为新生代和老生代两个部分,新生代用于存放刚刚产生的对象和年轻的对象,如果对象一直没有被回收,生存得足够长,老年对象就被移入老年代。新生代又可进一步细分为 eden、survivor space0 和 survivor space1。eden 即对象的出生地,大部分对象刚刚建立时都会被存放在这里。survivor 空间是存放其中的对象至少经历了一次垃圾回收,并得以幸存下来的。如果在幸存区的对象到了指定年龄仍未被回收,则有机会进入老年代 (tenured)。下面例子演示了对象在内存中的分配方式。 62 | 63 | >进行一次新生代GC 64 | 65 | public class TestHeapGC{ 66 | public static void main(String[] args){ 67 | byte[] b1 = new byte[2014*1024/2]; 68 | byte[] b2 = new byte[2014*1024*8]; 69 | b2 = null; 70 | b2 = new byte[1024 * 1024 * 8]; 71 | System.gc(); 72 | } 73 | } 74 | >执行上面代码的虚拟机配置 75 | 76 | -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -Xms40M -Xmx40M -Xmn20M 77 | 78 | >运行输出 79 | 80 | [GC [DefNew: 9031K->661K(18432K), 0.0022784 secs] 9031K->661K(38912K), 81 | 0.0023178 secs] [Times: user=0.02 sys=0.00, real=0.02 secs] 82 | Heap 83 | def new generation total 18432K, used 9508K [0x34810000, 0x35c10000, 0x35c10000) 84 | eden space 16384K, 54% used [0x34810000, 0x350b3e58, 0x35810000) 85 | from space 2048K, 32% used [0x35a10000, 0x35ab5490, 0x35c10000) 86 | to space 2048K, 0% used [0x35810000, 0x35810000, 0x35a10000) 87 | tenured generation total 20480K, used 0K [0x35c10000, 0x37010000, 0x37010000) 88 | the space 20480K, 0% used [0x35c10000, 0x35c10000, 0x35c10200, 0x37010000) 89 | compacting perm gen total 12288K, used 374K [0x37010000, 0x37c10000, 0x3b010000) 90 | the space 12288K, 3% used [0x37010000, 0x3706db10, 0x3706dc00, 0x37c10000) 91 | ro space 10240K, 51% used [0x3b010000, 0x3b543000, 0x3b543000, 0x3ba10000) 92 | rw space 12288K, 55% used [0x3ba10000, 0x3c0ae4f8, 0x3c0ae600, 0x3c610000) 93 | 94 | 95 | 上述输出显示 JVM 在进行多次内存分配的过程中,触发了一次新生代 GC。在这次 GC 中,原本分配在 eden 段的变量 b1 被移动到 from 空间段 (s0)。最后分配的 8MB 内存被分配在 eden 新生代。 96 | 方法区 97 | 方法区用于存放程序的类元数据信息。方法区与堆空间类似,它也是被 JVM 中所有的线程共享的。方法区主要保存的信息是类的元数据。方法区中最为重要的是类的类型信息、常量池、域信息、方法信息。类型信息包括类的完整名称、父类的完整名称、类型修饰符和类型的直接接口类表;常量池包括这个类方法、域等信息所引用的常量信息;域信息包括域名称、域类型和域修饰符;方法信息包括方法名称、返回类型、方法参数、方法修饰符、方法字节码、操作数栈和方法栈帧的局部变量区大小以及异常表。总之,方法区内保持的信息大部分来自于 class 文件,是 Java 应用程序运行必不可少的重要数据。 98 | 在 Hot Spot 虚拟机中,方法区也称为永久区,是一块独立于 Java 堆的内存空间。虽然叫做永久区,但是在永久区中的对象同样也可以被 GC 回收的。只是对于 GC 的表现也和 Java 堆空间略有不同。对永久区 GC 的回收,通常主要从两个方面分析:一是 GC 对永久区常量池的回收;二是永久区对类元数据的回收。Hot Spot 虚拟机对常量池的回收策略是很明确的,只要常量池中的常量没有被任何地方引用,就可以被回收。 99 | 清单 6 所示代码会生成大量 String 对象,并将其加入常量池中。String.intern() 方法的含义是如果常量池中已经存在当前 String,则返回池中的对象,如果常量池中不存在当前 String 对象,则先将 String 加入常量池,并返回池中的对象引用。因此,不停地将 String 对象加入常量池会导致永久区饱和。如果 GC 不能回收永久区的这些常量数据,那么就会抛出 OutofMemoryError 错误。 100 | 101 | >GC收集永久区 102 | 103 | public class permGenGC{ 104 | public static void main(String[] args){ 105 | for(int i = 0; i < Integer.MAX_VALUE; i++){ 106 | String t = String.valueOf(i).intern(); 107 | } 108 | } 109 | } 110 | >执行上面代码的虚拟机配置 111 | 112 | -XX:PermSize=2M -XX:MaxPermSize=4M -XX:+PrintGCDetails 113 | >输出 114 | 115 | [Full GC [Tenured: 0K->149K(10944K), 0.0177107 secs] 3990K->149K(15872K), 116 | [Perm : 4096K->374K(4096K)], 0.0181540 secs] [Times: user=0.02 sys=0.02, real=0.03 secs] 117 | [Full GC [Tenured: 149K->149K(10944K), 0.0165517 secs] 3994K->149K(15936K), 118 | [Perm : 4096K->374K(4096K)], 0.0169260 secs] [Times: user=0.01 sys=0.00, real=0.02 secs] 119 | [Full GC [Tenured: 149K->149K(10944K), 0.0166528 secs] 3876K->149K(15936K), 120 | [Perm : 4096K->374K(4096K)], 0.0170333 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 121 | 122 | 每当常量池饱和时,FULL GC 总能顺利回收常量池数据,确保程序稳定持续进行。 123 | 124 | #####JVM参数调优实例 125 | 126 | 由于 Java 字节码是运行在 JVM 虚拟机上的,同样的字节码使用不同的 JVM 虚拟机参数运行,其性能表现可能各不一样。为了能使系统性能最优,就需要选择使用合适的 JVM 参数运行 Java 应用程序。 127 | 设置最大堆内存 128 | JVM 内存结构分配对 Java 应用程序的性能有较大的影响。 129 | Java 应用程序可以使用的最大堆可以用-Xmx 参数指定。最大堆指的是新生代和老生代的大小之和的最大值,它是 Java 应用程序的堆上限。清单 9 所示代码是在堆上分配空间直到内存溢出。-Xmx 参数的大小不同,将直接决定程序能够走过几个循环,本例配置为-Xmx5M,设置最大堆上限为 5MB。 130 | 131 | >Java堆分配空间 132 | 133 | import java.util.Vector; 134 | 135 | public class maxHeapTest{ 136 | public static void main(String[] args){ 137 | Vector v = new Vector(); 138 | for(int i = 0; i <= 10; i++){ 139 | byte[] b = new byte[1024 * 1024]; 140 | v.add(b); 141 | System.out.println(i + " M is allocated"); 142 | } 143 | 144 | System.out.println("Max memory: "+Runtime.getRuntime().maxMemory()); 145 | } 146 | } 147 | >运行结果 148 | 149 | 0M is allocated 150 | 1M is allocated 151 | 2M is allocated 152 | 3M is allocated 153 | Exception in thread "main" java.lang.OutOfMemoryError: Java heap space 154 | at maxHeapTest.main(maxHeapTest.java:8) 155 | 156 | 此时表明在完成 4MB 数据分配后系统空闲的堆内存大小已经不足 1MB 了。 157 | 设置 GC 新生代区大小 158 | 参数-Xmn 或者用于 Hot Spot 虚拟机中的参数-XX:NewSize(新生代初始大小)、-XX:MaxNewSize 用于设置新生代的大小。设置一个较大的新生代会减小老生代的大小,这个参数对系统性能以及 GC 行为有很大的影响。新生代的大小一般设置为整个堆空间的 1/4 到 1/3 左右。 159 | 以清单 9 的代码为例,若使用 JVM 参数-XX:+PrintGCDetails -Xmx11M -XX:NewSize=2M -XX:MaxNewSize=2M -verbose:gc 运行程序,将新生代的大小减小为 2MB,那么 MinorGC 次数将从 4 次增加到 9 次 (默认情况下是 3.5MB 左右)。 160 | 161 | >运行输出 162 | 163 | [GC [DefNew: 1272K->150K(1856K), 0.0028101 secs] 1272K->1174K(11072K), 164 | 0.0028504 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 165 | [GC [DefNew: 1174K->0K(1856K), 0.0018805 secs] 2198K->2198K(11072K), 166 | 0.0019097 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 167 | clearing.... 168 | [GC [DefNew: 1076K->0K(1856K), 0.0004046 secs] 3274K->2198K(11072K), 169 | 0.0004382 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 170 | [GC [DefNew: 1024K->0K(1856K), 0.0011834 secs] 3222K->3222K(11072K), 171 | 0.0013508 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 172 | [GC [DefNew: 1024K->0K(1856K), 0.0012983 secs] 4246K->4246K(11072K), 173 | 0.0013299 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 174 | clearing.... 175 | [GC [DefNew: 1024K->0K(1856K), 0.0001441 secs] 5270K->4246K(11072K), 176 | 0.0001686 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 177 | [GC [DefNew: 1024K->0K(1856K), 0.0012028 secs] 5270K->5270K(11072K), 178 | 0.0012328 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 179 | [GC [DefNew: 1024K->0K(1856K), 0.0012553 secs] 6294K->6294K(11072K), 180 | 0.0012845 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 181 | clearing.... 182 | [GC [DefNew: 1024K->0K(1856K), 0.0001524 secs] 7318K->6294K(11072K), 183 | 0.0001780 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 184 | Heap 185 | def new generation total 1856K, used 1057K [0x36410000, 0x36610000, 0x36610000) 186 | eden space 1664K, 63% used [0x36410000, 0x365185a0, 0x365b0000) 187 | from space 192K, 0% used [0x365e0000, 0x365e0088, 0x36610000) 188 | to space 192K, 0% used [0x365b0000, 0x365b0000, 0x365e0000) 189 | tenured generation total 9216K, used 6294K [0x36610000, 0x36f10000, 0x37010000) 190 | the space 9216K, 68% used [0x36610000, 0x36c35868, 0x36c35a00, 0x36f10000) 191 | compacting perm gen total 12288K, used 375K [0x37010000, 0x37c10000, 0x3b010000) 192 | the space 12288K, 3% used [0x37010000, 0x3706dc88, 0x3706de00, 0x37c10000) 193 | ro space 10240K, 51% used [0x3b010000, 0x3b543000, 0x3b543000, 0x3ba10000) 194 | rw space 12288K, 55% used [0x3ba10000, 0x3c0ae4f8, 0x3c0ae600, 0x3c610000) 195 | 196 | 设置持久代大小 197 | 持久代 (方法区) 不属于堆的一部分。在 Hot Spot 虚拟机中,使用-XX:MaxPermSize 参数可以设置持久代的最大值,使用-XX:PermSize 可以设置持久代的初始大小。持久代的大小直接决定了系统可以支持多少个类定义和多少常量。对于使用 CGLIB 或者 Javassit 等动态字节码生成工具的应用程序而言,设置合理的持久代大小有助于维持系统稳定。系统所支持的最大类与 MaxPermSize 成正比。一般来说,MaxPermSize 设置为 64MB 已经可以满足绝大部分应用程序正常工作。如果依然出现永久区溢出,可以设置为 128MB。这是两个很常用的永久区取值。如果 128MB 依然不能满足应用程序需求,那么对于大部分应用程序来说,则应该考虑优化系统的设计,减少动态类的产生,或者利用 GC 回收部分驻扎在永久区的无用类信息,以使系统健康运行。 198 | 设置线程栈大小 199 | 线程栈是线程的一块私有空间。在 JVM 中可以使用-Xss 参数设置线程栈的大小。 200 | 在线程中进行局部变量分配,函数调用时都需要在栈中开辟空间。如果栈的空间分配太小,那么线程在运行时可能没有足够的空间分配局部变量或者达不到足够的函数调用深度,导致程序异常退出;如果栈空间过大,那么开设线程所需的内存成本就会上升,系统所能支持的线程总数就会下降。由于 Java 堆也是向操作系统申请内存空间的,因此,如果堆空间过大,就会导致操作系统可用于线程栈的内存减少,从而间接减少程序所能支持的线程数量。 201 | 202 | >尝试开启尽可能多的线程 203 | 204 | public class TestXss{ 205 | public static class MyThread extends Thread{ 206 | @Override 207 | public void run(){ 208 | try{ 209 | Thread.sleep(10000); 210 | }catch(InterruptedException e){ 211 | e.printStackTrace(); 212 | } 213 | } 214 | } 215 | 216 | public static void main(String[] args){ 217 | int count = 0; 218 | 219 | try{ 220 | for(int i = 0; i < 10000; i++){ 221 | new MyThread().start(); 222 | count++; 223 | } 224 | }catch(OutOfMemoryError e){ 225 | System.out.println(count); 226 | System.out.println(e.getMessage()); 227 | } 228 | } 229 | } 230 | >虚拟机配置 231 | 232 | -Xss1M 233 | 234 | >运行输出 235 | 236 | 1578 237 | unable to create new native thread 238 | 239 | >虚拟机配置 240 | 241 | -Xss20M 242 | 243 | >运行输出 244 | 245 | 69 246 | unable to create new native thread 247 | 248 | 实验证明如果改变系统的最大堆空间设定,可以发现系统所能支持的线程数量也会相应改变。 249 | Java 堆的分配以 200MB 递增,当栈大小为 1MB 时,最大线程数量以 200 递减。当系统物理内存被堆占据时,就不可以被栈使用。当系统由于内存空间不够而无法创建新的线程时会抛出 OOM 异常。这并不是由于堆内存不够而导致的 OOM,而是因为操作系统内存减去堆内存后剩余的系统内存不足而无法创建新的线程。在这种情况下可以尝试减少堆内存以换取更多的系统空间来解决这个问题。综上所述,如果系统确实需要大量线程并发执行,那么设置一个较小的堆和较小的栈有助于提高系统所能承受的最大线程数。 250 | 设置堆的比例分配 251 | 参数-XX:SurvivorRatio 是用来设置新生代中 eden 空间和 s0 空间的比例关系。s0 和 s1 空间又分别称为 from 空间和 to 空间。它们的大小是相同的,职能也是相同的,并在 Minor GC 后互换角色。 252 | 253 | >演示不断插入字符时使用的GC输出 254 | 255 | import java.util.ArrayList; 256 | import java.util.List; 257 | 258 | public class StringDemo { 259 | public static void main(String[] args){ 260 | List handler = new ArrayList(); 261 | for(int i=0;i<1000;i++){ 262 | HugeStr h = new HugeStr(); 263 | ImprovedHugeStr h1 = new ImprovedHugeStr(); 264 | handler.add(h.getSubString(1, 5)); 265 | handler.add(h1.getSubString(1, 5)); 266 | } 267 | } 268 | 269 | static class HugeStr{ 270 | private String str = new String(new char[800000]); 271 | public String getSubString(int begin,int end){ 272 | return str.substring(begin, end); 273 | } 274 | } 275 | 276 | static class ImprovedHugeStr{ 277 | private String str = new String(new char[10000000]); 278 | public String getSubString(int begin,int end){ 279 | return new String(str.substring(begin, end)); 280 | } 281 | } 282 | } 283 | 284 | >设置新生代堆为10MB,并使eden区是s0的8 285 | 286 | -XX:+PrintGCDetails -XX:MaxNewSize=10M -XX:SurvivorRatio=8 287 | 288 | >运行输出 289 | 290 | [Full GC [Tenured: 233756K->233743K(251904K), 0.0524229 secs] 233756K->233743K(261120K), 291 | [Perm : 377K->372K(12288K)], 0.0524703 secs] [Times: user=0.06 sys=0.00, real=0.06 secs] 292 | def new generation total 9216K, used 170K [0x27010000, 0x27a10000, 0x27a10000) 293 | eden space 8192K, 2% used [0x27010000, 0x2703a978, 0x27810000) 294 | from space 1024K, 0% used [0x27910000, 0x27910000, 0x27a10000) 295 | to space 1024K, 0% used [0x27810000, 0x27810000, 0x27910000) 296 | tenured generation total 251904K, used 233743K [0x27a10000, 0x37010000, 0x37010000) 297 | the space 251904K, 92% used [0x27a10000, 0x35e53d00, 0x35e53e00, 0x37010000) 298 | compacting perm gen total 12288K, used 372K [0x37010000, 0x37c10000, 0x3b010000) 299 | the space 12288K, 3% used [0x37010000, 0x3706d310, 0x3706d400, 0x37c10000) 300 | ro space 10240K, 51% used [0x3b010000, 0x3b543000, 0x3b543000, 0x3ba10000) 301 | rw space 12288K, 55% used [0x3ba10000, 0x3c0ae4f8, 0x3c0ae600, 0x3c610000) 302 | 303 | 修改参数 SurvivorRatio 为 2 运行程序,相当于设置 eden 区是 s0 的 2 倍大小,由于 s1 与 s0 相同,故有 eden=[10MB/(1+1+2)]*2=5MB。 304 | 305 | 306 | >运行输出 307 | 308 | [Full GC [Tenured: 233756K->233743K(251904K), 0.0546689 secs] 233756K->233743K(259584K), 309 | [Perm : 377K->372K(12288K)],0.0547257 secs] [Times: user=0.05 sys=0.00, real=0.05 secs] 310 | def new generation total 7680K, used 108K [0x27010000, 0x27a10000, 0x27a10000) 311 | eden space 5120K, 2% used [0x27010000, 0x2702b3b0, 0x27510000) 312 | from space 2560K, 0% used [0x27510000, 0x27510000, 0x27790000) 313 | to space 2560K, 0% used [0x27790000, 0x27790000, 0x27a10000) 314 | tenured generation total 251904K, used 233743K [0x27a10000, 0x37010000, 0x37010000) 315 | the space 251904K, 92% used [0x27a10000, 0x35e53d00, 0x35e53e00, 0x37010000) 316 | compacting perm gen total 12288K, used 372K [0x37010000, 0x37c10000, 0x3b010000) 317 | the space 12288K, 3% used [0x37010000, 0x3706d310, 0x3706d400, 0x37c10000) 318 | ro space 10240K, 51% used [0x3b010000, 0x3b543000, 0x3b543000, 0x3ba10000) 319 | rw space 12288K, 55% used [0x3ba10000, 0x3c0ae4f8, 0x3c0ae600, 0x3c610000) 320 | 321 | ######Java堆参数总结 322 | 323 | Java 堆操作是主要的数据存储操作,总结的主要参数配置如下。 324 | 325 | 与 Java 应用程序堆内存相关的 JVM 参数有: 326 | 327 | -Xms:设置 Java 应用程序启动时的初始堆大小; 328 | 329 | -Xmx:设置 Java 应用程序能获得的最大堆大小; 330 | 331 | -Xss:设置线程栈的大小; 332 | 333 | -XX:MinHeapFreeRatio:设置堆空间最小空闲比例。当堆空间的空闲内存小于这个数值时,JVM 便会扩展堆空间; 334 | 335 | -XX:MaxHeapFreeRatio:设置堆空间的最大空闲比例。当堆空间的空闲内存大于这个数值时,便会压缩堆空间,得到一个较小的堆; 336 | 337 | -XX:NewSize:设置新生代的大小; 338 | 339 | -XX:NewRatio:设置老年代与新生代的比例,它等于老年代大小除以新生代大小; 340 | 341 | -XX:SurvivorRatio:新生代中 eden 区与 survivor 区的比例; 342 | 343 | -XX:MaxPermSize:设置最大的持久区大小; 344 | 345 | -XX:TargetSurvivorRatio: 设置 survivor 区的可使用率。当 survivor 区的空间使用率达到这个数值时,会将对象送入老年代。 346 | 347 | 从所有这些参数描述信息和代码示例可以看到,没有哪一条固定的规则可以供程序员参考。性能优化需要根据您应用的实际情况来有选择性地挑选参数及配制值,没有完全绝对的最优方案,最优方案是基于您对 JVM 数据存储方式及自己代码的了解程度来作出的最佳选择。 348 | -------------------------------------------------------------------------------- /Java volatile 关键字.md: -------------------------------------------------------------------------------- 1 | ####Java volatile 关键字 2 | 在Java中,valatile关键字被用来标识一个Java变量 ”being stored in main memory“。更准确地讲,每次读取一个volatile变量都会从主存中去读,而不是从CPU缓存中;每次修改一个volatile变量都会将其写入到主存中,而不仅仅是CPU缓存中。 3 | 4 | #####保证内存可见性 5 | 在一个多线程的程序中,多个线程操作一个非volatile修饰的变量,每个线程可能会把变量从主存复制到CPU缓存中去,处于性能原因,如果你的计算机装有多个CPU,每个线程可能运行在不同的CPU上。这也就意味着,每个线程可能将变量拷贝到不同CPU到的CPU缓存中去。如下图: 6 | 7 | ![volatile](http://tutorials.jenkov.com/images/java-concurrency/java-volatile-1.png) 8 | 9 | 对于非volatile变量,Java虚拟机将数据从主存读入到CPU缓存,或者从CPU缓存写回到主存中并没有任何保证。 10 | 11 | 看这样的一个例子,多个线程访问一个包含一个counter变量的共享对象,声明如下: 12 | 13 | public class SharedObject{ 14 | public int counter = 0; 15 | } 16 | 线程1读取共享counter变量(值为0)到它的CPU的缓存中,然后将counter增加到1,没有将变化后的值写回到主存中。线程2做了和线程1同样的行为。线程1和线程2并没有进行同步。counter变量实际的值应该为2,但是每个线程在它们的CPU缓存中读到的变量的值为1,在主存中counter变量的值还是0.尽管线程最终会将counter变量的值写回到主存中,但值将会是错误的。 17 | 18 | 如果将counter声明为volatile,JVM会保证每次都从主存中读取变量,每次修改过值后都会写回到主存中。声明如下: 19 | 20 | public class SharedObject{ 21 | public volatile int counter = 0; 22 | } 23 | 在某些场景下,将变量简单声明为volatile就可以完全确保多个线程访问这个变量时可以最后被修改的值。 24 | 25 | 在两个线程都读和写同一个变量时,简单的将变量声明为volatile是不够的。 26 | 在CPU1中,线程1可能将counter变量(值为0)读入到一个CPU寄存器中。与此同时(或者稍靠后),在CPU2中,线程2可能将counter变量(值为0)读入到一个CPU寄存器中。两个线程都从主存中读取变量。现在,都将变量的值加1,然后写回到主存中。它们都增加counter的寄存器版本为1,都将值1写回到主存中。经过两次自增后counter变量的值应该为2. 27 | 28 | 这个多线程的问题就是没有看到变量最后被修改后的值因为值还没有被写回到主存中,一个线程的更新对其它线程是不可见的。 29 | 30 | #####volatile 31 | 实际上,volatile保证了两点: 32 | - **内存可见性** 33 | - **防止指令重排序** 34 | 35 | #####volatile的适用场景 36 | 如果一个线程读和写一个volatile变量,其它线程只读取这个变量,然后读线程可以确保看到这个变量最后的被修改的值。如果这个变量不被修改为volatile,将没有这样的担保。 37 | 38 | #####volatile的性能考虑 39 | - **发生在主存上的读写操作的代价要高于访问CPU缓存** 40 | - **volatile阻止了指令重排序这种性能优化技术** -------------------------------------------------------------------------------- /Java volatile 关键字.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlankKelly/ConcurrencyNote/4596da2d8592a53c7d60e1df35f67dd0f65b7cae/Java volatile 关键字.zip -------------------------------------------------------------------------------- /Java8并发教程:Threads和Executors.md: -------------------------------------------------------------------------------- 1 | ###Java8并发教程:Threads和Executors 2 | 欢迎阅读我的Java8并发教程的第一部分。这份指南将会以简单易懂的代码示例来教给你如何在Java8中进行并发编程。这是一系列教程中的第一部分。在接下来的15分钟,你将会学会如何通过线程,任务(tasks)和 exector services来并行执行代码。 3 | - 第一部分:Threads和Executors 4 | - 第二部分:同步和锁 5 | 6 | 并发在Java5中首次被引入并在后续的版本中不断得到增强。在这篇文章中介绍的大部分概念同样适用于以前的Java版本。不过我的代码示例聚焦于Java8,大量使用lambda表达式和其他新特性。如果你对lambda表达式不属性,我推荐你首先阅读我的[Java 8 教程](http://winterbe.com/posts/2014/03/16/java-8-tutorial/)。 7 | 8 | ####Threads 和 Runnables 9 | 10 | 所有的现代操作系统都通过进程和线程来支持并发。进程是通常彼此独立运行的程序的实例,比如,如果你启动了一个Java程序,操作系统产生一个新的进程,与其他程序一起并行执行。在这些进程的内部,我们使用线程并发执行代码,因此,我们可以最大限度的利用CPU可用的核心(core)。 11 | 12 | Java从JDK1.0开始执行线程。在开始一个新的线程之前,你必须指定由这个线程执行的代码,通常称为task。这可以通过实现Runnable——一个定义了一个无返回值无参数的run()方法的函数接口,如下面的代码所示: 13 | 14 | Runnable task = () -> { 15 | String threadName = Thread.currentThread().getName(); 16 | System.out.println("Hello " + threadName); 17 | }; 18 | 19 | task.run(); 20 | 21 | Thread thread = new Thread(task); 22 | thread.start(); 23 | 24 | System.out.println("Done!"); 25 | 26 | 因为Runnable是一个函数接口,所以我们利用lambda表达式将当前的线程名打印到控制台。首先,在开始一个线程前我们在主线程中直接运行runnable。 27 | 28 | 控制台输出的结果可能像下面这样: 29 | 30 | Hello main 31 | Hello Thread-0 32 | Done! 33 | 34 | 或者这样: 35 | 36 | Hello main 37 | Done! 38 | Hello Thread-0 39 | 40 | 由于我们不能预测这个runnable是在打印'done'前执行还是在之后执行。顺序是不确定的,因此在大的程序中编写并发程序是一个复杂的任务。 41 | 42 | 我们可以将线程休眠确定的时间。在这篇文章接下来的代码示例中我们可以通过这种方法来模拟长时间运行的任务。 43 | 44 | Runnable runnable = () -> { 45 | try { 46 | String name = Thread.currentThread().getName(); 47 | System.out.println("Foo " + name); 48 | TimeUnit.SECONDS.sleep(1); 49 | System.out.println("Bar " + name); 50 | } 51 | catch (InterruptedException e) { 52 | e.printStackTrace(); 53 | } 54 | }; 55 | 56 | Thread thread = new Thread(runnable); 57 | thread.start(); 58 | 59 | 当你运行上面的代码时,你会注意到在第一条打印语句和第二条打印语句之间存在一分钟的延迟。TimeUnit在处理单位时间时一个有用的枚举类。你可以通过调用Thread.sleep(1000)来达到同样的目的。 60 | 61 | 使用Thread类是很单调的且容易出错。由于并发API在2004年Java5发布的时候才被引入。这些API位于java.util.concurrent包下,包含很多处理并发编程的有用的类。自从这些并发API引入以来,在随后的新的Java版本发布过程中得到不断的增强,甚至Java8提供了新的类和方法来处理并发。 62 | 63 | 接下来,让我们走进并发API中最重要的一部——executor services。 64 | 65 | ####Executors 66 | 67 | 并发API引入了ExecutorService作为一个在程序中直接使用Thread的高层次的替换方案。Executos支持运行异步任务,通常管理一个线程池,这样一来我们就不需要手动去创建新的线程。在不断地处理任务的过程中,线程池内部线程将会得到复用,因此,在我们可以使用一个executor service来运行和我们想在我们整个程序中执行的一样多的并发任务。 68 | 69 | 下面是使用executors的第一个代码示例: 70 | 71 | ExecutorService executor = Executors.newSingleThreadExecutor(); 72 | executor.submit(() -> { 73 | String threadName = Thread.currentThread().getName(); 74 | System.out.println("Hello " + threadName); 75 | }); 76 | 77 | // => Hello pool-1-thread-1 78 | 79 | Executors类提供了便利的工厂方法来创建不同类型的 executor services。在这个示例中我们使用了一个单线程线程池的 executor。 80 | 81 | 代码运行的结果类似于上一个示例,但是当运行代码时,你会注意到一个很大的差别:Java进程从没有停止!Executors必须显式的停止-否则它们将持续监听新的任务。 82 | 83 | ExecutorService提供了两个方法来达到这个目的——shutdwon()会等待正在执行的任务执行完而shutdownNow()会终止所有正在执行的任务并立即关闭execuotr。 84 | 85 | 这是我喜欢的通常关闭executors的方式: 86 | 87 | try { 88 | System.out.println("attempt to shutdown executor"); 89 | executor.shutdown(); 90 | executor.awaitTermination(5, TimeUnit.SECONDS); 91 | } 92 | catch (InterruptedException e) { 93 | System.err.println("tasks interrupted"); 94 | } 95 | finally { 96 | if (!executor.isTerminated()) { 97 | System.err.println("cancel non-finished tasks"); 98 | } 99 | executor.shutdownNow(); 100 | System.out.println("shutdown finished"); 101 | } 102 | 103 | executor通过等待指定的时间让当前执行的任务终止来“温柔的”关闭executor。在等待最长5分钟的时间后,execuote最终会通过中断所有的正在执行的任务关闭。 104 | 105 | #####Callables 和 Futures 106 | 107 | 除了Runnable,executor还支持另一种类型的任务——Callable。Callables也是类似于runnables的函数接口,不同之处在于,Callable返回一个值。 108 | 109 | 下面的lambda表达式定义了一个callable:在休眠一分钟后返回一个整数。 110 | 111 | Callable task = () -> { 112 | try { 113 | TimeUnit.SECONDS.sleep(1); 114 | return 123; 115 | } 116 | catch (InterruptedException e) { 117 | throw new IllegalStateException("task interrupted", e); 118 | } 119 | }; 120 | 121 | Callbale也可以像runnbales一样提交给 executor services。但是callables的结果怎么办?因为submit()不会等待任务完成,executor service不能直接返回callable的结果。不过,executor 可以返回一个Future类型的结果,它可以用来在稍后某个时间取出实际的结果。 122 | 123 | ExecutorService executor = Executors.newFixedThreadPool(1); 124 | Future future = executor.submit(task); 125 | 126 | System.out.println("future done? " + future.isDone()); 127 | 128 | Integer result = future.get(); 129 | 130 | System.out.println("future done? " + future.isDone()); 131 | System.out.print("result: " + result); 132 | 133 | 在将callable提交给exector之后,我们先通过调用isDone()来检查这个future是否已经完成执行。我十分确定这会发生什么,因为在返回那个整数之前callable会休眠一分钟、 134 | 135 | 在调用get()方法时,当前线程会阻塞等待,直到callable在返回实际的结果123之前执行完成。现在future执行完毕,我们可以在控制台看到如下的结果: 136 | 137 | future done? false 138 | future done? true 139 | result: 123 140 | 141 | Future与底层的executor service紧密的结合在一起。记住,如果你关闭executor,所有的未中止的future都会抛出异常。 142 | 143 | executor.shutdownNow(); 144 | future.get(); 145 | 146 | 你可能注意到我们这次创建executor的方式与上一个例子稍有不同。我们使用newFixedThreadPool(1)来创建一个单线程线程池的 execuot service。 147 | 这等同于使用newSingleThreadExecutor不过使用第二种方式我们可以稍后通过简单的传入一个比1大的值来增加线程池的大小。 148 | 149 | #####Timeouts 150 | 151 | 任何future.get()调用都会阻塞,然后等待直到callable中止。在最糟糕的情况下,一个callable持续运行——因此使你的程序将没有响应。我们可以简单的传入一个时长来避免这种情况。 152 | 153 | ExecutorService executor = Executors.newFixedThreadPool(1); 154 | 155 | Future future = executor.submit(() -> { 156 | try { 157 | TimeUnit.SECONDS.sleep(2); 158 | return 123; 159 | } 160 | catch (InterruptedException e) { 161 | throw new IllegalStateException("task interrupted", e); 162 | } 163 | }); 164 | 165 | future.get(1, TimeUnit.SECONDS); 166 | 167 | 运行上面的代码将会产生一个TimeoutException: 168 | 169 | Exception in thread "main" java.util.concurrent.TimeoutException 170 | at java.util.concurrent.FutureTask.get(FutureTask.java:205) 171 | 172 | 你可能已经猜到俄为什么会排除这个异常。我们指定的最长等待时间为1分钟,而这个callable在返回结果之前实际需要两分钟。 173 | 174 | #####invokeAll 175 | 176 | Executors支持通过invokeAll()一次批量提交多个callable。这个方法结果一个callable的集合,然后返回一个future的列表。 177 | 178 | ExecutorService executor = Executors.newWorkStealingPool(); 179 | 180 | List> callables = Arrays.asList( 181 | () -> "task1", 182 | () -> "task2", 183 | () -> "task3"); 184 | 185 | executor.invokeAll(callables) 186 | .stream() 187 | .map(future -> { 188 | try { 189 | return future.get(); 190 | } 191 | catch (Exception e) { 192 | throw new IllegalStateException(e); 193 | } 194 | }) 195 | .forEach(System.out::println); 196 | 197 | 在这个例子中,我们利用Java8中的函数流(stream)来处理invokeAll()调用返回的所有future。我们首先将每一个future映射到它的返回值,然后将每个值打印到控制台。如果你还不属性stream,可以阅读我的[Java8 Stream 教程](http://winterbe.com/posts/2014/07/31/java8-stream-tutorial-examples/)。 198 | 199 | #####invokeAny 200 | 201 | 批量提交callable的另一种方式就是invokeAny(),它的工作方式与invokeAll()稍有不同。在等待future对象的过程中,这个方法将会阻塞直到第一个callable中止然后返回这一个callable的结果。 202 | 203 | 为了测试这种行为,我们利用这个帮助方法来模拟不同执行时间的callable。这个方法返回一个callable,这个callable休眠指定 的时间直到返回给定的结果。 204 | 205 | Callable callable(String result, long sleepSeconds) { 206 | return () -> { 207 | TimeUnit.SECONDS.sleep(sleepSeconds); 208 | return result; 209 | }; 210 | } 211 | 212 | 我们利用这个方法创建一组callable,这些callable拥有不同的执行时间,从1分钟到3分钟。通过invokeAny()将这些callable提交给一个executor,返回最快的callable的字符串结果-在这个例子中为任务2: 213 | 214 | ExecutorService executor = Executors.newWorkStealingPool(); 215 | 216 | List> callables = Arrays.asList( 217 | callable("task1", 2), 218 | callable("task2", 1), 219 | callable("task3", 3)); 220 | 221 | String result = executor.invokeAny(callables); 222 | System.out.println(result); 223 | 224 | // => task2 225 | 226 | 上面这个例子又使用了另一种方式来创建executor——调用newWorkStealingPool()。这个工厂方法是Java8引入的,返回一个ForkJoinPool类型的 executor,它的工作方法与其他常见的execuotr稍有不同。与使用一个固定大小的线程池不同,ForkJoinPools使用一个并行因子数来创建,默认值为主机CPU的可用核心数。 227 | 228 | ForkJoinPools 在Java7时引入,将会在这个系列后面的教程中详细讲解。让我们深入了解一下 scheduled executors 来结束本次教程。 229 | 230 | ####Scheduled Executors 231 | 232 | 我们已经学习了如何在一个 executor 中提交和运行一次任务。为了持续的多次执行常见的任务,我们可以利用调度线程池。 233 | 234 | ScheduledExecutorService支持任务调度,持续执行或者延迟一段时间后执行。 235 | 236 | 下面的实例,调度一个任务在延迟3分钟后执行: 237 | 238 | ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); 239 | 240 | Runnable task = () -> System.out.println("Scheduling: " + System.nanoTime()); 241 | ScheduledFuture future = executor.schedule(task, 3, TimeUnit.SECONDS); 242 | 243 | TimeUnit.MILLISECONDS.sleep(1337); 244 | 245 | long remainingDelay = future.getDelay(TimeUnit.MILLISECONDS); 246 | System.out.printf("Remaining Delay: %sms", remainingDelay); 247 | 248 | 调度一个任务将会产生一个专门的future类型——ScheduleFuture,它除了提供了Future的所有方法之外,他还提供了getDelay()方法来获得剩余的延迟。在延迟消逝后,任务将会并发执行。 249 | 250 | 为了调度任务持续的执行,executors 提供了两个方法scheduleAtFixedRate()scheduleWithFixedDelay()。第一个方法用来以固定频率来执行一个任务,比如,下面这个示例中,每分钟一次: 251 | 252 | ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); 253 | 254 | Runnable task = () -> System.out.println("Scheduling: " + System.nanoTime()); 255 | 256 | int initialDelay = 0; 257 | int period = 1; 258 | executor.scheduleAtFixedRate(task, initialDelay, period, TimeUnit.SECONDS); 259 | 260 | 另外,这个方法还接收一个初始化延迟,用来指定这个任务首次被执行等待的时长。 261 | 262 | 请记住:scheduleAtFixedRate()并不考虑任务的实际用时。所以,如果你指定了一个period为1分钟而任务需要执行2分钟,那么线程池为了性能会更快的执行。 263 | 264 | 在这种情况下,你应该考虑使用scheduleWithFixedDelay()。这个方法的工作方式与上我们上面描述的类似。不同之处在于等待时间 period 的应用是在一次任务的结束和下一个任务的开始之间。例如: 265 | 266 | ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); 267 | 268 | Runnable task = () -> { 269 | try { 270 | TimeUnit.SECONDS.sleep(2); 271 | System.out.println("Scheduling: " + System.nanoTime()); 272 | } 273 | catch (InterruptedException e) { 274 | System.err.println("task interrupted"); 275 | } 276 | }; 277 | 278 | executor.scheduleWithFixedDelay(task, 0, 1, TimeUnit.SECONDS); 279 | 280 | 这个例子调度了一个任务,并在一次执行的结束和下一次执行的开始之间设置了一个1分钟的固定延迟。初始化延迟为0,任务执行时间为0。所以我们分别在0s,3s,6s,9s等间隔处结束一次执行。如你所见,scheduleWithFixedDelay()在你不能预测调度任务的执行时长时是很有用的。 281 | 282 | 这是并发系列教程的第以部分。我推荐你亲手实践一下上面的代码示例。你可以从 [Github](https://github.com/winterbe/java8-tutorial) 上找到这篇文章中所有的代码示例,所以欢迎你fork这个repo,[给我星星](https://github.com/winterbe/java8-tutorial/stargazers)。 283 | 284 | 我希望你会喜欢这篇文章。如果你有任何的问题都可以在下面评论或者通过 [Twitter](https://twitter.com/benontherun) 给我回复。 285 | 286 | 287 | -------------------------------------------------------------------------------- /Java中的ThreadLocal.md: -------------------------------------------------------------------------------- 1 | ####Java中的ThreadLocal 2 | 在Java中的ThreadLocal类允许你创建的变量只被同一个线程读和写。因此,即使两个线程执行同样的代码,代码中有一个ThreadLocal类型的变量,两个线程相互之间也不能“看到”对方的ThreadLocal类型的变量、 3 | 4 | #####创建一个ThreadLocal 5 | 6 | private ThreadLocal myThreadLocal = new ThreadLocal(); 7 | 8 | #####操作一个ThreadLocal 9 | 10 | myThreadLocal.set("A thread local value"); 11 | 12 | String threadLocalValue = (String)myThreadLocal.get(); 13 | 14 | #####泛型的ThreadLocal 15 | 16 | 17 | private ThreadLocal myThreadLocal = new ThreadLocal(); 18 | myThreadLocal.set("Hello ThreadLocal"); 19 | String threadLocalValue = myThreadLocal.get(); 20 | 21 | #####初始化ThreadLocal值 22 | 23 | private ThreadLocal myThreadLocal = new ThreadLocal(){ 24 | @Override 25 | protected String initialValue(){ 26 | return "This is the initial value"; 27 | } 28 | } 29 | 30 | 初始值对所有线程可见。 31 | 32 | ##### -------------------------------------------------------------------------------- /Java中的读写锁.md: -------------------------------------------------------------------------------- 1 | ###Java中的读写锁 2 | 一个读写锁是比**Java中的锁**那篇文章中展现的锁实现更加复杂的锁。假设你的程序涉及一些共享资源的读写,但是写的次数没有读的次数频繁。两个线程读同一个资源不会有什么问题,所以多个线程应该被允许同时读同一个资源。但是如果有一个线程想去写这些共享资源,就不应该再有其它线程对该资源进行读或写(译者注:也就是说:读-读能共存,读-写不能共存,写-写不能共存)。这就需要一个读/写锁来解决这个问题。 3 | 4 | Java5在java.util.concurrent包中已经包含了读写锁。尽管如此,我们还是应该了解其实现背后的原理。 5 | 6 | ####读写锁的Java实现 7 | 8 | 先让我们对读写访问资源的条件做个概述: 9 | 10 | 读取 没有线程正在做写操作,且没有线程在请求写操作。 11 | 12 | 写入 没有线程正在做读写操作。 13 | 14 | 如果某个线程想要读取资源,只要没有线程正在对该资源进行写操作且没有线程请求对该资源的写操作即可。我们假设对写操作的请求比对读操作的请求更重要,就要提升写请求的优先级。此外,如果读操作发生的比较频繁,我们又没有提升写操作的优先级,那么就会产生“饥饿”现象。请求写操作的线程会一直阻塞,直到所有的读线程都从ReadWriteLock上解锁了。如果一直保证新线程的读操作权限,那么等待写操作的线程就会一直阻塞下去,结果就是发生“饥饿”。因此,只有当没有线程正在锁住ReadWriteLock进行写操作,且没有线程请求该锁准备执行写操作时,才能保证读操作继续。 15 | 16 | 当其它线程没有对共享资源进行读操作或者写操作时,某个线程就有可能获得该共享资源的写锁,进而对共享资源进行写操作。有多少线程请求了写锁以及以何种顺序请求写锁并不重要,除非你想保证写锁请求的公平性。 17 | 18 | 按照上面的叙述,简单的实现出一个读/写锁,代码如下 19 | 20 | public class ReadWriteLock{ 21 | private int readers = 0; 22 | private int writers = 0; 23 | private int writeRequests = 0; 24 | 25 | public synchronized void lockRead()throws InterruptedException{ 26 | while(writers > 0 || writeRequests > 0){ 27 | wait(); 28 | } 29 | readers++; 30 | } 31 | 32 | public synchronized void unlockRead(){ 33 | readers--; 34 | notifyAll(); 35 | } 36 | 37 | public synchronized void lockWrite()throws InterruptedException{ 38 | writeRequests++; 39 | 40 | //读写互斥 41 | while(readers > 0 || writers > 0){ 42 | wait(); 43 | } 44 | writeRequests--; 45 | writers++; 46 | } 47 | 48 | public synchronized void unlockWrite()throws InterruptedException{ 49 | writers--; 50 | notifyAll(); 51 | } 52 | } 53 | 54 | ReadWriteLock类中,读锁和写锁各有一个获取锁和释放锁的方法。 55 | 56 | 读锁的实现在lockRead()中,只要没有线程拥有写锁(writers==0),且没有线程在请求写锁(writeRequests ==0),所有想获得读锁的线程都能成功获取。 57 | 58 | 写锁的实现在lockWrite()中,当一个线程想获得写锁的时候,首先会把写锁请求数加1(writeRequests++),然后再去判断是否能够真能获得写锁,当没有线程持有读锁(readers==0 ),且没有线程持有写锁(writers==0)时就能获得写锁。有多少线程在请求写锁并无关系。 59 | 60 | 需要注意的是,在两个释放锁的方法(unlockRead,unlockWrite)中,都调用了notifyAll方法,而不是notify。要解释这个原因,我们可以想象下面一种情形: 61 | 62 | 如果有线程在等待获取读锁,同时又有线程在等待获取写锁。如果这时其中一个等待读锁的线程被notify方法唤醒,但因为此时仍有请求写锁的线程存在(writeRequests>0),所以被唤醒的线程会再次进入阻塞状态。然而,等待写锁的线程一个也没被唤醒,就像什么也没发生过一样(译者注:信号丢失现象)。如果用的是notifyAll方法,所有的线程都会被唤醒,然后判断能否获得其请求的锁。 63 | 64 | 用notifyAll还有一个好处。如果有多个读线程在等待读锁且没有线程在等待写锁时,调用unlockWrite()后,所有等待读锁的线程都能立马成功获取读锁 —— 而不是一次只允许一个。 65 | 66 | ####读写锁重入 67 | 68 | 上面实现的读/写锁(ReadWriteLock) 是不可重入的,当一个已经持有写锁的线程再次请求写锁时,就会被阻塞。原因是已经有一个写线程了——就是它自己。此外,考虑下面的例子: 69 |
 70 | Thread 1 获得了读锁
 71 | 
 72 | Thread 2 请求写锁,但因为Thread 1 持有了读锁,所以写锁请求被阻塞。
 73 | 
 74 | Thread 1 再想请求一次读锁,但因为Thread 2处于请求写锁的状态,所以想再次获取读锁也会被阻塞。
 75 | 
76 | 上面这种情形使用前面的ReadWriteLock就会被锁定——一种类似于死锁的情形。不会再有线程能够成功获取读锁或写锁了。 77 | 78 | 为了让ReadWriteLock可重入,需要对它做一些改进。下面会分别处理读锁的重入和写锁的重入。 79 | 80 | ####读锁重入 81 | 82 | 为了让ReadWriteLock的读锁可重入,我们要先为读锁重入建立规则: 83 | 84 | 要保证某个线程中的读锁可重入,要么满足获取读锁的条件(没有写或写请求),要么已经持有读锁(不管是否有写请求)。 85 | 要确定一个线程是否已经持有读锁,可以用一个map来存储已经持有读锁的线程以及对应线程获取读锁的次数,当需要判断某个线程能否获得读锁时,就利用map中存储的数据进行判断。 86 | 87 | 下面是方法lockRead和unlockRead修改后的的代码: 88 | 89 | public class ReadWriteLock{ 90 | private Map readingThreads = new HashMap(); 91 | 92 | private int writers = 0; 93 | private int writeRequests = 0; 94 | 95 | ` public synchronized void lockRead()throws InterruptedException{ 96 | Thread callingThread = Thread.currentThread(); 97 | 98 | while(!canGrantReadAccess(callingThread)){ 99 | wait(); 100 | } 101 | 102 | readingThreads.put(callingThread, (getAccessCount(callingThread) + 1)); 103 | } 104 | 105 | public synchronized void unlockRead(){ 106 | Thread callingThread = Thread.currentThread(); 107 | int accessCount = getAccessCount(callingThread); 108 | 109 | if(accessCount == 1){ 110 | readingThreads.remove(callingThreads); 111 | }else{ 112 | readingThreads.put(callingThread, (accessCount - 1)); 113 | } 114 | 115 | notifyAll(); 116 | } 117 | 118 | private boolean canGrantReadAccess(Thread callingThread){ 119 | if(writers > 0) 120 | return false; 121 | if(isReader(callingThread)) 122 | return true; 123 | if(writeRequests > 0) 124 | return false; 125 | return true; 126 | } 127 | 128 | private int getReadAccessCount(Thread callingThread){ 129 | Integer accessCount = readingThreads.get(callingThread); 130 | 131 | if(accessCount == null){ 132 | return 0; 133 | }else{ 134 | return accessCount.intValue(); 135 | } 136 | } 137 | 138 | private boolean isReader(Thread callingThread){ 139 | return readingThreads.get(callingThread) != null; 140 | } 141 | } 142 | 143 | 代码中我们可以看到,只有在没有线程拥有写锁的情况下才允许读锁的重入。此外,重入的读锁比写锁优先级高。 144 | 145 | ####写锁重入 146 | 147 | 仅当一个线程已经持有写锁,才允许写锁重入(再次获得写锁)。下面是方法lockWrite和unlockWrite修改后的的代码。 148 | 149 | public class ReadWriteLock{ 150 | private Map readingThreads = new HashMap(); 151 | private int writeAccesses = 0; 152 | private int writeRequests = 0; 153 | private Thread writingThread = null; 154 | 155 | public synchronized void lockWrite()throws InterruptedException{ 156 | writeRequests++; 157 | Thread callingThread = Thread.currentThread(); 158 | 159 | while(!canGrantWriteAccess(callingThread)){ 160 | wait(); 161 | } 162 | writeRequests--; 163 | writeAccesses++; 164 | writingThread = callingThread; 165 | } 166 | 167 | public synchronized void unlockWrite()throws InterruptedException{ 168 | writeAccesses--; 169 | 170 | if(writeAccesses == 0){ 171 | writingThread = null; 172 | } 173 | 174 | notifyAll(); 175 | } 176 | 177 | private boolean canGrantWriteAccess(Thread callingThread){ 178 | if(hasReaders()) 179 | return false; 180 | if(writingThread == null) 181 | return true; 182 | if(!isWriter(callingThread)) 183 | return false; 184 | return true; 185 | } 186 | 187 | private boolean hasReaders(){ 188 | return readingThreads.size() > 0; 189 | } 190 | 191 | private boolean isWriter(Thread callingThread){ 192 | return writingThread == callingThread; 193 | } 194 | } 195 | 196 | 注意在确定当前线程是否能够获取写锁的时候,是如何处理的。 197 | 198 | ####读锁升级到写锁 199 | 有时候,一个线程获得读锁后也要获得写锁是必要的。对于这种情况,这个线程必须是读线程。writeLock方法需要做一点小小的修改。下面是它的一个实现版本。 200 | 201 | public class ReadWriteLock{ 202 | private Map readingThreads = new HashMap(); 203 | private int writeAccesses = 0; 204 | private writeRequests = 0; 205 | private Thread writingThread = null; 206 | 207 | public synchronized void lockWrite()throws InterruptedException{ 208 | writeRequests++; 209 | Thread callingThread = Thread.currentThread() 210 | 211 | while(!canGrantWriteAccess(callingThread)){ 212 | wait(); 213 | } 214 | writeRequests--; 215 | writeAccesses++; 216 | writingThread = callingThread; 217 | } 218 | 219 | private synchronized void unlockWrite()throws InterruptedException{ 220 | writeAccesses--; 221 | if(writeAccesses == 0){ 222 | writingThread = null; 223 | } 224 | notifyAll(); 225 | } 226 | 227 | 228 | private boolean canGrantWriteAccess(Thread callingThread){ 229 | //请求加锁的线程 230 | if(isOnlyReader(callingThread)) 231 | return true; 232 | //没有线程请求或持有读锁 233 | if(hasReaders()) 234 | return false; 235 | //还没有线程持有写锁 236 | if(writingThread == null) 237 | return true; 238 | if(!isWriter(callingThread)) 239 | return false; 240 | return true; 241 | } 242 | 243 | private boolean hasReaders(){ 244 | return readingThreads.size() > 0; 245 | } 246 | 247 | private boolean isWriter(Thread callingThread){ 248 | return writingThread == callingThread; 249 | } 250 | 251 | private boolean isOnlyReader(Thread thread){ 252 | return readers == 1 && readingThreads.get(callingThre) != null; 253 | } 254 | } 255 | 256 | 现在ReadWriteLock类就可以从读锁升级到写锁了。 257 | 258 | ####写锁降低到读锁 259 | 260 | 有时拥有写锁的线程也希望得到读锁。如果一个线程拥有了写锁,那么自然其它线程是不可能拥有读锁或写锁了。所以对于一个拥有写锁的线程,再获得读锁,是不会有什么危险的。我们仅仅需要对上面canGrantReadAccess方法进行简单地修改: 261 | 262 | public class ReadWriteLock{ 263 | private boolean canGrantReadAccess(Thread callingThread){ 264 | if(isWriter(callingThread)) 265 | return true; 266 | if(writingThread != null) 267 | return false; 268 | if(isReader(callingThread)) 269 | return true; 270 | if(writeRequests > 0) 271 | return false; 272 | return true; 273 | } 274 | } 275 | 276 | ####可重入的ReadWriteLock的完整实现 277 | 278 | 下面是完整的ReadWriteLock实现。为了便于代码的阅读与理解,简单对上面的代码做了重构。重构后的代码如下. 279 | 280 | public class ReadWriteLock{ 281 | 282 | private Map readingThreads = 283 | new HashMap(); 284 | 285 | private int writeAccesses = 0; 286 | private int writeRequests = 0; 287 | private Thread writingThread = null; 288 | 289 | 290 | public synchronized void lockRead() throws InterruptedException{ 291 | Thread callingThread = Thread.currentThread(); 292 | while(! canGrantReadAccess(callingThread)){ 293 | wait(); 294 | } 295 | 296 | readingThreads.put(callingThread, 297 | (getReadAccessCount(callingThread) + 1)); 298 | } 299 | 300 | private boolean canGrantReadAccess(Thread callingThread){ 301 | if( isWriter(callingThread) ) return true; 302 | if( hasWriter() ) return false; 303 | if( isReader(callingThread) ) return true; 304 | if( hasWriteRequests() ) return false; 305 | return true; 306 | } 307 | 308 | 309 | public synchronized void unlockRead(){ 310 | Thread callingThread = Thread.currentThread(); 311 | if(!isReader(callingThread)){ 312 | throw new IllegalMonitorStateException("Calling Thread does not" + 313 | " hold a read lock on this ReadWriteLock"); 314 | } 315 | int accessCount = getReadAccessCount(callingThread); 316 | if(accessCount == 1){ readingThreads.remove(callingThread); } 317 | else { readingThreads.put(callingThread, (accessCount -1)); } 318 | notifyAll(); 319 | } 320 | 321 | public synchronized void lockWrite() throws InterruptedException{ 322 | writeRequests++; 323 | Thread callingThread = Thread.currentThread(); 324 | while(! canGrantWriteAccess(callingThread)){ 325 | wait(); 326 | } 327 | writeRequests--; 328 | writeAccesses++; 329 | writingThread = callingThread; 330 | } 331 | 332 | public synchronized void unlockWrite() throws InterruptedException{ 333 | if(!isWriter(Thread.currentThread()){ 334 | throw new IllegalMonitorStateException("Calling Thread does not" + 335 | " hold the write lock on this ReadWriteLock"); 336 | } 337 | writeAccesses--; 338 | if(writeAccesses == 0){ 339 | writingThread = null; 340 | } 341 | notifyAll(); 342 | } 343 | 344 | private boolean canGrantWriteAccess(Thread callingThread){ 345 | if(isOnlyReader(callingThread)) return true; 346 | if(hasReaders()) return false; 347 | if(writingThread == null) return true; 348 | if(!isWriter(callingThread)) return false; 349 | return true; 350 | } 351 | 352 | 353 | private int getReadAccessCount(Thread callingThread){ 354 | Integer accessCount = readingThreads.get(callingThread); 355 | if(accessCount == null) return 0; 356 | return accessCount.intValue(); 357 | } 358 | 359 | 360 | private boolean hasReaders(){ 361 | return readingThreads.size() > 0; 362 | } 363 | 364 | private boolean isReader(Thread callingThread){ 365 | return readingThreads.get(callingThread) != null; 366 | } 367 | 368 | private boolean isOnlyReader(Thread callingThread){ 369 | return readingThreads.size() == 1 && 370 | readingThreads.get(callingThread) != null; 371 | } 372 | 373 | private boolean hasWriter(){ 374 | return writingThread != null; 375 | } 376 | 377 | private boolean isWriter(Thread callingThread){ 378 | return writingThread == callingThread; 379 | } 380 | 381 | private boolean hasWriteRequests(){ 382 | return this.writeRequests > 0; 383 | } 384 | } 385 | 386 | ####在一个finally块中调用unlock() 387 | 388 | 在利用ReadWriteLock来保护临界区时,如果临界区可能抛出异常,在finally块中调用readUnlock()和writeUnlock()就显得很重要了。这样做是为了保证ReadWriteLock能被成功解锁,然后其它线程可以请求到该锁。这里有个例子: 389 | 390 | lock.lockWrite(); 391 | try{ 392 | //do critical section code, which may throw exception 393 | }finally{ 394 | lock.unlockWrite(); 395 | } 396 | 397 | 上面这样的代码结构能够保证临界区中抛出异常时ReadWriteLock也会被释放。如果unlockWrite方法不是在finally块中调用的,当临界区抛出了异常时,ReadWriteLock 会一直保持在写锁定状态,就会导致所有调用lockRead()或lockWrite()的线程一直阻塞。唯一能够重新解锁ReadWriteLock的因素可能就是ReadWriteLock是可重入的,当抛出异常时,这个线程后续还可以成功获取这把锁,然后执行临界区以及再次调用unlockWrite(),这就会再次释放ReadWriteLock。但是如果该线程后续不再获取这把锁了呢?所以,在finally中调用unlockWrite对写出健壮代码是很重要的。 398 | 399 | 400 | -------------------------------------------------------------------------------- /Java中的锁.md: -------------------------------------------------------------------------------- 1 | ###Java中的锁 2 | 锁是类似于同步块的一种线程同步机制除了锁比Java中同步块更复杂外。锁是使用同步块创建的,因此,我们并不能完全摆脱synchronized关键字。 3 | 4 | 在java.util.concurrent.locks包中有几种锁实现,所以你可能自己实现锁。但是,你仍然需要知道如何使用它们以及它们背后的实现原理。 5 | 6 | ####一个简单的锁 7 | 8 | 让我们先看一个Java代码中的同步块: 9 | 10 | public class Counter{ 11 | private int count = 0; 12 | 13 | public int inc(){ 14 | synchronized(this){ 15 | return ++count; 16 | } 17 | } 18 | } 19 | 注意,在inc方法中的synchronized(this)代码块。这个代码块确保了在某一时刻只有一个线程可以执行return ++count。 20 | 21 | Counter还可以像如下这样实现,使用一个Lock来代替同步块。 22 | 23 | public class Counter{ 24 | private Lock lock = new Lock(); 25 | private int count = 0; 26 | 27 | public int inc(){ 28 | lock.lock(); 29 | int newCount = ++count; 30 | lock.unlock(); 31 | return newCount; 32 | } 33 | } 34 | 35 | lock()锁住一个Lock实例,这样所有的线程在调用lock()方法都会阻塞知道unlock方法被执行。 36 | 37 | 下面是一个Lock的简单实现: 38 | 39 | public class Lock{ 40 | private boolean isLocked = false; 41 | 42 | public synchronized void lock(){ 43 | while(isLocked){ 44 | wait(); 45 | } 46 | isLocked = true; 47 | } 48 | 49 | public synchronized void unlock(){ 50 | isLocked = false; 51 | notify(); 52 | } 53 | } 54 | 55 | 注意,while(isLocked)循环也被称作“自旋锁”(spin lock)。当isLockedtrue时,调用lock()的线程在wait()调用后阻塞等待。为防止该线程没有收到notify()调用也从wait()中返回(也称作虚假唤醒),这个线程会重新去检查isLocked条件以决定当前是否可以安全地继续执行还是需要重新保持等待,而不是认为线程被唤醒了就可以安全地继续执行了。如果isLocked为false,当前线程会退出while(isLocked)循环,并将isLocked设回true,让其它正在调用lock()方法的线程能够在Lock实例上加锁。 56 | 57 | 当线程执行完临界区的代码,调用unlock()方法。执行unlock()isLocked设置回false,然后唤醒在lock()方法中wait()调用上阻塞等待的一个线程。 58 | 59 | ####锁重入 60 | 61 | 在Java中同步块是可以重入的。意思是,如果一个Java线程进入了一个同步代码块,进而持有同步代码块中的管程对象,这个线程也可以进入被同一个管程同步代码块。这儿有一个例子: 62 | 63 | public class Reentrant{ 64 | public synzhronized outer(){ 65 | inner(); 66 | } 67 | 68 | public synchronized inner(){ 69 | //do something 70 | } 71 | } 72 | 73 | 我们注意到,outerinner方法都被声明为synchronized,这在Java中等同于一个synchronized(this)块。如果一个线程调用outer()方法,然后在outer()方法中调用inner()方法是可以的,因为这两个方法(块)都被同步在同一个管程对象"this"上。如果一个线程已经持有个一个管程对象上的锁,它可以访问所有同步在这个管程对象上的代码块。这被称作重入(reentrance)。 74 | 75 | 前面的锁实现都是非重入的。如果我们像下面这样重写Reentrance类,调用outer()方法的线程将会阻塞在inner()方法中的lock.lock()里面。 76 | 77 | public class Reentrance2{ 78 | Lock lock = new Lock(); 79 | 80 | public outer(){ 81 | lock.lock(); 82 | inner(); 83 | lock.unlock(); 84 | } 85 | 86 | public synchronized inner(){ 87 | lock.lock(); 88 | //do something 89 | lock.unlock(); 90 | } 91 | } 92 | 93 | 一个线程调用outer()会首先锁住Lock实例,然后继续调用inner()。在inner()内部,这个线程将会再次尝试锁住Lock实例。这将会失败(这个线程会被阻塞),因为Lock实例已经在outer()方法中被锁住。 94 | 95 | 当我们查看lock()方法的实现时,会发现线程两次调用lock()方法的过程中第二次在没有调用unlock()方法的情况下会被阻塞的原因也很明显: 96 | 97 | public class Lock{ 98 | boolean isLocked = false; 99 | 100 | public synchronized void lock()throws InterruptedException{ 101 | while(isLocked){ 102 | wait(); 103 | } 104 | 105 | isLocked = true; 106 | } 107 | 108 | ... 109 | } 110 | 111 | while循环里面的条件决定了一个线程是否被允许离开lock()方法。当前的判断条件是只有当isLocked为false时lock操作才被允许,而没有考虑是哪个线程锁住了它。 112 | 113 | 为了使Lock可重入,我们需要做一点小小的改动。 114 | 115 | public class Lock{ 116 | boolean isLocked = false; 117 | Thread lockedBy = null; 118 | int lockedCount = 0; 119 | 120 | public synzhronized void lock()throws InterruptedException{ 121 | Thread callingThread = Thread.currentThread(); 122 | while(isLocked && lockedBy != callingThread){ 123 | wait(); 124 | } 125 | isLocked = true; 126 | lockedCount++; 127 | lockedBy = callingThread; 128 | } 129 | 130 | public synchronized void unlock(){ 131 | if(Thread.currentThread() == this.lockBy){ 132 | lockedCount--; 133 | 134 | if(lockedCount == 0){ 135 | isLocked = false; 136 | notify(); 137 | } 138 | } 139 | } 140 | } 141 | 142 | 注意到现在的while循环(自旋锁)也考虑到了已锁住该Lock实例的线程。如果当前的锁对象没有被加锁(isLocked = false),或者当前调用线程已经对该Lock实例加了锁,那么while循环就不会被执行,调用lock()的线程就可以退出该方法(译者注:“被允许退出该方法”在当前语义下就是指不会调用wait()而导致阻塞)。 143 | 144 | 除此之外,我们需要记录同一个线程重复对一个锁对象加锁的次数。否则,一次unblock()调用就会解除整个锁,即使当前锁已经被加锁过多次。在unlock()调用没有达到对应lock()调用的次数之前,我们不希望锁被解除。 145 | 146 | 现在这个Lock类就是可重入的了。 147 | 148 | ####锁的公平性 149 | 150 | Java的synchronized块并不保证尝试进入它们的线程的顺序。因此,如果多个线程不断竞争访问相同的synchronized同步块,就存在一种风险,其中一个或多个线程永远也得不到访问权 —— 也就是说访问权总是分配给了其它线程。这种情况被称作线程饥饿。为了避免这种问题,锁需要实现公平性。本文所展现的锁在内部是用synchronized同步块实现的,因此它们也不保证公平性。饥饿和公平中有更多关于该内容的讨论。 151 | 152 | ####在finally语句中调用unlock() 153 | 154 | 如果用Lock来保护临界区,并且临界区有可能会抛出异常,那么在finally语句中调用unlock()就显得非常重要了。这样可以保证这个锁对象可以被解锁以便其它线程能继续对其加锁。以下是一个示例: 155 | 156 | lock.lock(); 157 | try{ 158 | //do critical section code, which may throw exception 159 | }finally{ 160 | lock.unlock(); 161 | } 162 | 163 | 这个简单的结构可以保证当临界区抛出异常时Lock对象可以被解锁。如果不是在finally语句中调用的unlock(),当临界区抛出异常时,Lock对象将永远停留在被锁住的状态,这会导致其它所有在该Lock对象上调用lock()的线程一直阻塞。 164 | 165 | 166 | 167 | -------------------------------------------------------------------------------- /Java内存模型.md: -------------------------------------------------------------------------------- 1 | ####Java内存模型 2 | Java内存模型描述了Java虚拟机和计算机内存之间是如何协同工作的。一个Java虚拟机也是一个完整的计算机的模型,因此,这个模型自然也包含了内存模型。 3 | 4 | 如果你想写出表现良好的并发程序就必须理解Java内存模型。Java内存模型描述了不同线程间如何和何时看到被其他线程修改的共享变量以及在需要时如何同步访问共享变量。 5 | 6 | 原来的Java内存模型存在很多不足,所以在Java5时进行了修改。这个一直使用至今。 7 | 8 | ####Java内存模型 9 | 10 | ![Java_Memory_Model](http://tutorials.jenkov.com/images/java-concurrency/java-memory-model-1.png) 11 | 12 | 每个运行在Java虚拟机中的线程都拥有自己的线程栈。这个线程栈包含了这个线程所调用方法的当前执行点,所有我们也可以称之为”调用栈“。在线程执行代码的过程总,调用栈随之发生变化。 13 | 14 | 线程栈也包含每个被执行的方法中的所有局部变量。一个线程只能访问自己的线程栈。一个线程创建的局部变量对其他线程是不可见的。即使两个线程执行同样的代码,这两个线程任然需要在它们各自的线程栈中创建这些变量。 15 | 16 | 所有的基本类型的局部变量都全部存放在各自的线程栈中,对其他线程不可见。一个线程可能会向另一个线程传递一个基本类型变量的拷贝,但是这并不能共享基本类型变量自身。 17 | 18 | 在堆中包含所有你在Java程序中创建的对象。这也包含所有基本类型所对应的装箱类型。即便,我们创建了一个对象然后我们把它赋给了一个局部变量,或者作为另一个对象的成员变量,这个对象仍然存放在堆中。 19 | 20 | ![Java_Memory_Model](http://tutorials.jenkov.com/images/java-concurrency/java-memory-model-2.png) 21 | 22 | 一个局部变量可能是基本类型,这样它就永远呆在线程栈中。 23 | 24 | 一个局部变量也可能是引用变量。在这种情况下,引用变量存放在线程栈总,对象本身存放在堆中。 25 | 26 | 一个对象可能包含方法,这些方法又可能包含局部变量。这些局部变量也被存放在线程栈中,即便这个方法所属的对象存放在堆中。 27 | 28 | 一个对象的成员变量随着对象自身存放在堆中。不管这个变量是基本类型还是引用类型都是如此。 29 | 30 | 静态类变量随着类定义也存放在堆中。 31 | 32 | 存放在堆中的对象可以被所有的线程通过指向对象的引用访问。当一个线程访问一个对象时,它也可以访问这个对象的成员遍历。如果两个线程在同一时刻调用同一个对象上的同一个方法,它们都可以访问对象的成员变量,但是每个线程都会拥有各自的局部变量拷贝。 33 | 34 | ![Java_Memory_Model](http://tutorials.jenkov.com/images/java-concurrency/java-memory-model-3.png) 35 | 36 | ####硬件内存架构 37 | 现代硬件内存架构和Java内存模型有一些不一样的地方。理解硬件内存架构和Java内存模型如何和它协同工作也非常重要。 38 | 通用的硬件内存架构: 39 | 40 | ![hardware_memory_arch](http://tutorials.jenkov.com/images/java-concurrency/java-memory-model-4.png) 41 | 42 | 现代计算机通常拥有两个或多个CPU。这些CPU中可能还有是多核的。这一点,使得多个线程同时运行在一台计算机上称为了可能。每个CPU可以在任何时刻运行一个线程。 43 | 这就意味着如果你的程序是多线程的,在你的程序内部,一个线程对应一个CPU可能同时运行。 44 | 45 | 每个CPU包含一些寄存器。CPU可以在这些寄存器上执行操作会比在内存上快很多。这是因为CPU访问寄存器的速度远高于访问内存的速度。 46 | 47 | 每个CPU还可能拥有一个CPU缓存层。实际上,现代计算机都会有一个一定大小的缓存层。CPU访问缓存的速度远高于主存,但通常又低于访问寄存器的速度。因此,缓存是用来平衡CPU访问寄存器和主存之间的速度差异的。一些CPU可能拥有多级缓存(一级缓存和二级缓存)。 48 | 49 | 一台计算机还拥有一块主存区域(RAM)。所有的CPU都可以访问主存。主存区域通常要比CPU的缓存大得多。 50 | 51 | 通常,当一个CPU需要访问主存的时候,它会将数据从主存读到CPU的缓存中,甚至再从CPU的缓存读到它内部的寄存器中,然后执行相关的操作。当CPU需要将结果写回到主存中时,它会先将值刷新到缓存中,然后在某一时刻刷新回主存中。 52 | 53 | 当CPU需要在缓存中存储一些其他的东西时,会将存储在缓存中的值刷新回主存中。 54 | 55 | CPU缓存可以局部刷新。 56 | 57 | ####Java内存模型和硬件内存架构之间的联接 58 | 59 | 正如上面所提到的,Java内存模型和硬件内存架构是不同。在硬件内存架构并不区分内存栈和堆。在硬件中,所有的线程栈和堆都位于主存中。线程栈的一部分和堆可能同一位于CPU缓存和寄存器中。 60 | 61 | ![gap](http://tutorials.jenkov.com/images/java-concurrency/java-memory-model-5.png) 62 | 63 | 当对象和变量存放在计算机的不同内存区域中时,就会暴露出一些问题。主要包括两个方面: 64 | - **内存可见性** 65 | - **当读,检查和写共享变量时的竞争条件** 66 | 67 | #####内存可见性 68 | 如果两个或多个线程共享同一个对象时,在不使用vloatile声明或者同步的情况下,一个线程更新了这个共享对象的值可能对其他线程不可见。 69 | 70 | 想象一下,这个共享对象最初存放在主存中。运行在一个CPU上的一个线程,将这个共享对象读到它的CPU缓存中。并在缓存中修改了这个共享对象。只要这个CPU缓存还没有刷新回主存,这个共享共享对象变化后的版本对其它CPU的线程来说就是不可见的。这种方式可能使每个线程最终拥有这个共享对象的拷贝,每个拷贝都停留在不同的CPU缓存中。 71 | 72 | ![cache](http://tutorials.jenkov.com/images/java-concurrency/java-memory-model-6.png) 73 | 74 | 解决这个问题,可以使用Java中的volatile关键字。volatile关键字可以确保你直接从主存中读取一个给定的变量,当变量发生更新总是会被写回到主存中。 75 | 76 | #####竞争条件 77 | 如果两个或者多个线程共享一个对象,超过一个对象去更新对象上的变量,竞争条件可能就会发生。 78 | 79 | ![race_condition](http://tutorials.jenkov.com/images/java-concurrency/java-memory-model-7.png) 80 | 81 | -------------------------------------------------------------------------------- /Java同步代码块.md: -------------------------------------------------------------------------------- 1 | ####Java同步代码块 2 | #####synchronized关键字 3 | 在Java中使用synchronized关键字来标识同步代码块。在Java中,一个同步代码块是在一些对象上进行同步。所有的同步代码块在某一个时刻只能有一个线程执行代码块里的代码。所有企图访问同步代码块的其它线程都会阻塞直到代码块里的线程离开代码块。 4 | 5 | synchronized关键字可以用来标识四种不同类型的块: 6 | - **实例方法** 7 | - **静态方法** 8 | - **实例方法中的代码块** 9 | - **静态方法中的代码块** 10 | 11 | #####同步实例方法 12 | 13 | public synchronized void add value(int value){ 14 | this.count += value; 15 | } 16 | 17 | #####同步静态方法 18 | 19 | public static synchronized void add(int value){ 20 | count += value; 21 | } 22 | 23 | #####实例方法中的同步代码块 24 | 25 | public void add(int value){ 26 | synchronized(this){ 27 | this.count += value; 28 | } 29 | } 30 | #####静态方法中的同步代码块 31 | 32 | public static void add(int value){ 33 | synchronized(this){ 34 | count += value; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Java多线程详解.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlankKelly/ConcurrencyNote/4596da2d8592a53c7d60e1df35f67dd0f65b7cae/Java多线程详解.pdf -------------------------------------------------------------------------------- /Java并发.md: -------------------------------------------------------------------------------- 1 | ####Java并发 2 | 让我们回到过去,计算机只有一个CPU,某一刻只能运行一个程序。随后出现的多任务一位计算机同时可以运行多个程序,尽管并不是真正的某一个时间点有多个程序在运行一个CPU在多个程序之间共享。操作系统进行CPU的上下文切换,让每个程序获得CPU的使用权利,让后运行。 3 | 4 | 随着多任务的到来,给开发者们提出了新的挑战。程序不能再假定拥有全部的CPU时间,全部的内存或者其他的计算机资源。一个良好的程序应该释放全部的资源当他不再需要的时候,以便让其他程序使用。 5 | 6 | 随后又出现了多线程,意味着我们可以在同一个程序中执行多个线程。一个线程执行可以看做一个CPU在执行这个程序。当一个程序有多个线程的时候,可以被看做多个CPU在执行这个程序。 7 | 8 | 多线程是一种提升同种类型程序的性能的一种方式。然而,多线程比多任务更有挑战性。一个线程在同一个程序内部运行,因此,会读写同一片内存区域。这会产生在单线程程序中不会出现的错误。其中一些错误可能会在单个CPU的机器中发生,因为两个线程从未同时执行过。现代计算机,出现了多核CPU甚至多个CPU,这就意味着我们可以通过多核或者多个CPU同时分开执行多个线程。 9 | 10 | ![enter image description here](http://tutorials.jenkov.com/images/java-concurrency/java-concurrency-tutorial-introduction-1.png) 11 | 12 | 如果一个线程在读取一个内存地址的同时另一个线程在向这块内存写入数据,第一个线程将会读取哪一个值,旧值?第一个线程写入的值?或者两个值中的最大会?又或者,两个线程同时向同一块内存写入数据,两个线程都执行完毕后,这块内存区域被写入的到底是那个值?第一个线程写入的值?第二个线程写入的值?还是两个值中的最大值。 13 | 14 | 上述的这行行为都是不可预料的。最后的结果可能时刻在变化。 15 | 16 | #####Java中的多线程和并发 17 | Java是可以让开发者很容易编写出多线程程序的语言之一。Java在一开始就拥有多线程的能力。因此,Java开发者常常面临上面提到的问题。 18 | 19 | #####Java 并发 in 2015 和 展望 20 | 自从第一本关于Java并发的书籍被编写以来,甚至自从Java5并发工具包发布以来,并发架构和设计的世界都发生着很大的变化。 21 | 22 | 像Vert.x、Play/Akkah和Qbit 这些异步 “shared-nothing" 平台和API已经出现,这些平台使用了不同于Java/Java EE 平台中标准的关于线程,共享内存和锁的并发模型。新的非阻塞并发算法已经发布,新的非阻塞工具像LMax Disrupter已经被加入到我们的工具箱中。 -------------------------------------------------------------------------------- /Java并发编程教程.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlankKelly/ConcurrencyNote/4596da2d8592a53c7d60e1df35f67dd0f65b7cae/Java并发编程教程.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ConcurrencyNote 2 | Java并发 3 | 4 | https://docs.oracle.com/javase/tutorial/essential/concurrency/index.html 5 | 6 | http://tutorials.jenkov.com/java-concurrency/index.html 7 | 8 | http://www.infoq.com/cn/author/方腾飞#文章 9 | -------------------------------------------------------------------------------- /Slipped Conditions.md: -------------------------------------------------------------------------------- 1 | ####Slipped Conditions 2 | 所谓Slipped conditions,就是说, 从一个线程检查某一特定条件到该线程操作此条件期间,这个条件已经被其它线程改变,导致第一个线程在该条件上执行了错误的操作。这里有一个简单的例子: 3 | 4 | public class Lock { 5 | 6 | private boolean isLocked = true; 7 | 8 | public void lock(){ 9 | synchronized(this){ 10 | while(isLocked){ 11 | try{ 12 | this.wait(); 13 | } catch(InterruptedException e){ 14 | //do nothing, keep waiting 15 | } 16 | } 17 | } 18 | 19 | synchronized(this){ 20 | isLocked = true; 21 | } 22 | } 23 | 24 | public synchronized void unlock(){ 25 | isLocked = false; 26 | this.notify(); 27 | } 28 | 29 | } 30 | 31 | 我们可以看到,lock()方法包含了两个同步块。第一个同步块执行wait操作直到isLocked变为false才退出,第二个同步块将isLocked置为true,以此来锁住这个Lock实例避免其它线程通过lock()方法。 32 | 33 | 我们可以设想一下,假如在某个时刻isLocked为false, 这个时候,有两个线程同时访问lock方法。如果第一个线程先进入第一个同步块,这个时候它会发现isLocked为false,若此时允许第二个线程执行,它也进入第一个同步块,同样发现isLocked是false。现在两个线程都检查了这个条件为false,然后它们都会继续进入第二个同步块中并设置isLocked为true。 34 | 35 | 这个场景就是slipped conditions的例子,两个线程检查同一个条件, 然后退出同步块,因此在这两个线程改变条件之前,就允许其它线程来检查这个条件。换句话说,条件被某个线程检查到该条件被此线程改变期间,这个条件已经被其它线程改变过了。 36 | 37 | 为避免slipped conditions,条件的检查与设置必须是原子的,也就是说,在第一个线程检查和设置条件期间,不会有其它线程检查这个条件。 38 | 39 | 解决上面问题的方法很简单,只是简单的把isLocked = true这行代码移到第一个同步块中,放在while循环后面即可: 40 | 41 | public class Lock { 42 | 43 | private boolean isLocked = true; 44 | 45 | public void lock(){ 46 | synchronized(this){ 47 | while(isLocked){ 48 | try{ 49 | this.wait(); 50 | } catch(InterruptedException e){ 51 | //do nothing, keep waiting 52 | } 53 | } 54 | isLocked = true; 55 | } 56 | } 57 | 58 | public synchronized void unlock(){ 59 | isLocked = false; 60 | this.notify(); 61 | } 62 | 63 | } 64 | 现在检查和设置isLocked条件是在同一个同步块中原子地执行了。 65 | 66 | #####一个更现实的例子 67 | 68 | 也许你会说,我才不可能写这么挫的代码,还觉得slipped conditions是个相当理论的问题。但是第一个简单的例子只是用来更好的展示slipped conditions。 69 | 70 | 饥饿和公平中实现的公平锁也许是个更现实的例子。再看下嵌套管程锁死中那个幼稚的实现,如果我们试图解决其中的嵌套管程锁死问题,很容易产生slipped conditions问题。 首先让我们看下嵌套管程锁死中的例子: 71 | 72 | //Fair Lock implementation with nested monitor lockout problem 73 | 74 | public class FairLock { 75 | private boolean isLocked = false; 76 | private Thread lockingThread = null; 77 | private List waitingThreads = 78 | new ArrayList(); 79 | 80 | public void lock() throws InterruptedException{ 81 | QueueObject queueObject = new QueueObject(); 82 | 83 | synchronized(this){ 84 | waitingThreads.add(queueObject); 85 | 86 | while(isLocked || waitingThreads.get(0) != queueObject){ 87 | 88 | synchronized(queueObject){ 89 | try{ 90 | queueObject.wait(); 91 | }catch(InterruptedException e){ 92 | waitingThreads.remove(queueObject); 93 | throw e; 94 | } 95 | } 96 | } 97 | waitingThreads.remove(queueObject); 98 | isLocked = true; 99 | lockingThread = Thread.currentThread(); 100 | } 101 | } 102 | 103 | public synchronized void unlock(){ 104 | if(this.lockingThread != Thread.currentThread()){ 105 | throw new IllegalMonitorStateException( 106 | "Calling thread has not locked this lock"); 107 | } 108 | isLocked = false; 109 | lockingThread = null; 110 | if(waitingThreads.size() > 0){ 111 | QueueObject queueObject = waitingThread.get(0); 112 | synchronized(queueObject){ 113 | queueObject.notify(); 114 | } 115 | } 116 | } 117 | } 118 | 119 | public class QueueObject {} 120 | 121 | 我们可以看到synchronized(queueObject)及其中的queueObject.wait()调用是嵌在synchronized(this)块里面的,这会导致嵌套管程锁死问题。为避免这个问题,我们必须将synchronized(queueObject)块移出synchronized(this)块。移出来之后的代码可能是这样的: 122 | 123 | //Fair Lock implementation with slipped conditions problem 124 | 125 | public class FairLock { 126 | private boolean isLocked = false; 127 | private Thread lockingThread = null; 128 | private List waitingThreads = 129 | new ArrayList(); 130 | 131 | public void lock() throws InterruptedException{ 132 | QueueObject queueObject = new QueueObject(); 133 | 134 | synchronized(this){ 135 | waitingThreads.add(queueObject); 136 | } 137 | 138 | boolean mustWait = true; 139 | while(mustWait){ 140 | synchronized(this){ 141 | mustWait = isLocked || waitingThreads.get(0) != queueObject; 142 | } 143 | 144 | synchronized(queueObject){ 145 | if(mustWait){ 146 | try{ 147 | queueObject.wait(); 148 | }catch(InterruptedException e){ 149 | waitingThreads.remove(queueObject); 150 | throw e; 151 | } 152 | } 153 | } 154 | } 155 | 156 | synchronized(this){ 157 | waitingThreads.remove(queueObject); 158 | isLocked = true; 159 | lockingThread = Thread.currentThread(); 160 | } 161 | } 162 | } 163 | 164 | 165 | 注意:因为我只改动了lock()方法,这里只展现了lock方法。 166 | 167 | 现在lock()方法包含了3个同步块。 168 | 169 | 第一个,synchronized(this)块通过mustWait = isLocked || waitingThreads.get(0) != queueObject检查内部变量的值。 170 | 171 | 第二个,synchronized(queueObject)块检查线程是否需要等待。也有可能其它线程在这个时候已经解锁了,但我们暂时不考虑这个问题。我们就假设这个锁处在解锁状态,所以线程会立马退出synchronized(queueObject)块。 172 | 173 | 第三个,synchronized(this)块只会在mustWait为false的时候执行。它将isLocked重新设回true,然后离开lock()方法。 174 | 175 | 设想一下,在锁处于解锁状态时,如果有两个线程同时调用lock()方法会发生什么。首先,线程1会检查到isLocked为false,然后线程2同样检查到isLocked为false。接着,它们都不会等待,都会去设置isLocked为true。这就是slipped conditions的一个最好的例子。 176 | 177 | #####解决Slipped Conditions问题 178 | 179 | 要解决上面例子中的slipped conditions问题,最后一个synchronized(this)块中的代码必须向上移到第一个同步块中。为适应这种变动,代码需要做点小改动。下面是改动过的代码: 180 | 181 | //Fair Lock implementation without nested monitor lockout problem, 182 | //but with missed signals problem. 183 | 184 | public class FairLock { 185 | private boolean isLocked = false; 186 | private Thread lockingThread = null; 187 | private List waitingThreads = 188 | new ArrayList(); 189 | 190 | public void lock() throws InterruptedException{ 191 | QueueObject queueObject = new QueueObject(); 192 | 193 | synchronized(this){ 194 | waitingThreads.add(queueObject); 195 | } 196 | 197 | boolean mustWait = true; 198 | while(mustWait){ 199 | synchronized(this){ 200 | mustWait = isLocked || waitingThreads.get(0) != queueObject; 201 | if(!mustWait){ 202 | waitingThreads.remove(queueObject); 203 | isLocked = true; 204 | lockingThread = Thread.currentThread(); 205 | return; 206 | } 207 | } 208 | 209 | synchronized(queueObject){ 210 | if(mustWait){ 211 | try{ 212 | queueObject.wait(); 213 | }catch(InterruptedException e){ 214 | waitingThreads.remove(queueObject); 215 | throw e; 216 | } 217 | } 218 | } 219 | } 220 | } 221 | } 222 | 223 | 224 | 我们可以看到对局部变量mustWait的检查与赋值是在同一个同步块中完成的。还可以看到,即使在synchronized(this)块外面检查了mustWait,在while(mustWait)子句中,mustWait变量从来没有在synchronized(this)同步块外被赋值。当一个线程检查到mustWait是false的时候,它将自动设置内部的条件(isLocked),所以其它线程再来检查这个条件的时候,它们就会发现这个条件的值现在为true了。 225 | 226 | synchronized(this)块中的return;语句不是必须的。这只是个小小的优化。如果一个线程肯定不会等待(即mustWait为false),那么就没必要让它进入到synchronized(queueObject)同步块中和执行if(mustWait)子句了。 227 | 228 | 细心的读者可能会注意到上面的公平锁实现仍然有可能丢失信号。设想一下,当该FairLock实例处于锁定状态时,有个线程来调用lock()方法。执行完第一个 synchronized(this)块后,mustWait变量的值为true。再设想一下调用lock()的线程是通过抢占式的,拥有锁的那个线程那个线程此时调用了unlock()方法,但是看下之前的unlock()的实现你会发现,它调用了queueObject.notify()。但是,因为lock()中的线程还没有来得及调用queueObject.wait(),所以queueObject.notify()调用也就没有作用了,信号就丢失掉了。如果调用lock()的线程在另一个线程调用queueObject.notify()之后调用queueObject.wait(),这个线程会一直阻塞到其它线程调用unlock方法为止,但这永远也不会发生。 229 | 230 | 公平锁实现的信号丢失问题在饥饿和公平一文中我们已有过讨论,把QueueObject转变成一个信号量,并提供两个方法:doWait()和doNotify()。这些方法会在QueueObject内部对信号进行存储和响应。用这种方式,即使doNotify()在doWait()之前调用,信号也不会丢失。 231 | 232 | 233 | 234 | -------------------------------------------------------------------------------- /jvm/Introduction to Class File Format & Byte Code.md: -------------------------------------------------------------------------------- 1 | ###Introduction to Class File Format & Byte Code 2 | 3 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-1-728.jpg?cb=1296527630) 4 | 5 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-2-728.jpg?cb=1296527630) 6 | 7 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-3-728.jpg?cb=1296527630) 8 | 9 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-4-728.jpg?cb=1296527630) 10 | 11 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-5-728.jpg?cb=1296527630) 12 | 13 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-6-728.jpg?cb=1296527630) 14 | 15 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-7-728.jpg?cb=1296527630) 16 | 17 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-8-728.jpg?cb=1296527630) 18 | 19 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-9-728.jpg?cb=1296527630) 20 | 21 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-10-728.jpg?cb=1296527630) 22 | 23 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-11-728.jpg?cb=1296527630) 24 | 25 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-12-728.jpg?cb=1296527630) 26 | 27 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-13-728.jpg?cb=1296527630) 28 | 29 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-14-728.jpg?cb=1296527630) 30 | 31 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-15-728.jpg?cb=1296527630) 32 | 33 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-16-728.jpg?cb=1296527630) 34 | 35 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-17-728.jpg?cb=1296527630) 36 | 37 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-18-728.jpg?cb=1296527630) 38 | 39 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-19-728.jpg?cb=1296527630) 40 | 41 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-20-728.jpg?cb=1296527630) 42 | 43 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-21-728.jpg?cb=1296527630) 44 | 45 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-22-728.jpg?cb=1296527630) 46 | 47 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-23-728.jpg?cb=1296527630) 48 | 49 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-24-728.jpg?cb=1296527630) 50 | 51 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-25-728.jpg?cb=1296527630) 52 | 53 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-26-728.jpg?cb=1296527630) 54 | 55 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-27-728.jpg?cb=1296527630) 56 | 57 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-28-728.jpg?cb=1296527630) 58 | 59 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-29-728.jpg?cb=1296527630) 60 | 61 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-30-728.jpg?cb=1296527630) 62 | 63 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-31-728.jpg?cb=1296527630) 64 | 65 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-32-728.jpg?cb=1296527630) 66 | 67 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-33-728.jpg?cb=1296527630) 68 | 69 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-34-728.jpg?cb=1296527630) 70 | 71 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-35-728.jpg?cb=1296527630) 72 | 73 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-36-728.jpg?cb=1296527630) 74 | 75 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-37-728.jpg?cb=1296527630) 76 | 77 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-38-728.jpg?cb=1296527630) 78 | 79 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-39-728.jpg?cb=1296527630) 80 | 81 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-40-728.jpg?cb=1296527630) 82 | 83 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-41-728.jpg?cb=1296527630) 84 | 85 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-42-728.jpg?cb=1296527630) 86 | 87 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-43-728.jpg?cb=1296527630) 88 | 89 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-44-728.jpg?cb=1296527630) 90 | 91 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-45-728.jpg?cb=1296527630) 92 | 93 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-46-728.jpg?cb=1296527630) 94 | 95 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-47-728.jpg?cb=1296527630) 96 | 97 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-48-728.jpg?cb=1296527630) 98 | 99 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-49-728.jpg?cb=1296527630) 100 | 101 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-50-728.jpg?cb=1296527630) 102 | 103 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-51-728.jpg?cb=1296527630) 104 | 105 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-52-728.jpg?cb=1296527630) 106 | 107 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-53-728.jpg?cb=1296527630) 108 | 109 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-54-728.jpg?cb=1296527630) 110 | 111 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-55-728.jpg?cb=1296527630) 112 | 113 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-56-728.jpg?cb=1296527630) 114 | 115 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-57-728.jpg?cb=1296527630) 116 | 117 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-58-728.jpg?cb=1296527630) 118 | 119 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-59-728.jpg?cb=1296527630) 120 | 121 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-60-728.jpg?cb=1296527630) 122 | 123 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-61-728.jpg?cb=1296527630) 124 | 125 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-62-728.jpg?cb=1296527630) 126 | 127 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-63-728.jpg?cb=1296527630) 128 | 129 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-64-728.jpg?cb=1296527630) 130 | 131 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-65-728.jpg?cb=1296527630) 132 | 133 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-66-728.jpg?cb=1296527630) 134 | 135 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-67-728.jpg?cb=1296527630) 136 | 137 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-68-728.jpg?cb=1296527630) 138 | 139 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-69-728.jpg?cb=1296527630) 140 | 141 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-70-728.jpg?cb=1296527630) 142 | 143 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-71-728.jpg?cb=1296527630) 144 | 145 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-72-728.jpg?cb=1296527630) 146 | 147 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-73-728.jpg?cb=1296527630) 148 | 149 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-74-728.jpg?cb=1296527630) 150 | 151 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-75-728.jpg?cb=1296527630) 152 | 153 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-76-728.jpg?cb=1296527630) 154 | 155 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-77-728.jpg?cb=1296527630) 156 | 157 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-78-728.jpg?cb=1296527630) 158 | 159 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-79-728.jpg?cb=1296527630) 160 | 161 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-80-728.jpg?cb=1296527630) 162 | 163 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-81-728.jpg?cb=1296527630) 164 | 165 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-82-728.jpg?cb=1296527630) 166 | 167 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-83-728.jpg?cb=1296527630) 168 | 169 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-84-728.jpg?cb=1296527630) 170 | 171 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-85-728.jpg?cb=1296527630) 172 | 173 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-86-728.jpg?cb=1296527630) 174 | 175 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-87-728.jpg?cb=1296527630) 176 | 177 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-88-728.jpg?cb=1296527630) 178 | 179 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-89-728.jpg?cb=1296527630) 180 | 181 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-90-728.jpg?cb=1296527630) 182 | 183 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-91-728.jpg?cb=1296527630) 184 | 185 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-92-728.jpg?cb=1296527630) 186 | 187 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-93-728.jpg?cb=1296527630) 188 | 189 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-94-728.jpg?cb=1296527630) 190 | 191 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-95-728.jpg?cb=1296527630) 192 | 193 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-96-728.jpg?cb=1296527630) 194 | 195 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-97-728.jpg?cb=1296527630) 196 | 197 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-98-728.jpg?cb=1296527630) 198 | 199 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-99-728.jpg?cb=1296527630) 200 | 201 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-100-728.jpg?cb=1296527630) 202 | 203 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-101-728.jpg?cb=1296527630) 204 | 205 | ![Class File Format](http://image.slidesharecdn.com/bytecode-101023205825-phpapp02/95/introduction-to-class-file-format-byte-code-102-728.jpg?cb=1296527630) 206 | 207 | -------------------------------------------------------------------------------- /jvm/JVM Internals - Garbage Collection & Runtime Optimizations.md: -------------------------------------------------------------------------------- 1 | ###JVM Internals - Garbage Collection & Runtime Optimizations 2 | 3 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-1-728.jpg?cb=1296527665) 4 | 5 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-2-728.jpg?cb=1296527665) 6 | 7 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-3-728.jpg?cb=1296527665) 8 | 9 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-4-728.jpg?cb=1296527665) 10 | 11 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-5-728.jpg?cb=1296527665) 12 | 13 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-6-728.jpg?cb=1296527665) 14 | 15 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-7-728.jpg?cb=1296527665) 16 | 17 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-8-728.jpg?cb=1296527665) 18 | 19 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-9-728.jpg?cb=1296527665) 20 | 21 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-10-728.jpg?cb=1296527665) 22 | 23 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-11-728.jpg?cb=1296527665) 24 | 25 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-12-728.jpg?cb=1296527665) 26 | 27 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-13-728.jpg?cb=1296527665) 28 | 29 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-14-728.jpg?cb=1296527665) 30 | 31 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-15-728.jpg?cb=1296527665) 32 | 33 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-16-728.jpg?cb=1296527665) 34 | 35 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-17-728.jpg?cb=1296527665) 36 | 37 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-18-728.jpg?cb=1296527665) 38 | 39 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-19-728.jpg?cb=1296527665) 40 | 41 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-20-728.jpg?cb=1296527665) 42 | 43 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-21-728.jpg?cb=1296527665) 44 | 45 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-22-728.jpg?cb=1296527665) 46 | 47 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-23-728.jpg?cb=1296527665) 48 | 49 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-24-728.jpg?cb=1296527665) 50 | 51 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-25-728.jpg?cb=1296527665) 52 | 53 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-26-728.jpg?cb=1296527665) 54 | 55 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-27-728.jpg?cb=1296527665) 56 | 57 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-28-728.jpg?cb=1296527665) 58 | 59 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-29-728.jpg?cb=1296527665) 60 | 61 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-30-728.jpg?cb=1296527665) 62 | 63 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-31-728.jpg?cb=1296527665) 64 | 65 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-32-728.jpg?cb=1296527665) 66 | 67 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-33-728.jpg?cb=1296527665) 68 | 69 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-33-728.jpg?cb=1296527665) 70 | 71 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-34-728.jpg?cb=1296527665) 72 | 73 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-35-728.jpg?cb=1296527665) 74 | 75 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-36-728.jpg?cb=1296527665) 76 | 77 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-37-728.jpg?cb=1296527665) 78 | 79 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-38-728.jpg?cb=1296527665) 80 | 81 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-39-728.jpg?cb=1296527665) 82 | 83 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-40-728.jpg?cb=1296527665) 84 | 85 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-41-728.jpg?cb=1296527665) 86 | 87 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-42-728.jpg?cb=1296527665) 88 | 89 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-43-728.jpg?cb=1296527665) 90 | 91 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-44-728.jpg?cb=1296527665) 92 | 93 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-45-728.jpg?cb=1296527665) 94 | 95 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-46-728.jpg?cb=1296527665) 96 | 97 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-47-728.jpg?cb=1296527665) 98 | 99 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-48-728.jpg?cb=1296527665) 100 | 101 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-49-728.jpg?cb=1296527665) 102 | 103 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-50-728.jpg?cb=1296527665) 104 | 105 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-51-728.jpg?cb=1296527665) 106 | 107 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-52-728.jpg?cb=1296527665) 108 | 109 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-53-728.jpg?cb=1296527665) 110 | 111 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-54-728.jpg?cb=1296527665) 112 | 113 | ![jvm](http://image.slidesharecdn.com/jvminternalskeynote-101023204818-phpapp01/95/jvm-internals-garbage-collection-runtime-optimizations-55-728.jpg?cb=1296527665) 114 | -------------------------------------------------------------------------------- /jvm/JVM Mechanics When Does the JVM JIT & Deoptimize.md: -------------------------------------------------------------------------------- 1 | ###JVM Mechanics: When Does the JVM JIT & Deoptimize? 2 | 3 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-1-638.jpg?cb=1424382217) 4 | 5 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-2-638.jpg?cb=1424382217) 6 | 7 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-3-638.jpg?cb=1424382217) 8 | 9 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-4-638.jpg?cb=1424382217) 10 | 11 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-5-638.jpg?cb=1424382217) 12 | 13 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-6-638.jpg?cb=1424382217) 14 | 15 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-7-638.jpg?cb=1424382217) 16 | 17 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-8-638.jpg?cb=1424382217) 18 | 19 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-9-638.jpg?cb=1424382217) 20 | 21 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-10-638.jpg?cb=1424382217) 22 | 23 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-11-638.jpg?cb=1424382217) 24 | 25 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-12-638.jpg?cb=1424382217) 26 | 27 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-13-638.jpg?cb=1424382217) 28 | 29 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-14-638.jpg?cb=1424382217) 30 | 31 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-15-638.jpg?cb=1424382217) 32 | 33 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-16-638.jpg?cb=1424382217) 34 | 35 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-17-638.jpg?cb=1424382217) 36 | 37 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-18-638.jpg?cb=1424382217) 38 | 39 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-19-638.jpg?cb=1424382217) 40 | 41 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-20-638.jpg?cb=1424382217) 42 | 43 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-21-638.jpg?cb=1424382217) 44 | 45 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-22-638.jpg?cb=1424382217) 46 | 47 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-23-638.jpg?cb=1424382217) 48 | 49 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-24-638.jpg?cb=1424382217) 50 | 51 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-25-638.jpg?cb=1424382217) 52 | 53 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-26-638.jpg?cb=1424382217) 54 | 55 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-27-638.jpg?cb=1424382217) 56 | 57 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-28-638.jpg?cb=1424382217) 58 | 59 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-29-638.jpg?cb=1424382217) 60 | 61 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-30-638.jpg?cb=1424382217) 62 | 63 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-31-638.jpg?cb=1424382217) 64 | 65 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-32-638.jpg?cb=1424382217) 66 | 67 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-33-638.jpg?cb=1424382217) 68 | 69 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-34-638.jpg?cb=1424382217) 70 | 71 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-35-638.jpg?cb=1424382217) 72 | 73 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-36-638.jpg?cb=1424382217) 74 | 75 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-37-638.jpg?cb=1424382217) 76 | 77 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-38-638.jpg?cb=1424382217) 78 | 79 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-39-638.jpg?cb=1424382217) 80 | 81 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-40-638.jpg?cb=1424382217) 82 | 83 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-41-638.jpg?cb=1424382217) 84 | 85 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-42-638.jpg?cb=1424382217) 86 | 87 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-43-638.jpg?cb=1424382217) 88 | 89 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-44-638.jpg?cb=1424382217) 90 | 91 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-45-638.jpg?cb=1424382217) 92 | 93 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-46-638.jpg?cb=1424382217) 94 | 95 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-47-638.jpg?cb=1424382217) 96 | 97 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-48-638.jpg?cb=1424382217) 98 | 99 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-49-638.jpg?cb=1424382217) 100 | 101 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-50-638.jpg?cb=1424382217) 102 | 103 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-51-638.jpg?cb=1424382217) 104 | 105 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-52-638.jpg?cb=1424382217) 106 | 107 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-53-638.jpg?cb=1424382217) 108 | 109 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-54-638.jpg?cb=1424382217) 110 | 111 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-55-638.jpg?cb=1424382217) 112 | 113 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-56-638.jpg?cb=1424382217) 114 | 115 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-57-638.jpg?cb=1424382217) 116 | 117 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-58-638.jpg?cb=1424382217) 118 | 119 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-59-638.jpg?cb=1424382217) 120 | 121 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-60-638.jpg?cb=1424382217) 122 | 123 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-61-638.jpg?cb=1424382217) 124 | 125 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-62-638.jpg?cb=1424382217) 126 | 127 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-63-638.jpg?cb=1424382217) 128 | 129 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-64-638.jpg?cb=1424382217) 130 | 131 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-65-638.jpg?cb=1424382217) 132 | 133 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-66-638.jpg?cb=1424382217) 134 | 135 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-67-638.jpg?cb=1424382217) 136 | 137 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-68-638.jpg?cb=1424382217) 138 | 139 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-69-638.jpg?cb=1424382217) 140 | 141 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-70-638.jpg?cb=1424382217) 142 | 143 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-71-638.jpg?cb=1424382217) 144 | 145 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-72-638.jpg?cb=1424382217) 146 | 147 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-73-638.jpg?cb=1424382217) 148 | 149 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-74-638.jpg?cb=1424382217) 150 | 151 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-75-638.jpg?cb=1424382217) 152 | 153 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-76-638.jpg?cb=1424382217) 154 | 155 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-77-638.jpg?cb=1424382217) 156 | 157 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-78-638.jpg?cb=1424382217) 158 | 159 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-79-638.jpg?cb=1424382217) 160 | 161 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-80-638.jpg?cb=1424382217) 162 | 163 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-81-638.jpg?cb=1424382217) 164 | 165 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-82-638.jpg?cb=1424382217) 166 | 167 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-83-638.jpg?cb=1424382217) 168 | 169 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-84-638.jpg?cb=1424382217) 170 | 171 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-85-638.jpg?cb=1424382217) 172 | 173 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-86-638.jpg?cb=1424382217) 174 | 175 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-87-638.jpg?cb=1424382217) 176 | 177 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-88-638.jpg?cb=1424382217) 178 | 179 | ![understanding GC](http://image.slidesharecdn.com/jvmmechanics-150219153921-conversion-gate01/95/jvm-mechanics-when-does-the-jvm-jit-deoptimize-89-638.jpg?cb=1424382217) 180 | 181 | -------------------------------------------------------------------------------- /jvm/JVM线程池发展趋势.md: -------------------------------------------------------------------------------- 1 | ###JVM线程池发展趋势 2 | 多线程已经成为大多数开发者的兴趣所在了。他们努力尝试想找出最优策略来解决这个问题。过去已经有各种尝试去标准化这些方案。特别是随着大数据,实时分析等问题领域的兴起,又存在着新的挑战。在这个方向需要走的一步是“Doug Lea”的作品(一部巨作),以并发框架(JSR 166)的形式提供给我们。 3 | 4 | 现在开始区分并发和并行性。这些只是不同的策略,而且市面上有很多框架提供,都能帮我们达到相同的目的。但在选择的时候如果能同时知道他们内部的实现细节对我们也是大有好处的。本文将要探究JVM中线程池和线程共享的一些稳定有效的选项。当然,随着多核处理器的广泛使用,新的问题也随之而来。开发人员也开始思考利用高级硬件的“mechanical sympathy”(译者注:表示底层硬件的运作方式以及与硬件运行方式协同的软件编程)来提高性能。 5 | 6 | 个人以为,当讨论线程池时,目前广泛应用的主要有下述机制: 7 | 8 | ####Executor框架提供的线程。 9 | 10 | LMAX的Ring Buffer概念 (译者注:Ring Buffer即环形缓冲,LMAX是一种新型零售金融交易平台,其框架能以低延迟产生大量交易,LMAX建立在JVM平台上。 11 | 基于Actor(事件)的实现。 12 | 并发框架下的线程池选项: 13 | 14 | 首先,我个人不赞同使用当下流行的线程池概念,而应该使用工作队列的概念。简而言之,在一个执行框架可供选择的各种不同选项都是基于某种顺序数据结构,如数组或队列(阻塞或非阻塞)之类的,比如ConcurrentLinkedQueue(并发链式队列),ArrayBlockingQueue(数组阻塞队列), LinkedBlockingQueue(链式阻塞队列)等等。文档表明,尽管它们的使用环境各不相同,但他们隐含的本质/数据结构有相同的性质,如顺序插入和遍历。 15 | 16 | ![jvm](http://static.codeceo.com/images/2015/04/28c8edde3d61a0411511d3b1866f0636.jpg) 17 | 18 | 优势: 19 | 20 | 减少线程创建导致的延迟 21 | 通过优化线程数量,可以解决资源不足的问题。 22 | 这些可以使应用程序和服务器应用响应更快。使用线程池看似一个很不错的解决方案但是却有一个根本性的缺陷:连续争用问题。这里是Java中关于一些并发框架下线程池选项的讨论。 23 | 24 | ####Disruptor(环形缓冲): 25 | 26 | (LMAX的一个基于环形缓冲区的高性能进程间消息库)LMAX的开发人员使用一个干扰框架来解决连续争用问题,这个框架是基于一个叫环形缓冲的数据结构。它可能是线程间发送消息的最有效方式了。它是队列的一种替代实现方式,但它又和SEDA和Actors(译者注:这两种都是和Disruptor类似的并发模型)有一些共同特征。向Disruptor中放入消息需要两步,第一步申请一个环形缓冲的槽位,槽位可为用户提供写对应数据的记录。然后需要提交该条记录,为了能灵活使用内存,2步法是必须的。只有经过提交,这条消息才能对消费者线程可见。下图描述了环状缓冲这个数据结构(Disruptor的核心): 27 | 28 | (译者注:LMAX的核心是一个业务逻辑处理器,而该业务逻辑处理器的核心是Disruptor,这是一个并发组件,能够在无锁的情况下实现网络的Queue并发操作) 29 | 30 | ![disruptor](http://static.codeceo.com/images/2015/04/665f644e43731ff9db3d341da5c827e1.png) 31 | 32 | Disruptor在多核平台上能达到很低的延迟同时又有高吞吐量,尽管线程程间需要共享数据以及传递消息。 33 | 34 | 它的独特之处在于锁和免争用结构。它甚至不使用CAS或内存保护。若想了解关于它的更多细节,这里有一篇不错的文章和官网。使用Disruptor的一个缺点(事实上也算不上缺点)是,你需要提前告知Disruptor应用程序完成任务所需要的线程数。 35 | 36 | ####基于事件: 37 | 38 | 对于传统线程池机制,一个强大的替代方案就是基于事件模型。这种基于事件的线程轮询/线程池/线程调度机制在函数式编程中很常见。关于这个概念的一个非常流行的实现是基于actor的系统(译者注:Scala的并发系统),Akka已成为其实际上的标准。(译者注:Akka,一种善于处理进程间通信的框架) 39 | 40 | Actors是非常轻量级的并发实体。它们使用一种事件驱动的接收循环来异步处理消息。消息模式匹配可以很方便地表述一个actor的行为。它们提高了抽象级别从而使写,测试,理解和维护并发/分布式系统更加容易。让你专注于工作流——消息如何流入系统——而不是低层次的基本概念如线程,锁以及套接字IO。一个线程可以分配多个或单个actor,而且两种模型都是依需要选择的。 41 | 42 | 像Akka这种基于actor的系统的优势有如下所列: 43 | 44 | **可封装** 45 | 46 | **可监督** 47 | 48 | **可配置执行** 49 | 50 | **位置透明** 51 | 52 | **重试机制** 53 | 54 | 注:调试一个基于actor的系统是一个非常艰难的事情。 55 | 56 | Disruptor使用一个线程一个消费者模式,不同于Actors使用N个线程M个消费者模式。比如,你可以拥有任意多的actors,然后它们会被分散到一些数目固定的线程中(通常是一个核一个线程),至于其他的部分,actor模型就和Disruptor模型差不多了;特别是用于批处理的时候。 57 | 58 | 我最初在因特网上搜多到的答案也说明开源空间中关于确定JVM选项基准的贡献还是有一些的。其中一个选项是ExecutorBenchmarkt。它是一个并行任务的开源测试框架。它是用Scala编写的,但是可以用于Java和Scala负载。 59 | 60 | 简而言之,快速发展的软硬件行业在呈现新挑战给我们的同时也提供了大量解决应用程序容错性和响应性的方法。对于不可预知的少量线程,我建议使用JDK并发框架中的线程池机制。对于大量规模相似的任务,个人建议使用Disruptor。Disruptor的确是有一点学习曲线,但在性能和扩展性方面的收获远远值得投入的学习时间。在应用程序需要某种重试或管理机制,以及分布式任务时,建议使用Akka的Actor模型。尽管结果还有可能被其它因素所影响,你还是会选择map reduce或fork/join模型或是其它自定义实现一个分布式应用程序。 61 | 62 | ####Link 63 | 64 | [LMAX Disruptor](http://lmax-exchange.github.io/disruptor/) 65 | 66 | [Dissecting the Disruptor: What's so special about a ring buffer?](http://mechanitis.blogspot.in/2011/06/dissecting-disruptor-whats-so-special.html) 67 | 68 | [Executor Benchmark](https://github.com/mslinn/ExecutorBenchmark) 69 | -------------------------------------------------------------------------------- /jvm/Understanding Garbage Collection.md: -------------------------------------------------------------------------------- 1 | ###Understanding Garbage Collection 2 | 3 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-1-638.jpg?cb=1373662923) 4 | 5 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-2-638.jpg?cb=1373662923) 6 | 7 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-3-638.jpg?cb=1373662923) 8 | 9 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-4-638.jpg?cb=1373662923) 10 | 11 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-5-638.jpg?cb=1373662923) 12 | 13 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-6-638.jpg?cb=1373662923) 14 | 15 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-7-638.jpg?cb=1373662923) 16 | 17 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-8-638.jpg?cb=1373662923) 18 | 19 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-9-638.jpg?cb=1373662923) 20 | 21 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-10-638.jpg?cb=1373662923) 22 | 23 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-11-638.jpg?cb=1373662923) 24 | 25 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-12-638.jpg?cb=1373662923) 26 | 27 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-13-638.jpg?cb=1373662923) 28 | 29 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-14-638.jpg?cb=1373662923) 30 | 31 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-15-638.jpg?cb=1373662923) 32 | 33 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-16-638.jpg?cb=1373662923) 34 | 35 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-17-638.jpg?cb=1373662923) 36 | 37 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-18-638.jpg?cb=1373662923) 38 | 39 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-19-638.jpg?cb=1373662923) 40 | 41 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-20-638.jpg?cb=1373662923) 42 | 43 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-21-638.jpg?cb=1373662923) 44 | 45 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-22-638.jpg?cb=1373662923) 46 | 47 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-23-638.jpg?cb=1373662923) 48 | 49 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-24-638.jpg?cb=1373662923) 50 | 51 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-25-638.jpg?cb=1373662923) 52 | 53 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-26-638.jpg?cb=1373662923) 54 | 55 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-27-638.jpg?cb=1373662923) 56 | 57 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-28-638.jpg?cb=1373662923) 58 | 59 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-29-638.jpg?cb=1373662923) 60 | 61 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-30-638.jpg?cb=1373662923) 62 | 63 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-31-638.jpg?cb=1373662923) 64 | 65 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-32-638.jpg?cb=1373662923) 66 | 67 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-33-638.jpg?cb=1373662923) 68 | 69 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-34-638.jpg?cb=1373662923) 70 | 71 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-35-638.jpg?cb=1373662923) 72 | 73 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-36-638.jpg?cb=1373662923) 74 | 75 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-37-638.jpg?cb=1373662923) 76 | 77 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-38-638.jpg?cb=1373662923) 78 | 79 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-39-638.jpg?cb=1373662923) 80 | 81 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-40-638.jpg?cb=1373662923) 82 | 83 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-41-638.jpg?cb=1373662923) 84 | 85 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-42-638.jpg?cb=1373662923) 86 | 87 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-43-638.jpg?cb=1373662923) 88 | 89 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-44-638.jpg?cb=1373662923) 90 | 91 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-45-638.jpg?cb=1373662923) 92 | 93 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-46-638.jpg?cb=1373662923) 94 | 95 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-47-638.jpg?cb=1373662923) 96 | 97 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-48-638.jpg?cb=1373662923) 98 | 99 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-49-638.jpg?cb=1373662923) 100 | 101 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-50-638.jpg?cb=1373662923) 102 | 103 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-51-638.jpg?cb=1373662923) 104 | 105 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-52-638.jpg?cb=1373662923) 106 | 107 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-53-638.jpg?cb=1373662923) 108 | 109 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-54-638.jpg?cb=1373662923) 110 | 111 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-55-638.jpg?cb=1373662923) 112 | 113 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-56-638.jpg?cb=1373662923) 114 | 115 | ![understanding GC](http://image.slidesharecdn.com/understandinggarbagecollection-handout-130712155850-phpapp01/95/understanding-garbage-collection-57-638.jpg?cb=1373662923) 116 | 117 | -------------------------------------------------------------------------------- /信号量.md: -------------------------------------------------------------------------------- 1 | ###信号量 2 | Semaphore(信号量) 是一个线程同步结构,用于在线程间传递信号,以避免出现信号丢失(译者注:下文会具体介绍),或者像锁一样用于保护一个关键区域。自从5.0开始,jdk在java.util.concurrent包里提供了Semaphore 的官方实现,因此大家不需要自己去实现Semaphore。但是还是很有必要去熟悉如何使用Semaphore及其背后的原理 3 | 4 | ####简单的信号量 5 | 6 | 下面是一个简单的Semaphore实现: 7 | 8 | public class Semaphore{ 9 | private boolean signal = false; 10 | 11 | public synchronized void take(){ 12 | this.signal = true; 13 | this.notify(); 14 | } 15 | 16 | public synchronized void release()throws InterruptedException{ 17 | while(!this.signal) 18 | wait(); 19 | this.signal = false; 20 | } 21 | } 22 | 23 | Take方法发出一个被存放在Semaphore内部的信号,而Release方法则等待一个信号,当其接收到信号后,标记位signal被清空,然后该方法终止。 24 | 25 | 使用这个semaphore可以避免错失某些信号通知。用take方法来代替notify,release方法来代替wait。如果某线程在调用release等待之前调用take方法,那么调用release方法的线程仍然知道take方法已经被某个线程调用过了,因为该Semaphore内部保存了take方法发出的信号。而wait和notify方法就没有这样的功能。 26 | 27 | 当用semaphore来产生信号时,take和release这两个方法名看起来有点奇怪。这两个名字来源于后面把semaphore当做锁的例子,后面会详细介绍这个例子,在该例子中,take和release这两个名字会变得很合理。 28 | 29 | ####使用Semaphores产生信号 30 | 31 | 下面是一个两个线程使用Semaphore互相发送信号的简单示例: 32 | 33 | Semaphore semaphore = new Semaphore(); 34 | 35 | SendingThread sender = new SendingThread(semaphore); 36 | 37 | ReceivingThread receiver = new ReceivingThread(semsphore); 38 | 39 | receiver.start(); 40 | sender.start(); 41 | 42 | 43 | public class SendingThread{ 44 | Semaphore semaphore = null; 45 | 46 | public SendingThread(Semaphore semaphore){ 47 | this.semaphore = semaphore; 48 | } 49 | 50 | public void run(){ 51 | while(true){ 52 | //do something, then signal 53 | this.semaphore.take(); 54 | } 55 | } 56 | } 57 | 58 | 59 | 60 | 61 | public class RecevingThread{ 62 | Semaphore semaphore = null; 63 | 64 | public ReceivingThread(Semaphore semaphore){ 65 | this.semaphore = semaphore; 66 | } 67 | 68 | public void run(){ 69 | while(true){ 70 | this.semaphore.release(); 71 | //receive signal, then do something 72 | } 73 | } 74 | } 75 | 76 | ####可计数的Semaphore 77 | 78 | 上面的Semaphore的实现没有计算调用take()方法发送的信号的个数。我们可以修改Semaphore来达到这个目的。这被称作可计数的Semaphore。下面是一个可计数的Semaphore的简单实现: 79 | 80 | public class CountingSemaphore{ 81 | private int signals = 0; 82 | 83 | public synchronized void take(){ 84 | this.signals++; 85 | this.notify(); 86 | } 87 | 88 | public synchronized void release()throws InterruptedException{ 89 | while(this.signals == 0) 90 | wait(); 91 | this.signals--; 92 | } 93 | } 94 | 95 | ####有上限的Semaphore 96 | 97 | CountingSemaphore对于可以存储多少信号并没有上限。我们可以修改这个semaphore来让它用于上限,像这样: 98 | 99 | public class BoundedSemaphore{ 100 | private int signals = 0; 101 | private int bound = 0; 102 | 103 | public BoundedSemaphore(int upperBound){ 104 | this.bound = upperBound; 105 | } 106 | 107 | public synchronized void take()throws InterruptedException{ 108 | while(this.signals == upper) 109 | wait(); 110 | this.signals++; 111 | this.notify(); 112 | } 113 | 114 | public synchronized void release()throws InterruptedException{ 115 | while(this.signals == 0) 116 | wait(); 117 | this.signals--; 118 | this.notify(); 119 | } 120 | } 121 | 122 | 如果信号量达到上限take()就会阻塞。如果BoundedSemaphore已经达到了信号量上限限制,不用等到一个线程调用release()方法,这个线程就可以调用take()方法发送信息号。 123 | 124 | ####把Semaphore当做锁来使用 125 | 126 | 我们可以把一个有上限的Semaphore当做锁来使用。我们可以把信号量的上限设为1,通过调用take()release()来保护临界区。下面是一个例子: 127 | 128 | BoundedSemaphore semaphore = new BoundedSemaphore(1); 129 | ... 130 | semaphore.take(); 131 | 132 | try{ 133 | //critical section 134 | }finally{ 135 | semaphore.release(); 136 | } 137 | 138 | 在前面的例子中,Semaphore被用来在多个线程之间传递信号,这种情况下,take和release分别被不同的线程调用。但是在锁这个例子中,take和release方法将被同一线程调用,因为只允许一个线程来获取信号(允许进入关键区域的信号),其它调用take方法获取信号的线程将被阻塞,知道第一个调用take方法的线程调用release方法来释放信号。对release方法的调用永远不会被阻塞,这是因为任何一个线程都是先调用take方法,然后再调用release。 139 | 140 | 通过有上限的Semaphore可以限制进入某代码块的线程数量。设想一下,在上面的例子中,如果BoundedSemaphore 上限设为5将会发生什么?意味着允许5个线程同时访问关键区域,但是你必须保证,这个5个线程不会互相冲突。否则你的应用程序将不能正常运行。 141 | 142 | 必须注意,release方法应当在finally块中被执行。这样可以保在关键区域的代码抛出异常的情况下,信号也一定会被释放。 143 | 144 | -------------------------------------------------------------------------------- /内置监视器失效.md: -------------------------------------------------------------------------------- 1 | ####内置监视器失效 2 | #####内置监视器失效是如何发生的 3 | 内置监视器失效(nested monitor lockout)是类似于死锁的问题。 4 | 5 | Thread 1 synchronizes on A 6 | Thread 1 synchronizes on B (while synchronized on A) 7 | Thread 1 decides to wait for a signal from another thread before continuing 8 | Thread 1 calls B.wait() thereby releasing the lock on B, but not A. 9 | 10 | Thread 2 needs to lock both A and B (in that sequence) 11 | to send Thread 1 the signal. 12 | Thread 2 cannot lock A, since Thread 1 still holds the lock on A. 13 | Thread 2 remain blocked indefinately waiting for Thread1 14 | to release the lock on A 15 | 16 | Thread 1 remain blocked indefinately waiting for the signal from 17 | Thread 2, thereby 18 | never releasing the lock on A, that must be released to make 19 | it possible for Thread 2 to send the signal to Thread 1, etc. 20 | 21 | 上面描述的可能更像一个理论场景,但是,看下面这个简单的Lock实现: 22 | 23 | //lock implementation with nested monitor lockout problem 24 | 25 | public class Lock{ 26 | protected MonitorObject monitorObject = new MonitorObject(); 27 | protected boolean isLock = false; 28 | 29 | public void lock() throws InterruptedException{ 30 | synchronized(this){ 31 | while(isLocked){ 32 | synchronized(this.monitorObject){ 33 | this.monitorObject.wait(); 34 | } 35 | } 36 | 37 | isLocked = true; 38 | } 39 | } 40 | 41 | public void unlock(){ 42 | synchronized(this){ 43 | this.isLocked = false; 44 | synchronized(this.monitorObject){ 45 | this.monitorObject.notify(); 46 | } 47 | } 48 | } 49 | } 50 | 51 | lock()方法首先在"this"上同步,然后在monitorObject傻上同步。如果isLocked为true,将不会有什么问题。线程不调用monitorObject.wait()。如果isLocked为true,线程调用lock()将会等待monitorObject.wait()调用。 52 | 53 | 问题就出在这里,调用monitorObject.wait()仅释放在monitorObject成员变量上的同步监视器,而没有释放关联在"this"上的同步监视器。换句话说,正在等待的那个线程任然持有在"this"对象上的同步锁。 54 | 55 | 当首次锁住Lock的线程准备通过调用unlock()方法来释放它时,它将会在尝试进入unlock()方法的synchronized(this)代码块时阻塞住。它将会一直阻塞直到等待在lock()里的线程离开synzhronized(this)代码块。但是等待在lock()方法里的线程并不会离开这个代码块,除非isLocked被设置为falsemonitorObject.notify()被执行,这些都发生在unlock()方法中。 56 | 57 | Put shortly, the thread waiting in lock() needs an unlock() call to execute successfully for it to exit lock() and the synchronized blocks inside it. But, no thread can actually execute unlock() until the thread waiting in lock() leaves the outer synchronized block. 58 | 59 | 最后的结果就是,任何一个调用lock()或者unlock()线程都会一直阻塞。这被称为 nested monitor lockout。 60 | 61 | #####一个更真实的例子 62 | 63 | 你可能生成你从来不会去实现像上面那样的锁。你不会在一个内部的锁对象上调用wait()notify()方法。但是,还是有可能碰到这样的情况。例如,如果你在一个锁里实现了公平性。当这么做时,你想每个线程在它们自己的队列对象上调用wait方法,这样一来你就可以唤醒某一个线程。 64 | 65 | 看下面这个一个简单锁的简单实现: 66 | 67 | //Fair Lock implementation with nested monitor lockout problem 68 | 69 | public class FairLock{ 70 | private boolean isLocked = false; 71 | private Thread lockingThread = null; 72 | private List waitingThreads = new ArrayList(); 73 | 74 | public void lock()throws InterruptedException{ 75 | QueueObject queueObject = new QueueObject(); 76 | 77 | aynchronized(this){ 78 | waitingThreads.add(queueObject); 79 | 80 | while(isLocked || waitingThreads.get(0) != queueObject){ 81 | synchronized(queueObject){ 82 | try{ 83 | queueObject.wait() 84 | }catch(InterruptedException e){ 85 | waitingThreads.remove(queueObject); 86 | throw e; 87 | } 88 | } 89 | } 90 | 91 | waitingThreads.remove(queueObject); 92 | isLocked = true; 93 | lockingThread = Thread.currentThread(); 94 | } 95 | } 96 | 97 | public synchronized void unlock(){ 98 | if(this.lockingThread != Thread.currentThread()){ 99 | throw new IllegalMonitorStateException("Calling thread has not locks this lock"); 100 | 101 | } 102 | 103 | isLocked = false; 104 | lockingThread = null; 105 | if(waitingThreads.size() > 0){ 106 | QueueObjct queueObject = waitingThreads.get(0); 107 | synchronized(queueObject){ 108 | queueObect.notify(); 109 | } 110 | } 111 | } 112 | } 113 | 114 | 115 | public class QueueObject{ 116 | private boolean isNotify = false; 117 | 118 | public synzhronized void doWait() throws InterruptedException{ 119 | if(!isNotify){ 120 | this.isNotify = false; 121 | } 122 | 123 | public synchronized void doNotify(){ 124 | this.isNoitfy = true; 125 | this.notify(); 126 | } 127 | 128 | public boolean equals(Object o){ 129 | return (this == o); 130 | } 131 | } 132 | 133 | 首先,浏览一下这个实现可能看起来挺好的,但是注意lock()方法是如何调用queueObject.wait()的。queueObject.wait()调用发生在两个同步代码块中。一个是在“this”上的同步,乞讨在它里面的是一个在queueObject局部变量上的同步代码块。当一个线程调用queueObject.wait()时,它释放在queueObject上的锁。但不是与"this"关联的锁。 134 | 135 | 同时,我们也注意到,unlock()方法被声明为synchronized,等同于一个synchronized(this)代码块。这意味着,如果一个线程在lock()方法里正在等待与“this”关联的监视器对象就会被这个阻塞线程等待。所有调用unlock()方法就会一直阻塞等待那个正在等待的线程释放掉在“this”上的锁。但这永远也不会发生,因为这仅仅在一个线程成功向那个正在等待的线程发送一个信号才会发生,而只有在执行unlock()方法时才会发送这样的信号。 136 | 137 | 综上所述,上面公平锁的实现可能会导致内置监视器失效(nested monitor lockout)。 138 | 139 | #####内置监视器失效 vs. 死锁 140 | 141 | 内置监视器失效和死锁的结果趋近于相同:线程间彼此一直等待。 142 | 143 | 这两种场景也不完全相同。当两个线程以不同的顺序去获取锁时就会发生死锁,线程1锁住A,等待B。线程2已经锁住B,但是现在等待A。我们可以通过维护锁的顺序来避免死锁。然而,当两个线程以相同的顺序获得锁时就会发生内置监视器失效。线程1锁住了A和B,然后等待一个来自线程B的信号释放B。线程2又需要同时持有A和B才能向线程1发信号。因此,一个线程在等待信号,另一个在等待锁被释放。 144 | 145 | 差异总结如下: 146 | 147 | In deadlock, two threads are waiting for each other to release locks. 148 | 149 | In nested monitor lockout, Thread 1 is holding a lock A, and waits 150 | for a signal from Thread 2. Thread 2 needs the lock A to send the 151 | signal to Thread 1. 152 | 153 | -------------------------------------------------------------------------------- /剖析同步器.md: -------------------------------------------------------------------------------- 1 | ###剖析同步器 2 | 虽然许多同步器(如锁,信号量,阻塞队列等)功能上各不相同,但它们的内部设计上却差别不大。换句话说,它们内部的的基础部分是相同(或相似)的。了解这些基础部件能在设计同步器的时候给我们大大的帮助。这就是本文要细说的内容。 3 | 4 | 注:本文的内容是哥本哈根信息技术大学一个由Jakob Jenkov,Toke Johansen和Lars Bjørn参与的M.Sc.学生项目的部分成果。在此项目期间我们咨询Doug Lea是否知道类似的研究。有趣的是在开发Java 5并发工具包期间他已经提出了类似的结论。Doug Lea的研究,我相信,在《Java Concurrency in Practice》一书中有描述。这本书有一章“剖析同步器”就类似于本文,但不尽相同。 5 | 6 | 大部分同步器都是用来保护某个区域(临界区)的代码,这些代码可能会被多线程并发访问。要实现这个目标,同步器一般要支持下列功能: 7 | 8 | - **状态** 9 | - **访问条件** 10 | - **状态变化** 11 | - **通知策略** 12 | - **Test-and-Set方法** 13 | - **Set方法** 14 | 15 | 并不是所有同步器都包含上述部分,也有些并不完全遵照上面的内容。但通常你能从中发现这些部分的一或多个。 16 | 17 | ####状态 18 | 19 | 同步器中的状态是用来确定某个线程是否有访问权限。在Lock中,状态是boolean类型的,表示当前Lock对象是否处于锁定状态。在BoundedSemaphore中,内部状态包含一个计数器(int类型)和一个上限(int类型),分别表示当前已经获取的许可数和最大可获取的许可数。BlockingQueue的状态是该队列中元素列表以及队列的最大容量。 20 | 21 | 下面是Lock和BoundedSemaphore中的两个代码片段。 22 | 23 | public class Lock{ 24 | //state is kept here 25 | private boolean isLocked = false; 26 | public synchronized void lock() 27 | throws InterruptedException{ 28 | while(isLocked){ 29 | wait(); 30 | } 31 | isLocked = true; 32 | } 33 | ... 34 | } 35 | 36 | 37 | public class BoundedSemaphore { 38 | //state is kept here 39 | private int signals = 0; 40 | private int bound = 0; 41 | 42 | public BoundedSemaphore(int upperBound){ 43 | this.bound = upperBound; 44 | } 45 | public synchronized void take() throws InterruptedException{ 46 | while(this.signals == bound) wait(); 47 | this.signal++; 48 | this.notify(); 49 | } 50 | ... 51 | } 52 | 53 | ####访问条件 54 | 55 | 访问条件决定调用test-and-set-state方法的线程是否可以对状态进行设置。访问条件一般是基于同步器状态的。通常是放在一个while循环里,以避免虚假唤醒问题。访问条件的计算结果要么是true要么是false。 56 | 57 | Lock中的访问条件只是简单地检查isLocked的值。根据执行的动作是“获取”还是“释放”,BoundedSemaphore中实际上有两个访问条件。如果某个线程想“获取”许可,将检查signals变量是否达到上限;如果某个线程想“释放”许可,将检查signals变量是否为0。 58 | 59 | 这里有两个来自Lock和BoundedSemaphore的代码片段,它们都有访问条件。注意观察条件是怎样在while循环中检查的。 60 | 61 | public class Lock{ 62 | private boolean isLocked = false; 63 | public synchronized void lock() 64 | throws InterruptedException{ 65 | //access condition 66 | while(isLocked){ 67 | wait(); 68 | } 69 | isLocked = true; 70 | } 71 | ... 72 | } 73 | 74 | 75 | public class BoundedSemaphore { 76 | private int signals = 0; 77 | private int bound = 0; 78 | 79 | public BoundedSemaphore(int upperBound){ 80 | this.bound = upperBound; 81 | } 82 | public synchronized void take() throws InterruptedException{ 83 | //access condition 84 | while(this.signals == bound) wait(); 85 | this.signals++; 86 | this.notify(); 87 | } 88 | public synchronized void release() throws InterruptedException{ 89 | //access condition 90 | while(this.signals == 0) wait(); 91 | this.signals--; 92 | this.notify(); 93 | } 94 | } 95 | 96 | ####状态变化 97 | 98 | 一旦一个线程获得了临界区的访问权限,它得改变同步器的状态,让其它线程阻塞,防止它们进入临界区。换而言之,这个状态表示正有一个线程在执行临界区的代码。其它线程想要访问临界区的时候,该状态应该影响到访问条件的结果。 99 | 100 | 在Lock中,通过代码设置isLocked = true来改变状态,在信号量中,改变状态的是signals–或signals++; 101 | 102 | 这里有两个状态变化的代码片段: 103 | 104 | public class Lock{ 105 | 106 | private boolean isLocked = false; 107 | 108 | public synchronized void lock() 109 | throws InterruptedException{ 110 | while(isLocked){ 111 | wait(); 112 | } 113 | //state change 114 | isLocked = true; 115 | } 116 | 117 | public synchronized void unlock(){ 118 | //state change 119 | isLocked = false; 120 | notify(); 121 | } 122 | } 123 | 124 | 125 | public class BoundedSemaphore { 126 | private int signals = 0; 127 | private int bound = 0; 128 | 129 | public BoundedSemaphore(int upperBound){ 130 | this.bound = upperBound; 131 | } 132 | 133 | public synchronized void take() throws InterruptedException{ 134 | while(this.signals == bound) wait(); 135 | //state change 136 | this.signals++; 137 | this.notify(); 138 | } 139 | 140 | public synchronized void release() throws InterruptedException{ 141 | while(this.signals == 0) wait(); 142 | //state change 143 | this.signals--; 144 | this.notify(); 145 | } 146 | } 147 | 148 | ####通知策略 149 | 150 | 一旦某个线程改变了同步器的状态,可能需要通知其它等待的线程状态已经变了。因为也许这个状态的变化会让其它线程的访问条件变为true。 151 | 152 | 通知策略通常分为三种: 153 | 154 | - **通知所有等待的线程** 155 | - **通知N个等待线程中的任意一个** 156 | - **通知N个等待线程中的某个指定的线程** 157 | 158 | 通知所有等待的线程非常简单。所有等待的线程都调用的同一个对象上的wait()方法,某个线程想要通知它们只需在这个对象上调用notifyAll()方法。 159 | 160 | 通知等待线程中的任意一个也很简单,只需将notifyAll()调用换成notify()即可。调用notify方法没办法确定唤醒的是哪一个线程,也就是“等待线程中的任意一个”。 161 | 162 | 有时候可能需要通知指定的线程而非任意一个等待的线程。例如,如果你想保证线程被通知的顺序与它们进入同步块的顺序一致,或按某种优先级的顺序来通知。想要实现这种需求,每个等待的线程必须在其自有的对象上调用wait()。当通知线程想要通知某个特定的等待线程时,调用该线程自有对象的notify()方法即可。饥饿和公平中有这样的例子。 163 | 164 | 下面是通知策略的一个例子(通知任意一个等待线程): 165 | 166 | public class Lock{ 167 | 168 | private boolean isLocked = false; 169 | 170 | public synchronized void lock() 171 | throws InterruptedException{ 172 | while(isLocked){ 173 | //wait strategy - related to notification strategy 174 | wait(); 175 | } 176 | isLocked = true; 177 | } 178 | 179 | public synchronized void unlock(){ 180 | isLocked = false; 181 | notify(); //notification strategy 182 | } 183 | } 184 | 185 | ####Test-and-Set方法 186 | 187 | 同步器中最常见的有两种类型的方法,test-and-set是第一种(set是另一种)。Test-and-set的意思是,调用这个方法的线程检查访问条件,如若满足,该线程设置同步器的内部状态来表示它已经获得了访问权限。 188 | 189 | 状态的改变通常使其它试图获取访问权限的线程计算条件状态时得到false的结果,但并不一定总是如此。例如,在读写锁中,获取读锁的线程会更新读写锁的状态来表示它获取到了读锁,但是,只要没有线程请求写锁,其它请求读锁的线程也能成功。 190 | 191 | test-and-set很有必要是原子的,也就是说在某个线程检查和设置状态期间,不允许有其它线程在test-and-set方法中执行。 192 | 193 | test-and-set方法的程序流通常遵照下面的顺序: 194 | 195 | - **如有必要,在检查前先设置状态** 196 | - **检查访问条件** 197 | - **如果访问条件不满足,则等待** 198 | - **如果访问条件满足,设置状态,如有必要还要通知等待线程** 199 | 200 | 下面的ReadWriteLock类的lockWrite()方法展示了test-and-set方法。调用lockWrite()的线程在检查之前先设置状态(writeRequests++)。然后检查canGrantWriteAccess()中的访问条件,如果检查通过,在退出方法之前再次设置内部状态。这个方法中没有去通知等待线程。 201 | 202 | public class ReadWriteLock{ 203 | private Map readingThreads = 204 | new HashMap(); 205 | 206 | private int writeAccesses = 0; 207 | private int writeRequests = 0; 208 | private Thread writingThread = null; 209 | 210 | ... 211 | 212 | public synchronized void lockWrite() throws InterruptedException{ 213 | writeRequests++; 214 | Thread callingThread = Thread.currentThread(); 215 | while(! canGrantWriteAccess(callingThread)){ 216 | wait(); 217 | } 218 | writeRequests--; 219 | writeAccesses++; 220 | writingThread = callingThread; 221 | } 222 | 223 | ... 224 | } 225 | 226 | 227 | 下面的BoundedSemaphore类有两个test-and-set方法:take()和release()。两个方法都有检查和设置内部状态。 228 | 229 | 230 | public class BoundedSemaphore { 231 | private int signals = 0; 232 | private int bound = 0; 233 | 234 | public BoundedSemaphore(int upperBound){ 235 | this.bound = upperBound; 236 | } 237 | 238 | public synchronized void take() throws InterruptedException{ 239 | while(this.signals == bound) wait(); 240 | this.signals++; 241 | this.notify(); 242 | } 243 | 244 | public synchronized void release() throws InterruptedException{ 245 | while(this.signals == 0) wait(); 246 | this.signals--; 247 | this.notify(); 248 | } 249 | } 250 | 251 | ####Set方法 252 | 253 | set方法是同步器中常见的第二种方法。set方法仅是设置同步器的内部状态,而不先做检查。set方法的一个典型例子是Lock类中的unlock()方法。持有锁的某个线程总是能够成功解锁,而不需要检查该锁是否处于解锁状态。 254 | 255 | set方法的程序流通常如下: 256 | 257 | - **设置内部状态** 258 | - **通知等待的线程** 259 | 260 | 这里是unlock()方法的一个例子: 261 | 262 | public class Lock{ 263 | private boolean isLocked = false; 264 | 265 | public synchronized void unlock(){ 266 | isLocked = false; 267 | notify(); 268 | } 269 | } 270 | 271 | 272 | 273 | -------------------------------------------------------------------------------- /多线程能够给我们带来什么.md: -------------------------------------------------------------------------------- 1 | ####多线程能够给我们带来什么 2 | - **更好的资源利用** 3 | - **磁盘IO、网络IO等** 4 | - **在一些情景下简化程序设计** 5 | - **提高程序响应性** 6 | 7 | ####多线程的开销 8 | >不要为了多线程而使用多线程 9 | 10 | - **更复杂的设计** 11 | - **控制临界资源的访问** 12 | - **出现问题,难以调试、重现和修复** 13 | - **...** 14 | - **上下文切换次数过高** 15 | - **当CPU发生上下文切换时,需要保存现场** 16 | - **CPU上下文切换的代价很昂贵** 17 | - **...** 18 | - **增加资源消耗** 19 | - **需要内存保存本地栈** 20 | - **操作系统需要一些资源管理线程** 21 | - **...** 22 | 23 | ####在Java中使用线程 24 | 在Java中创建线程有两种方式: 25 | 26 | Thread myThread = new Thread(); 27 | 或者 28 | 29 | public WorkerTask implements Runnale{...}; 30 | Thread myThread = new Thread(new WorkerTask()); 31 | 32 | 启动线程: 33 | 34 | myThread.start(); 35 | 36 | 最佳实践: 37 | 38 | //为线程指定一个名字 39 | 40 | ####竞争条件 41 | 临界资源: 42 | 43 | ####线程安全和共享资源 44 | 当多个线程区更新共享资源的时候才会发生竞争条件。所以,我们需要知道存在哪些临界资源。 45 | 46 | - **本地变量** 47 | - **本地变量被存储在每个线程所属的栈中** 48 | - **所以,不存在多个线程共享的问题,是线程安全的** 49 | - **本地对象引用** 50 | - **本地对象引用与本地变量相比有一些不同。引用变量本身是不被共享的,但引用变量指向的对象并没有存放在线程所有的栈中,它们存放在JVM的堆中。** 51 | - **对象成员** 52 | -**不是线程安全的** 53 | 54 | >如果一个资源的创建、使用和销毁都在同一个线程的控制之内,而且从未逃脱这个线程的控制,这个资源就是线程安全的。 55 | 56 | 资源可以是任何类型的共享资源,比如对象,数组,文件,数据库连接,socket等。在Java中你不需要显式的销毁资源,因此”销毁“意味着丢弃或者为对象的引用赋值null。 57 | 58 | 尽管一个对象的使用是线程安全的,但如果这个对象指向了一个共享资源比如文件或者数据库,你的程序整体看来可能就不是线程安全的。 59 | 60 | ####线程安全和不可变性 61 | 如果多个线程同时去访问同一个资源,或者,一个或多个线程对这个资源发生写操作时才会发生竞争条件;一个或多个线程对同一个资源发生读操作并不会发生竞争条件。 62 | 63 | 我们可以通过每次在发生写操作后,重新构造一个新的实例的方式,来达到线程安全的目的。比如String类。 64 | 65 | -------------------------------------------------------------------------------- /并发编程实战.pdfx.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlankKelly/ConcurrencyNote/4596da2d8592a53c7d60e1df35f67dd0f65b7cae/并发编程实战.pdfx.pdf -------------------------------------------------------------------------------- /数据库连接池/DBCP.md: -------------------------------------------------------------------------------- 1 | ###DBCP 2 | ####简介 3 | ![commons dbcp](https://commons.apache.org/proper/commons-dbcp/images/dbcp-logo-white.png) 4 | 许多apache项目都支持与关系型数据库交互。为了执行一个可能只需要几毫秒的数据库事务,而要为每个用户创建一个新的数据库连接会造成大量时间消耗。对打存在大量并发访问的互联网应用来说,为每一个用户打开一个连接是很困难的。于是,工程师们常常希望共享一个连接池共所有并发访问应用的用户使用。在某一个时间段执行一个请求的用户数通常占所有活跃用户数很小的一部分,在请求处理过程中,一个数据库连接的时间至少是需要的。应用自身记录日志到关系型数据库管理系统,在内部处理任何用户报告的问题。 5 | 6 | 已经有几个数据库连接池发布,包括apache发布的和其他的。这个 Commons 包提供了一个机会去感受需要去创建和操作一个高效的、富有特性的包带来的影响。 7 | 8 | commons-dbcp2依赖commons-pool2包下的代码,它操作的底层对象池机制。 9 | 10 | DBCP现在一共有3个不同的版本去支持不同版本的JDBC。如下所示: 11 | - DBCP2 仅能在Java7下编译和运行(JDBC 4.1) 12 | - DBCP 1.4 仅能在Java5下编译和运行(JDBC 4) 13 | - DBCP 1.3 仅能在Java 1.4 - 1,5下编译和运行(JDBC 3) 14 | 15 | DBCP 2 应该被运行在Java7下的应用使用。 16 | 17 | DBCP 1.4 应该被运行在Java6下的应用使用。 18 | 19 | DBCP 1,3应该被运行在Java1.4或Java1.5下的应用使用。 20 | 21 | DBCP 2基于Commons Pool 2,与DBCP 1.x 相比提供了更好的性能,支持JMX和其它一些新特性。迁移到2.x的用户应该认识到包的名称和Maven 坐标银镜发生了改变。从DBCP 2.x以后,不再二进制兼容 DBCP 1.x。这些用户也应该意识到一些配置选型应该被重命名,要和Commons Pool 2的新名称保持一致。 22 | 23 | ####基本数据源配置参数 24 | | 参数 | 描述 | 25 | | :----- | :----- | 26 | | username | 连接用户名,传递给我们的JDBC驱动,建立一个连接 | 27 | | password | 连接密码,传递给我们的JDBC驱动,建立一个连接 | 28 | | url | 连接URL,床底给我们的JDBC驱动,建立一个连接 | 29 | | driverClassName | 所使用的数据库驱动类的全限定名 | 30 | | connectionProperties | 当建立一个连接时,将会传递给我们的JDBC驱动的连接属性。字符串格式必须为[属性名=属性;]*,注意:"user"和"password"属性会被显式的传递,所以它们不需要被包含在这里 | 31 | 32 | | 参数 | 默认值 | 描述 | 33 | | :---- | :-------- | :----: | 34 | | defaultAutoCommit | driver default | 被这个连接池创建的连接的默认的自动提交状态。如果没有设置,setAutoCommit方法将不会被调用 | 35 | | defaultReadOnly | dirver default | 被这个数据库连接池创建的连接的默认只读状态。如果没有设置,setReadOnly方法将不会被调用。(一些数据驱动不支持只读模式,比如,Informix)| 36 | | defaultTransactionlsolation | driver default | 被这个数据库连接池创建的连接的默认事务隔离状态。可以为下列值之一:NONE, READ_COMMITTED,READ_UNCOMMITED, REPEATABLE_READ ,SERIALIZE | 37 | | defaultCatalog | | 被这个数据库连接创建的连接的默认catalog | 38 | | cacheState | true | 如果为true,连接池连接将会在第一次读或者写以及随后的所有写操作时缓存当前的readOnly和autoCommit设置。这样,在再次获取数据时不再需要额外的数据库查询。如果可以直接访问底层的数据库连接,readOnly 和/或 autoCommit设置改变当前的缓存值将不会对当前状态造成影响。在这案例中,将这个属性设置为false,缓存将会被取消 | 39 | 40 | | 参数 | 默认值 | 描述 | 41 | | :--- | :--- | :-- | 42 | | initialSize | 0 | 当连接池被启动时,创建的初始化连接数 | 43 | | maxTotal | 8 | 在同一时刻,连接池可以分配的最大活跃连接数,负值没有限制 | 44 | | maxIdle | 8 | 在不额外的创建连接情况下,连接池中最大空闲连接数,负值没有限制 | 45 | | minIdle | 0 | 在不额外的创建连接的情况下,连接池中最小的空闲连接数,在不创建任何连接时为0 | 46 | | maxWaitMillis | indefinitely | 在抛出一个异常之前,连接池等待一个连接返回等待的最长时间(单位为毫秒),或者设为-1无限等待 | 47 | 48 | **注意:**如果在高负载的系统中maxIdle的值被设的很低,你可能会发现一些连接被关闭,几乎同时新的连接的被打开。这是活跃的线程关闭连接的时间快于它们打开连接的时间,导致空闲连接数上升到maxIdle带来的结果。对于高负载系统来说,maxIdle最合适的值是不确定的,不过默认值是一个好的起点。 49 | 50 | | 参数 | 默认值 | 描述 | 51 | | :---- | :---- | :---- | 52 | | validationQuery | | 在此连接池返回给调用者连接之前用于验证此连接的SQL查询如果指定,这个查询必须是SQL中的SELECT语句且至少返回一行数据。如果未指定,连接将通过isValid()来验证。 | 53 | | testOnCreate | false | 用于标识对象在被创建后是否会被验证。如果对象验证失败,触发对象创建的担保尝试(borrow attempt)也会失败。 | 54 | | testOnBorrow | true | 用于标识从此连接池借用(borrow)对象时,此对象是否会别验证。如果对象验证失败,它将会被从连接池中删除,然后我们尝试借用另一个。 | 55 | | testOnReturn | false | 用于标识对象在返回到连接池时是否需要验证 | 56 | | testWhileIdle | false | 用于标识对象是否会被空闲对象监视器验证。如果对象验证失败,它将会被从连接池中删除 | 57 | | timeBetweenEvictionRunsMillis | -1 | 空闲对象监视器线程运行期间休眠的毫秒数。当此值为负数时,没有空闲对象监视器线程运行 | 58 | | numTestsPerEvictionRun | 3 | 在每个空闲对象监视器线程运行期间测试的对象的个数 | 59 | | minEvictableIdleTimeMillis | 1000*60*30 | 一个对象在空闲对象监视器线程判定为可以被清除之前可以在连接池中保持空闲的最小时长 | 60 | | softMiniEvictableIdleTimeMillis | -1 | 一个连接在空闲连接监视器认定为可以被清除之前可以在连接池中保持空闲的最小时长,额外的条件就是至少 “最小空闲”(minIdle)连接保留在此连接池中。当miniEvictableIdle被设为一个整值时,miniEvictableTimeMillis首次被空闲连接监视器测试-比如,当空闲连接被监视器访问的时候,首先比较空闲时间和miniEvictableIdleTimeMillis(不考虑连接池中的空闲连接数),然后再比较softMinEvictableIdleTimeMillis,包括最小空闲约束。 | 61 | | maxConnLifetimeMillis | -1 | 一个连接的最长存活时间(单位为毫秒)。超过这个时间,这个连接将会在下次激活(activation)、钝化(passivation)或者验证(validation)测试时失败。此值如果小于或等于0,连接永久存活。 | 62 | | logExpiredConnections | true | 设置在一个连接因为maxConnLifetimeMillis超时被连接池关闭时是否记录一条日志。将这个属性设为false,来关闭默认开启的过期连接日志。 | 63 | | connectionInitSqls | null | 当物理连接被创建时,用来初始化这些连接的一些SQL语句。这些语句仅被执行一次——当配置连接工厂创建连接时。 | 64 | | lifo | true | 如果设为true,则从连接池中“借用”对象时将返回最近被使用的对象(如果有空闲对象可用)。如果设为false,则来连接池行为上类似于一个FIFO队列,连接将会被从空闲实例池中以他们返回连接池中时的顺序被取走。 | 65 | 66 | 67 | | 参数 | 默认值 | 描述 | 68 | | :----- | :----- | :----- | 69 | | poolPreparedStatements | false | 允许在此连接池中使用预编译语句池 | 70 | | maxOpenPreparedStatements | unlimited | 在同一时刻,此语句池可以分配的打开语句的最大个数,负值将没有限制。 | 71 | 72 | 这个组件也可以池化(pool)PreparedStatement。当允许为每一个连接创建一个语句池时,PreparedStatement会被连接池通过下列方法之一来创建: 73 | - public PreparedStatemet prepareStatement(String sql) 74 | - public PreparedSattement prepareSattement(String sql, int resultSetType, int resultSetConcurrency) 75 | 76 | **注意:**-确保你的连接为其它连接保留了一些资源。Pooling PreparedSattements 可能会让它们的游标在数据中一直打开,导致一个连接用尽游标,特别,如果maxPreparedStatements保留默认值(没有限制)且一个程序在每一个连接上打开了大量的PreparedSattements。为了避免这个问题,maxOpenPreparedStatements应该被设为一个小于在一个连接上可以打开的最大游标数的值。 77 | 78 | | 参数 | 默认值 | 描述 | 79 | | :--- | :--- | :--- | 80 | | accessToUnderlyingConnectionAllowed | false | 是否允许PoolGuard访问底层连接 | 81 | 82 | 当允许你访问底层连接的时候,可以通过如下方式: 83 | 84 | Connection conn = ds.getConnection(); 85 | Connection dconn = ((DelegatingConnection)conn).getInnermostDelegate(); 86 | ... 87 | conn.close(); 88 | 89 | **提示:**默认是false,这个一个存在潜在风险的操作,恶意程序可以做一些对系统有害的事情。(当 guarded connection 已经关闭的时候,关闭底层连接或者继续使用)时刻关注且仅在你需要直接访问驱动指定扩展时才使用。 90 | 91 | **注意:**不要关闭底层连接,只有一个。 92 | 93 | | 参数 | 默认值 | 描述 | 94 | | :--- | :--- | :--- | 95 | | removeAbandonedOnMaintenance/removeAbandonedOnBorrow | false |是否移除被丢弃的对象在它们超出removeAbandonedTimout时。一个连接在大于removeAbandonedTimeout时长内没有被使用就可以考虑丢弃并认定为可移除。创建一个StatementPreparedStatement或者CallableSatement或者使用它们其中之一执行一个查询会重置其父连接的lastUsed属性。Setting one or both of these to true can recover db connections from poorly written applications which fail to close connections.removeAbandonedOnMaintenance to true removes abandoned connections on the maintenance cycle (when eviction ends). This property has no effect unless maintenance is enabled by setting timeBetweenEvicionRunsMillis to a positive value. If removeAbandonedOnBorrow is true, abandoned connections are removed each time a connection is borrowed from the pool, with the additional requirements that: getNumActive() > getMaxTotal() - 3; and getNumIdle() < 2 | 96 | | removeAbandonedTimeout | 300 | 一个可丢弃连接被移除之前的时长(单位:秒) | 97 | | logAbandoned | false | Flag to log stack traces for application code which abandoned a Statement or Connection.Logging of abandoned Statements and Connections adds overhead for every Connection open or new Statement because a stack trace has to be generated. | 98 | 99 | **提示:**If you have enabled removeAbandonedOnMaintenance or removeAbandonedOnBorrow then it is possible that a connection is reclaimed by the pool because it is considered to be abandoned. This mechanism is triggered when (getNumIdle() < 2) and (getNumActive() > getMaxTotal() - 3) and removeAbandonedOnBorrow is true; or after eviction finishes and removeAbandonedOnMaintenance is true. For example, maxTotal=20 and 18 active connections and 1 idle connection would trigger removeAbandonedOnBorrow, but only the active connections that aren't used for more then "removeAbandonedTimeout" seconds are removed (default 300 sec). Traversing a resultset doesn't count as being used. Creating a Statement, PreparedStatement or CallableStatement or using one of these to execute a query (using one of the execute methods) resets the lastUsed property of the parent connection. 100 | 101 | | 参数 | 默认值 | 描述 | 102 | | :----| :---- | :---- | 103 | | fastFailValidation | false | When this property is true, validation fails fast for connections that have thrown "fatal" SQLExceptions. Requests to validate disconnected connections fail immediately, with no call to the driver's isValid method or attempt to execute a validation query.The SQL_STATE codes considered to signal fatal errors are by default the following: 57P01 (ADMIN SHUTDOWN) , 57P02 (CRASH SHUTDOWN),57P03 (CANNOT CONNECT NOW),01002 (SQL92 disconnect error),JZ0C0 (Sybase disconnect error),JZ0C1 (Sybase disconnect error),Any SQL_STATE code that starts with "08" To override this default set of disconnection codes, set the disconnectionSqlCodes property. | 104 | | disconnectionSqlCodes | null | Comma-delimited list of SQL_STATE codes considered to signal fatal disconnection errors. Setting this property has no effect unless fastFailValidation is set to true. | 105 | 106 | ####开发者指南 107 | **BasicDataSource** 108 | 109 | ![BasicDataSource](https://commons.apache.org/proper/commons-dbcp/images/uml/BasicDataSource.gif) 110 | 111 | 112 | **ConnectionFactory** 113 | 114 | ![ConnectionFactory](https://commons.apache.org/proper/commons-dbcp/images/uml/ConnectionFactory.gif) 115 | 116 | #####JNDI Howto 117 | JNDI是Java平台的一部分,提供给基于Java技术的应用一个统一的接口映射到多个命名和目录服务。你可以使用这项工业标准构建强大的、可移植的 directory-enabled 引用。 118 | 119 | 当你在一个应用服务器中部署你的程序时,容器将会为启动 JNDI 树。但是,如果你写的是一个框架或者仅仅是一个 standalone application ,那么下面的例子将会展示给你如何构造和将引用绑定到 DBCP 数据源上。 120 | 121 | 下面的例子使用的是sun文件系统JNDI服务提供器。你可以从 [JNDI software sownload](http://www.oracle.com/technetwork/java/index.html)页面下载它。 122 | 123 | **BasicDataSource** 124 | 125 | System.setProperty(Context.INITIAL_CONTEXT_FACTORY, 126 | "com.sun.jndi.fscontext.RefFSContextFactory"); 127 | System.setProperty(Context.PROVIDER_URL, "file:///tmp"); 128 | InitialContext ic = new InitialContext(); 129 | 130 | // Construct BasicDataSource 131 | BasicDataSource bds = new BasicDataSource(); 132 | bds.setDriverClassName("org.apache.commons.dbcp2.TesterDriver"); 133 | bds.setUrl("jdbc:apache:commons:testdriver"); 134 | bds.setUsername("username"); 135 | bds.setPassword("password"); 136 | 137 | ic.rebind("jdbc/basic", bds); 138 | 139 | // Use 140 | InitialContext ic2 = new InitialContext(); 141 | DataSource ds = (DataSource) ic2.lookup("jdbc/basic"); 142 | assertNotNull(ds); 143 | Connection conn = ds.getConnection(); 144 | assertNotNull(conn); 145 | conn.close(); 146 | 147 | **PerUserPoolDataSource** 148 | 149 | System.setProperty(Context.INITIAL_CONTEXT_FACTORY, 150 | "com.sun.jndi.fscontext.RefFSContextFactory"); 151 | System.setProperty(Context.PROVIDER_URL, "file:///tmp"); 152 | InitialContext ic = new InitialContext(); 153 | 154 | // Construct DriverAdapterCPDS reference 155 | Reference cpdsRef = new Reference("org.apache.commons.dbcp2.cpdsadapter.DriverAdapterCPDS", 156 | "org.apache.commons.dbcp2.cpdsadapter.DriverAdapterCPDS", null); 157 | cpdsRef.add(new StringRefAddr("driver", "org.apache.commons.dbcp2.TesterDriver")); 158 | cpdsRef.add(new StringRefAddr("url", "jdbc:apache:commons:testdriver")); 159 | cpdsRef.add(new StringRefAddr("user", "foo")); 160 | cpdsRef.add(new StringRefAddr("password", "bar")); 161 | ic.rebind("jdbc/cpds", cpdsRef); 162 | 163 | // Construct PerUserPoolDataSource reference 164 | Reference ref = new Reference("org.apache.commons.dbcp2.datasources.PerUserPoolDataSource", 165 | "org.apache.commons.dbcp2.datasources.PerUserPoolDataSourceFactory", null); 166 | ref.add(new StringRefAddr("dataSourceName", "jdbc/cpds")); 167 | ref.add(new StringRefAddr("defaultMaxTotal", "100")); 168 | ref.add(new StringRefAddr("defaultMaxIdle", "30")); 169 | ref.add(new StringRefAddr("defaultMaxWaitMillis", "10000")); 170 | ic.rebind("jdbc/peruser", ref); 171 | 172 | // Use 173 | InitialContext ic2 = new InitialContext(); 174 | DataSource ds = (DataSource) ic2.lookup("jdbc/peruser"); 175 | assertNotNull(ds); 176 | Connection conn = ds.getConnection("foo","bar"); 177 | assertNotNull(conn); 178 | conn.close(); 179 | 180 | ####类图 181 | **PoolingDataSource** 182 | 183 | ![PoolingDataSource](https://commons.apache.org/proper/commons-dbcp/images/uml/PoolingDataSource.gif) 184 | 185 | 186 | **PoolingDataConnection** 187 | 188 | ![PoolingDataSource](https://commons.apache.org/proper/commons-dbcp/images/uml/PoolingConnection.gif) 189 | 190 | **Delegating** 191 | 192 | ![Delegating](https://commons.apache.org/proper/commons-dbcp/images/uml/Delegating.gif) 193 | 194 | **AbandonedObjectPool** 195 | 196 | ![AbandonedObjectPool](https://commons.apache.org/proper/commons-dbcp/images/uml/AbandonedObjectPool.gif) 197 | 198 | ####序列图 199 | **createDataSource** 200 | ![createDataSource](https://commons.apache.org/proper/commons-dbcp/images/uml/createDataSource.gif) 201 | 202 | **getConnection** 203 | ![getConnection](https://commons.apache.org/proper/commons-dbcp/images/uml/getConnection.gif) 204 | 205 | **prepareStatement** 206 | ![prepareStatement](https://commons.apache.org/proper/commons-dbcp/images/uml/prepareStatement.gif) 207 | -------------------------------------------------------------------------------- /死锁.md: -------------------------------------------------------------------------------- 1 | ####死锁 2 | 当多个线程同时需要同一个锁,但是以不同的方式获取它们。 3 | 4 | 例如,如果线程1持有锁A,然后请求锁B,线程2已经持有锁B,然后请求锁A,这样一个死锁就发生了。线程1永远也得不到锁B,线程2永远也得不到锁A。例外,它们永远也不知道这种情况。 5 | 6 | public class TreeNode { 7 | 8 | TreeNode parent = null; 9 | List children = new ArrayList(); 10 | 11 | public synchronized void addChild(TreeNode child){ 12 | if(!this.children.contains(child)) { 13 | this.children.add(child); 14 | child.setParentOnly(this); 15 | } 16 | } 17 | 18 | public synchronized void addChildOnly(TreeNode child){ 19 | if(!this.children.contains(child){ 20 | this.children.add(child); 21 | } 22 | } 23 | 24 | public synchronized void setParent(TreeNode parent){ 25 | this.parent = parent; 26 | parent.addChildOnly(this); 27 | } 28 | 29 | public synchronized void setParentOnly(TreeNode parent){ 30 | this.parent = parent; 31 | } 32 | } 33 | 34 | 如果一个线程(1)调用parent.addChild(child)的同时其他线程(2)在同一个parent和child实例上调用child.setParent(parent)方法,就会发生死锁。 35 | 下面是说明这个问题的一些伪代码: 36 | 37 | Thread 1: parent.addChild(child); //locks parent 38 | --> child.setParentOnly(parent); 39 | 40 | Thread 2: child.setParent(parent); //locks child 41 | --> parent.addChildOnly() 42 | 43 | 首先,线程1调用parent.addChild(child),因为addChild()是同步的,所以线程1会锁住parent对象,防止其他线程获得。 44 | 45 | 然后,线程2调用child.setParent(parent),因为setParent()是同步的,所有线程2会锁住child对象,防止其他线程获得。 46 | 47 | 现在,parent和child对象被这两个不同的线程锁住。接下来,线程1尝试调用child.setParentOnly()方法,但是child对象被线程2锁住了,因此这个调用就会阻塞在那。线程2也尝试调用parent.addChildOnly()方法,但是parent对象被线程1锁住了。线程2也会阻塞在这个方法的调用上。现在两个线程都在等待获取被其他线程持有的锁。 48 | 49 | 线程确实需要同时获得锁。例如,如果线程1早线程2一点点,获得了锁A和B,然后,线程2就会在尝试获取锁B时,阻塞在那。这样就不会有死锁发生。由于,线程调度是不确定的,所以,我们无法准确预测什么时候会发生死锁。 50 | 51 | #####更复杂的死锁 52 | 53 | Thread 1 locks A, waits for B 54 | Thread 2 locks B, waits for C 55 | Thread 3 locks C, waits for D 56 | Thread 4 locks D, waits for A 57 | 线程1等待线程2,线程2等待线程3,线程3等待线程4,线程4等待线程1. 58 | 59 | #####数据库死锁 60 | 61 | 一个更复杂的死锁发生场景,就是数据库事务。一个数据库可能包含许多SQL更新请求。在一个事务中,要更新一条记录,但这条记录被来自其它事务的更新请求锁住了,知道第一个事务完成。在数据库中,同一个事务内的每条更新请求可能都会锁住一些记录。 62 | 63 | 如果多个事务同时运行,并且更新相同的记录。这就会有发生死锁的风险。 64 | 65 | 例如: 66 | 67 | Transaction 1, request 1, locks record 1 for update 68 | Transaction 2, request 1, locks record 2 for update 69 | Transaction 1, request 2, tries to lock record 2 for update. 70 | Transaction 2, request 2, tries to lock record 1 for update. 71 | 72 | 一个事务事先并不知道所有的它将要锁住的记录,所有在数据库中检测和预防死锁变得更加困难。 -------------------------------------------------------------------------------- /每个程序员必读系列/The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!).md: -------------------------------------------------------------------------------- 1 | ###The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!) 2 | 3 | Ever wonder about that mysterious Content-Type tag? You know, the one you're supposed to put in HTML and you never quite know what it should be? 4 | 5 | Did you ever get an email from your friends in Bulgaria with the subject line "???? ?????? ??? ????"? 6 | 7 | ![ibm](http://www.joelonsoftware.com/pictures/unicode/ibm.jpg) 8 | 9 | I've been dismayed to discover just how many software developers aren't really completely up to speed on the mysterious world of character sets, encodings, Unicode, all that stuff. A couple of years ago, a beta tester for FogBUGZ was wondering whether it could handle incoming email in Japanese. Japanese? They have email in Japanese? I had no idea. When I looked closely at the commercial ActiveX control we were using to parse MIME email messages, we discovered it was doing exactly the wrong thing with character sets, so we actually had to write heroic code to undo the wrong conversion it had done and redo it correctly. When I looked into another commercial library, it, too, had a completely broken character code implementation. I corresponded with the developer of that package and he sort of thought they "couldn't do anything about it." Like many programmers, he just wished it would all blow over somehow. 10 | 11 | But it won't. When I discovered that the popular web development tool PHP has almost complete ignorance of character encoding issues, blithely using 8 bits for characters, making it darn near impossible to develop good international web applications, I thought, enough is enough. 12 | 13 | So I have an announcement to make: if you are a programmer working in 2003 and you don't know the basics of characters, character sets, encodings, and Unicode, and I catch you, I'm going to punish you by making you peel onions for 6 months in a submarine. I swear I will. 14 | 15 | And one more thing: 16 | 17 | **IT'S NOT THAT HARD.** 18 | 19 | In this article I'll fill you in on exactly what every working programmer should know. All that stuff about "plain text = ascii = characters are 8 bits" is not only wrong, it's hopelessly wrong, and if you're still programming that way, you're not much better than a medical doctor who doesn't believe in germs. Please do not write another line of code until you finish reading this article. 20 | 21 | Before I get started, I should warn you that if you are one of those rare people who knows about internationalization, you are going to find my entire discussion a little bit oversimplified. I'm really just trying to set a minimum bar here so that everyone can understand what's going on and can write code that has a hope of working with text in any language other than the subset of English that doesn't include words with accents. And I should warn you that character handling is only a tiny portion of what it takes to create software that works internationally, but I can only write about one thing at a time so today it's character sets. 22 | 23 | ####A Historical Perspective 24 | 25 | ![ascii](http://www.joelonsoftware.com/pictures/unicode/ascii.png) 26 | 27 | The easiest way to understand this stuff is to go chronologically. 28 | 29 | You probably think I'm going to talk about very old character sets like EBCDIC here. Well, I won't. EBCDIC is not relevant to your life. We don't have to go that far back in time. 30 | 31 | Back in the semi-olden days, when Unix was being invented and K&R were writing The C Programming Language, everything was very simple. EBCDIC was on its way out. The only characters that mattered were good old unaccented English letters, and we had a code for them called ASCII which was able to represent every character using a number between 32 and 127. Space was 32, the letter "A" was 65, etc. This could conveniently be stored in 7 bits. Most computers in those days were using 8-bit bytes, so not only could you store every possible ASCII character, but you had a whole bit to spare, which, if you were wicked, you could use for your own devious purposes: the dim bulbs at WordStar actually turned on the high bit to indicate the last letter in a word, condemning WordStar to English text only. Codes below 32 were called unprintable and were used for cussing. Just kidding. They were used for control characters, like 7 which made your computer beep and 12 which caused the current page of paper to go flying out of the printer and a new one to be fed in. 32 | 33 | And all was good, assuming you were an English speaker. 34 | 35 | ![oem](http://www.joelonsoftware.com/pictures/unicode/oem.png) 36 | 37 | Because bytes have room for up to eight bits, lots of people got to thinking, "gosh, we can use the codes 128-255 for our own purposes." The trouble was, lots of people had this idea at the same time, and they had their own ideas of what should go where in the space from 128 to 255. The IBM-PC had something that came to be known as the OEM character set which provided some accented characters for European languages and a bunch of line drawing characters... horizontal bars, vertical bars, horizontal bars with little dingle-dangles dangling off the right side, etc., and you could use these line drawing characters to make spiffy boxes and lines on the screen, which you can still see running on the 8088 computer at your dry cleaners'. In fact as soon as people started buying PCs outside of America all kinds of different OEM character sets were dreamed up, which all used the top 128 characters for their own purposes. For example on some PCs the character code 130 would display as é, but on computers sold in Israel it was the Hebrew letter Gimel (ג), so when Americans would send their résumés to Israel they would arrive as rגsumגs. In many cases, such as Russian, there were lots of different ideas of what to do with the upper-128 characters, so you couldn't even reliably interchange Russian documents. 38 | 39 | Eventually this OEM free-for-all got codified in the ANSI standard. In the ANSI standard, everybody agreed on what to do below 128, which was pretty much the same as ASCII, but there were lots of different ways to handle the characters from 128 and on up, depending on where you lived. These different systems were called code pages. So for example in Israel DOS used a code page called 862, while Greek users used 737. They were the same below 128 but different from 128 up, where all the funny letters resided. The national versions of MS-DOS had dozens of these code pages, handling everything from English to Icelandic and they even had a few "multilingual" code pages that could do Esperanto and Galician on the same computer! Wow! But getting, say, Hebrew and Greek on the same computer was a complete impossibility unless you wrote your own custom program that displayed everything using bitmapped graphics, because Hebrew and Greek required different code pages with different interpretations of the high numbers. 40 | 41 | Meanwhile, in Asia, even more crazy things were going on to take into account the fact that Asian alphabets have thousands of letters, which were never going to fit into 8 bits. This was usually solved by the messy system called DBCS, the "double byte character set" in which some letters were stored in one byte and others took two. It was easy to move forward in a string, but dang near impossible to move backwards. Programmers were encouraged not to use s++ and s-- to move backwards and forwards, but instead to call functions such as Windows' AnsiNext and AnsiPrev which knew how to deal with the whole mess. 42 | 43 | But still, most people just pretended that a byte was a character and a character was 8 bits and as long as you never moved a string from one computer to another, or spoke more than one language, it would sort of always work. But of course, as soon as the Internet happened, it became quite commonplace to move strings from one computer to another, and the whole mess came tumbling down. Luckily, Unicode had been invented. 44 | 45 | ####Unicode 46 | 47 | Unicode was a brave effort to create a single character set that included every reasonable writing system on the planet and some make-believe ones like Klingon, too. Some people are under the misconception that Unicode is simply a 16-bit code where each character takes 16 bits and therefore there are 65,536 possible characters. This is not, actually, correct. It is the single most common myth about Unicode, so if you thought that, don't feel bad. 48 | 49 | In fact, Unicode has a different way of thinking about characters, and you have to understand the Unicode way of thinking of things or nothing will make sense. 50 | 51 | Until now, we've assumed that a letter maps to some bits which you can store on disk or in memory: 52 | 53 | A -> 0100 0001 54 | 55 | In Unicode, a letter maps to something called a code point which is still just a theoretical concept. How that code point is represented in memory or on disk is a whole nuther story. 56 | 57 | In Unicode, the letter A is a platonic ideal. It's just floating in heaven: 58 | 59 | A 60 | 61 | This platonic A is different than B, and different from a, but the same as A and A and A. The idea that A in a Times New Roman font is the same character as the A in a Helvetica font, but different from "a" in lower case, does not seem very controversial, but in some languages just figuring out what a letter is can cause controversy. Is the German letter ß a real letter or just a fancy way of writing ss? If a letter's shape changes at the end of the word, is that a different letter? Hebrew says yes, Arabic says no. Anyway, the smart people at the Unicode consortium have been figuring this out for the last decade or so, accompanied by a great deal of highly political debate, and you don't have to worry about it. They've figured it all out already. 62 | 63 | Every platonic letter in every alphabet is assigned a magic number by the Unicode consortium which is written like this: U+0639. This magic number is called a code point. The U+ means "Unicode" and the numbers are hexadecimal. U+0639 is the Arabic letter Ain. The English letter A would be U+0041. You can find them all using the charmap utility on Windows 2000/XP or visiting the Unicode web site. 64 | 65 | There is no real limit on the number of letters that Unicode can define and in fact they have gone beyond 65,536 so not every unicode letter can really be squeezed into two bytes, but that was a myth anyway. 66 | 67 | OK, so say we have a string: 68 | 69 | Hello 70 | 71 | which, in Unicode, corresponds to these five code points: 72 | 73 | U+0048 U+0065 U+006C U+006C U+006F. 74 | 75 | Just a bunch of code points. Numbers, really. We haven't yet said anything about how to store this in memory or represent it in an email message. 76 | 77 | ####Encodings 78 | 79 | That's where encodings come in. 80 | 81 | The earliest idea for Unicode encoding, which led to the myth about the two bytes, was, hey, let's just store those numbers in two bytes each. So Hello becomes 82 | 83 | 00 48 00 65 00 6C 00 6C 00 6F 84 | 85 | Right? Not so fast! Couldn't it also be: 86 | 87 | 48 00 65 00 6C 00 6C 00 6F 00 ? 88 | 89 | Well, technically, yes, I do believe it could, and, in fact, early implementors wanted to be able to store their Unicode code points in high-endian or low-endian mode, whichever their particular CPU was fastest at, and lo, it was evening and it was morning and there were already two ways to store Unicode. So the people were forced to come up with the bizarre convention of storing a FE FF at the beginning of every Unicode string; this is called a Unicode Byte Order Mark and if you are swapping your high and low bytes it will look like a FF FE and the person reading your string will know that they have to swap every other byte. Phew. Not every Unicode string in the wild has a byte order mark at the beginning. 90 | 91 | ![hummers](http://www.joelonsoftware.com/pictures/unicode/hummers.jpg) 92 | 93 | For a while it seemed like that might be good enough, but programmers were complaining. "Look at all those zeros!" they said, since they were Americans and they were looking at English text which rarely used code points above U+00FF. Also they were liberal hippies in California who wanted to conserve (sneer). If they were Texans they wouldn't have minded guzzling twice the number of bytes. But those Californian wimps couldn't bear the idea of doubling the amount of storage it took for strings, and anyway, there were already all these doggone documents out there using various ANSI and DBCS character sets and who's going to convert them all? Moi? For this reason alone most people decided to ignore Unicode for several years and in the meantime things got worse. 94 | 95 | Thus was invented the brilliant concept of UTF-8. UTF-8 was another system for storing your string of Unicode code points, those magic U+ numbers, in memory using 8 bit bytes. In UTF-8, every code point from 0-127 is stored in a single byte. Only code points 128 and above are stored using 2, 3, in fact, up to 6 bytes. 96 | 97 | ![utf8](http://www.joelonsoftware.com/pictures/unicode/utf8.png) 98 | 99 | This has the neat side effect that English text looks exactly the same in UTF-8 as it did in ASCII, so Americans don't even notice anything wrong. Only the rest of the world has to jump through hoops. Specifically, Hello, which was U+0048 U+0065 U+006C U+006C U+006F, will be stored as 48 65 6C 6C 6F, which, behold! is the same as it was stored in ASCII, and ANSI, and every OEM character set on the planet. Now, if you are so bold as to use accented letters or Greek letters or Klingon letters, you'll have to use several bytes to store a single code point, but the Americans will never notice. (UTF-8 also has the nice property that ignorant old string-processing code that wants to use a single 0 byte as the null-terminator will not truncate strings). 100 | 101 | So far I've told you three ways of encoding Unicode. The traditional store-it-in-two-byte methods are called UCS-2 (because it has two bytes) or UTF-16 (because it has 16 bits), and you still have to figure out if it's high-endian UCS-2 or low-endian UCS-2. And there's the popular new UTF-8 standard which has the nice property of also working respectably if you have the happy coincidence of English text and braindead programs that are completely unaware that there is anything other than ASCII. 102 | 103 | There are actually a bunch of other ways of encoding Unicode. There's something called UTF-7, which is a lot like UTF-8 but guarantees that the high bit will always be zero, so that if you have to pass Unicode through some kind of draconian police-state email system that thinks 7 bits are quite enough, thank you it can still squeeze through unscathed. There's UCS-4, which stores each code point in 4 bytes, which has the nice property that every single code point can be stored in the same number of bytes, but, golly, even the Texans wouldn't be so bold as to waste that much memory. 104 | 105 | And in fact now that you're thinking of things in terms of platonic ideal letters which are represented by Unicode code points, those unicode code points can be encoded in any old-school encoding scheme, too! For example, you could encode the Unicode string for Hello (U+0048 U+0065 U+006C U+006C U+006F) in ASCII, or the old OEM Greek Encoding, or the Hebrew ANSI Encoding, or any of several hundred encodings that have been invented so far, with one catch: some of the letters might not show up! If there's no equivalent for the Unicode code point you're trying to represent in the encoding you're trying to represent it in, you usually get a little question mark: ? or, if you're really good, a box. Which did you get? -> � 106 | 107 | There are hundreds of traditional encodings which can only store some code points correctly and change all the other code points into question marks. Some popular encodings of English text are Windows-1252 (the Windows 9x standard for Western European languages) and ISO-8859-1, aka Latin-1 (also useful for any Western European language). But try to store Russian or Hebrew letters in these encodings and you get a bunch of question marks. UTF 7, 8, 16, and 32 all have the nice property of being able to store any code point correctly. 108 | 109 | ####The Single Most Important Fact About Encodings 110 | 111 | If you completely forget everything I just explained, please remember one extremely important fact. It does not make sense to have a string without knowing what encoding it uses. You can no longer stick your head in the sand and pretend that "plain" text is ASCII. 112 | 113 | **There Ain't No Such Thing As Plain Text.** 114 | 115 | If you have a string, in memory, in a file, or in an email message, you have to know what encoding it is in or you cannot interpret it or display it to users correctly. 116 | 117 | Almost every stupid "my website looks like gibberish" or "she can't read my emails when I use accents" problem comes down to one naive programmer who didn't understand the simple fact that if you don't tell me whether a particular string is encoded using UTF-8 or ASCII or ISO 8859-1 (Latin 1) or Windows 1252 (Western European), you simply cannot display it correctly or even figure out where it ends. There are over a hundred encodings and above code point 127, all bets are off. 118 | 119 | How do we preserve this information about what encoding a string uses? Well, there are standard ways to do this. For an email message, you are expected to have a string in the header of the form 120 | 121 | **Content-Type: text/plain; charset="UTF-8"** 122 | 123 | For a web page, the original idea was that the web server would return a similar Content-Type http header along with the web page itself -- not in the HTML itself, but as one of the response headers that are sent before the HTML page. 124 | 125 | This causes problems. Suppose you have a big web server with lots of sites and hundreds of pages contributed by lots of people in lots of different languages and all using whatever encoding their copy of Microsoft FrontPage saw fit to generate. The web server itself wouldn't really know what encoding each file was written in, so it couldn't send the Content-Type header. 126 | 127 | It would be convenient if you could put the Content-Type of the HTML file right in the HTML file itself, using some kind of special tag. Of course this drove purists crazy... how can you read the HTML file until you know what encoding it's in?! Luckily, almost every encoding in common use does the same thing with characters between 32 and 127, so you can always get this far on the HTML page without starting to use funny letters: 128 | 129 | 130 | 131 | 132 | 133 | But that meta tag really has to be the very first thing in the section because as soon as the web browser sees this tag it's going to stop parsing the page and start over after reinterpreting the whole page using the encoding you specified. 134 | 135 | What do web browsers do if they don't find any Content-Type, either in the http headers or the meta tag? Internet Explorer actually does something quite interesting: it tries to guess, based on the frequency in which various bytes appear in typical text in typical encodings of various languages, what language and encoding was used. Because the various old 8 bit code pages tended to put their national letters in different ranges between 128 and 255, and because every human language has a different characteristic histogram of letter usage, this actually has a chance of working. It's truly weird, but it does seem to work often enough that naïve web-page writers who never knew they needed a Content-Type header look at their page in a web browser and it looks ok, until one day, they write something that doesn't exactly conform to the letter-frequency-distribution of their native language, and Internet Explorer decides it's Korean and displays it thusly, proving, I think, the point that Postel's Law about being "conservative in what you emit and liberal in what you accept" is quite frankly not a good engineering principle. Anyway, what does the poor reader of this website, which was written in Bulgarian but appears to be Korean (and not even cohesive Korean), do? He uses the View | Encoding menu and tries a bunch of different encodings (there are at least a dozen for Eastern European languages) until the picture comes in clearer. If he knew to do that, which most people don't. 136 | 137 | ![rose](http://www.joelonsoftware.com/pictures/unicode/rose.jpg) 138 | 139 | For the latest version of CityDesk, the web site management software published by my company, we decided to do everything internally in UCS-2 (two byte) Unicode, which is what Visual Basic, COM, and Windows NT/2000/XP use as their native string type. In C++ code we just declare strings as wchar_t ("wide char") instead of char and use the wcs functions instead of the str functions (for example wcscat and wcslen instead of strcat and strlen). To create a literal UCS-2 string in C code you just put an L before it as so: L"Hello". 140 | 141 | When CityDesk publishes the web page, it converts it to UTF-8 encoding, which has been well supported by web browsers for many years. That's the way all 29 language versions of Joel on Software are encoded and I have not yet heard a single person who has had any trouble viewing them. 142 | 143 | This article is getting rather long, and I can't possibly cover everything there is to know about character encodings and Unicode, but I hope that if you've read this far, you know enough to go back to programming, using antibiotics instead of leeches and spells, a task to which I will leave you now. 144 | 145 | ####Link 146 | 147 | [FogBUGZ](http://www.fogcreek.com/FogBUGZ/) 148 | 149 | [complete ignorance of character encoding issues](http://ca3.php.net/manual/en/language.types.string.php) 150 | 151 | [The C Program Language](http://cm.bell-labs.com/cm/cs/cbook/) 152 | 153 | [ASCII](http://www.robelle.com/library/smugbook/ascii.html) 154 | 155 | [Character Sets And Code Pages At The Push Of A Button](http://www.i18nguy.com/unicode/codepages.html#msftdos) 156 | 157 | [Unicode](http://www.unicode.org) 158 | 159 | [utf-8-history](http://www.cl.cam.ac.uk/~mgk25/ucs/utf-8-history.txt) 160 | 161 | [UTF-8](http://www.utf-8.com) 162 | 163 | [ISO-8859-1](http://www.htmlhelp.com/reference/charset/) 164 | 165 | -------------------------------------------------------------------------------- /每个程序员必读系列/What Every Programmer Should Know about Memory.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlankKelly/ConcurrencyNote/4596da2d8592a53c7d60e1df35f67dd0f65b7cae/每个程序员必读系列/What Every Programmer Should Know about Memory.pdf -------------------------------------------------------------------------------- /每个程序员必读系列/What Every Programmer Should know about Time.md: -------------------------------------------------------------------------------- 1 | ###What Every Programmer Should know about Time 2 | 3 | Some notes about time: 4 | 5 | - **UTC:** The time at zero degrees longitude (the Prime Meridian) is called Coordinated Universal Time (UTC is a compromise between the French and English initialisms) 6 | - **GMT: **UTC used to be called Greenwich Mean Time (GMT) because the Prime Meridian was (arbitrarily) chosen to pass through the Royal Observatory in Greenwich. 7 | - Other timezones can be written as an offset from UTC. Australian Eastern Standard Time is UTC+1000. e.g. 10:00 UTC is 20:00 EST on the same day. 8 | - **Daylight saving** does not affect UTC. It's just a polity deciding to change its timezone (offset from UTC). For example, GMT is still used: it's the British national timezone in winter. In summer it becomes BST. 9 | - **Leap Seconds: **By international convention, UTC (which is an arbitrary human invention) is kept within 0.9 seconds of physical reality (UT1, which is a measure of solar time) by introducing a "leap second" in the last minute of the UTC year, or in the last minute of June. 10 | - Leap seconds don't have to be announced much more than six months before they happen. This is a problem if you need second-accurate planning beyond six months. 11 | - **Unix time: **Measured as the number of seconds since epoch (the beginning of 1970 in UTC). Unix time is not affected by time zones or daylight saving. 12 | - According to POSIX.1, Unix time is supposed to handle a leap second by replaying the previous second. e.g.: 13 | 14 | 59.00 15 | 59.25 16 | 59.50 17 | 59.75 18 | 59.00 ← replay 19 | 59.25 20 | 59.50 21 | 59.75 22 | 00.00 ← increment 23 | 00.25 24 | 25 | This is a trade-off: you can't represent a leap second, and your time is guaranteed to go backwards. On the other hand, every day has exactly 86,400 seconds, and you don't need a table of all previous and future leap seconds in order to format Unix time as human-preferred hours-minutes-seconds. 26 | 27 | - See also: my write-up on the 2012 leap second, along with logs from several systems showing the replay behavior as well as Google's "leap smear." 28 | - ntpd can be configured with the leapfile directive to announce an upcoming leap second. Most leaf installations don't bother with this, and rely on enough of their upstreams getting it right. 29 | 30 | Some time-related considerations when programming: 31 | 32 | - **Timezone are a presentation-layer problem!** Most of your code shouldn't be dealing with timezones or local time, it should be passing Unix time around. 33 | - libc and/or your language's runtime has code to do timezone conversions and formatting. This stuff is tricky, avoid re-implementing it yourself. 34 | - When measuring an interval of time, use a monotonic (always increasing) clock. Read the clock_gettime() manpage. 35 | - When recording a point in time, measure Unix time. It's UTC. It's easy to obtain. It doesn't have timezone offsets or daylight saving (or leap seconds). 36 | - When storing a timestamp, store Unix time. It's a single number. 37 | - If you want to store a humanly-readable time (e.g. logs), consider storing it along with Unix time, not instead of Unix time. 38 | - When displaying time, always include the timezone offset. A time format without an offset is useless. 39 | - The system clock is inaccurate. 40 | - You're on a network? Every other system's clock is differently inaccurate. 41 | - The system clock can, and will, jump backwards and forwards in time due to things outside of your control. Your program should be designed to survive this. 42 | - The number of [clock] seconds per [real] second is both inaccurate and variable. It mostly varies with temperature. 43 | - ntpd can change the system time in two ways: 44 | - Step: making the clock jump backwards or forwards to the correct time instantaneously. 45 | - Slew: changing the frequency of the clock so that it slowly drifts toward the correct time. 46 | - Slew is preferred because it's less disruptive, but it's only useful for correcting small offsets. 47 | 48 | Special mentions: 49 | 50 | - Leap seconds happen more often than leap years. 51 | - Time passes at a rate of one second per second for every observer. The frequency of a remote clock relative to an observer is affected by velocity and gravity. The clocks inside GPS satellites are adjusted for relativistic effects. (A clock moving faster than you appears to tick more slowly.) 52 | - MySQL (at least 4.x and 5.x) stores the DATETIME type as a binary encoding of its "YYYY-MM-DD HH:MM:SS" string. It does not store an offset, and interprets times according to @@session.time_zone: 53 | 54 | mysql> insert into times values(now()); 55 | mysql> select unix_timestamp(t) from times; 56 | 1310128044 57 | mysql> SET SESSION time_zone='+0:00'; 58 | mysql> select unix_timestamp(t) from times; 59 | 1310164044 60 | 61 | 62 | There's also a TIMESTAMP type, which is stored as UNIX time, but has other magic. 63 | 64 | In conclusion, if you care at all about storing timestamps in MySQL, store them as integers and use the UNIX_TIMESTAMP() and FROM_UNIXTIME() functions. 65 | -------------------------------------------------------------------------------- /比较和替换.md: -------------------------------------------------------------------------------- 1 | ###比较和替换 2 | 比较和替换(Compare and swap)是设计并发算法时用到的一种技术。简单来说,比较和替换是使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替换当前变量的值。这听起来可能有一点复杂但是实际上你理解之后发现很简单,接下来,让我们跟深入的了解一下这项技术。 3 | 4 | ####CAS的使用场景 5 | 6 | 在程序和算法中一个经常出现的模式就是“check and act"模式。先检查后操作模式发生在代码中首先检查一个变量的值,然后再基于这个值做一些操作。下面是一个简单的示例: 7 | 8 | class MyLock{ 9 | private boolean locked = false; 10 | 11 | public boolean lock(){ 12 | if(!locked){ 13 | locked = true; 14 | return true; 15 | } 16 | return false; 17 | } 18 | } 19 | 20 | 上面这段代码,如果用在多线程的程序会出现很多错误,不过现在请忘掉它。 21 | 22 | 如你所见,lock()方法首先检查locked>成员变量是否等于false,如果等于,就将locked设为true。 23 | 24 | 如果同个线程访问同一个MyLock实例,上面的lock()将不能保证正常工作。如果一个线程检查locked的值,然后将其设置为false,与此同时,一个线程B也在检查locked的值,又或者,在线程A将locked的值设为false之前。因此,线程A和线程B可能都看到locked的值为false,然后两者都基于这个信息做一些操作。 25 | 26 | 为了在一个多线程程序中良好的工作,"check then act" 操作必须是原子的。原子就是说”check“操作和”act“被当做一个原子代码块执行。不存在多个线程同时执行原子块。 27 | 28 | 下面是一个代码示例,把之前的lock()方法用synchronized关键字重构成一个原子块。 29 | 30 | class MyLock{ 31 | private boolean locked = false; 32 | 33 | public synchronized boolean lock(){ 34 | if(!locked){ 35 | locked = true; 36 | return true; 37 | } 38 | return false; 39 | } 40 | } 41 | 42 | 现在lock()方法是同步的,所以,在某一时刻只能有一个线程在同一个MyLock实例上执行它。 43 | 44 | 原子的lock方法实际上是一个”compare and swap“的例子。 45 | 46 | ####CAS 用作原子操作 47 | 48 | 现在CPU内部已经执行原子的CAS操作。Java5以来,你可以使用java.util.concurrent.atomic包中的一些原子类来使用CPU中的这些功能。 49 | 50 | 下面是一个使用AtomicBoolean类实现lock()方法的例子: 51 | 52 | public static class MyLock{ 53 | private AtomicBoolean locked = new AtomicBoolean(false); 54 | 55 | public boolean lock(){ 56 | return locked.compareAndSet(false, true); 57 | } 58 | } 59 | 60 | locked变量不再是boolean类型而是AtomicBoolean。这个类中有一个compareAndSet()方法,它使用一个期望值和AtomicBoolean实例的值比较,和两者相等,则使用一个新值替换原来的值。在这个例子中,它比较locked的值和false,如果locked的值为false,则把修改为true。 61 | 62 | 如果值被替换了,compareAndSet()返回true,否则,返回false。 63 | 64 | 使用Java5+提供的CAS特性而不是使用自己实现的的好处是Java5+中内置的CAS特性可以让你利用底层的你的程序所运行机器的CPU的CAS特性。这会使使用了CAS的代码运行更快。 65 | -------------------------------------------------------------------------------- /线程信号.md: -------------------------------------------------------------------------------- 1 | ####线程信号 2 | 线程信号的主要目的是可以让线程给其他线程发信号。另外,线程信号也可以让线程等待其他线程的信号。 3 | 4 | #####通过共享对象发信号 5 | 让线程给其他线程发送信号的一种简单方式就是在某些共享对象变量上这只信号量。线程A可能通过一个同步代码块设置boolean类型的成员变量为true,线程B可能通过同步代码块读取这个变量。 6 | 7 | public class MySignal{ 8 | protected boolean hasDataToProcess = false; 9 | 10 | public synchronized boolean hasDataToProcess(){ 11 | return this.hasDataToProcess; 12 | } 13 | 14 | public synchronized void setHasDataToProcess(boolean hasData){ 15 | this.hasDataToProcess = hasData; 16 | } 17 | } 18 | 19 | #####忙等待 20 | 21 | protected MySignal = sharedSignal = ... 22 | ... 23 | while(!sharedSignal.hasDataToProcess()){ 24 | //do nothing ... busy wait 25 | } 26 | 27 | #####wait() notify() 和 notifyAll() 28 | 29 | 忙等待对于计算机中的CPU来说并不是一种高效的使用,除非平均等待的时间很短。另外,如果等待线程可以休眠或者进入非活跃状态直到收到信号。 30 | 31 | Java已经内建了等待机制,允许线程在等待信号时进入非活跃状态。java.lang.Object类定义了三个方法wait()notify()notifyAll()提供了对这种等待机制的支持。 32 | 33 | 一个线程可以在任何对象上调用wait()方法进入休眠状态直到其他线程砸这个对象上调用notify方法。为了调用notify()或者唤醒调用的线程必须首先获得此对象上的锁。换句话说,调用线程必须在同步代码块中调用wait()notify()方法。 34 | 35 | public class MonitorObject{ 36 | 37 | } 38 | 39 | public class MyWaitNotify{ 40 | MonitorObject myMonitorObject = new MonitorObject(); 41 | 42 | public void doWait(){ 43 | synchronized(myMonitorObject){ 44 | try{ 45 | myMonitorObject.wait(); 46 | }catch(InterruptedException e){ 47 | ... 48 | } 49 | } 50 | } 51 | 52 | public void doNotify(){ 53 | synchronized(myMonitorObject){ 54 | myMonitorObject.notify(); 55 | } 56 | } 57 | } 58 | 59 | 等待线程将会调用doWait()方法,唤醒线程将会调用doNotify()方法。当一个线程在一个对象上调用notify()方法,等待在这个对象上的线程之一将会被唤醒,继续执行。如果调用notifyAll()方法,则等待在该对象上的所有线程都会被唤醒。 60 | 61 | 所有对象都必须获得对象上的锁,才能调用wait()等方法,都则会抛出一个IllegalMonitorStateException异常。 62 | 63 | #####忽略信号 64 | 65 | 如果没有线程在等待时调用notify()notifyAll()方法的话,唤醒信号将会被忽略。因此,如果一个线程调用notify()方法在这个线程收到等待信号之前,这个唤醒信号将会被忽略。这可能不会引起什么问题,但在某些场景下,这将会引起等待线程永远等待,永远不会被唤醒,因为唤醒信号被忽略了。 66 | 67 | 为了防止丢失信号它们应该被保存在信号类中。在上面的例子中唤醒信号应该保存在MyWaitNotify实例的一个成员变量中。下面是一个MyWaitNotify的修改版本: 68 | 69 | public class MyWaitNotify2{ 70 | MonitorObject myMontiorObject = new MonitorObject(); 71 | boolean wasSingnalled = false; 72 | 73 | public void doWait(){ 74 | synchronized(myMonitorObject){ 75 | if(!wasSingnalled){ 76 | try{ 77 | myMonitorObject.wait(); 78 | }catch(InterruptedException e){...} 79 | } 80 | wasSignalled = false; 81 | } 82 | } 83 | 84 | public void doNotify(){ 85 | synchronized(myMonitorObject){ 86 | wasSignalled = true; 87 | myMonitorObject.notify(); 88 | } 89 | } 90 | } 91 | 92 | 93 | #####伪唤醒(Spurious Wakeups) 94 | 由于莫名其妙的原因,在没有调用notify()或者notifyAll()方法的情况下,线程被唤醒是有可能的。这就是我们熟知的“伪唤醒”。没有任何缘由的唤醒。 95 | 96 | 如果“伪唤醒”发生在MyWaitNotify2类的doWait()方法中,等待线程可能在没有收到适当的信号的情况下继续执行。这会在你的程序中引发很严重的问题。 97 | 98 | 为了避免“为唤醒”,我们应该在while循环语句中检查信号成员变量而不是在if语句中。像这样的while循环我们称之为**自旋锁**。 99 | 100 | public class MyWaitNotify3{ 101 | MonitorObject myMonitorObject = new MonitorObject(); 102 | boolean wasSignalled = false; 103 | 104 | public void doWait(){ 105 | synchronized(myMonitorObject){ 106 | while(!wasSignalled){ 107 | try{ 108 | myMonitorObject.wait(); 109 | }catch(InterruptedException e){...} 110 | } 111 | 112 | wasSignalled = false; 113 | } 114 | } 115 | 116 | 117 | public void doNotify(){ 118 | synchronized(myMonitorObject){ 119 | wasSignalled = true; 120 | myMonitorObject,notify(); 121 | } 122 | } 123 | } 124 | 125 | #####多个线程等待同一个信号 126 | 当多个线程等待时,while循环也是一个很好的解决方案,使用notifyAll方法唤醒所有等待的线程,不过只有一个线程被允许执行。在某一个时刻,只有一个线程被允许获得监视对象的锁,也就是说只有一个线程对出wait()方法。只要这个线程离开了doWait()方法中的同步代码块,其他线程就可以退出wait()调用,然后检查while循环内的wasSignalled成员变量,这个标识被第一个唤醒的线程清除。所以,剩余被唤醒的线程继续等待,知道下一个信号到达。 127 | 128 | #####不要在常量字符串或全局对象上调用wait() 129 | 130 | public class MyWaitNotify{ 131 | 132 | String myMonitorObject = ""; 133 | boolean wasSignalled = false; 134 | 135 | public void doWait(){ 136 | synchronized(myMonitorObject){ 137 | while(!wasSignalled){ 138 | try{ 139 | myMonitorObject.wait(); 140 | } catch(InterruptedException e){...} 141 | } 142 | //clear signal and continue running. 143 | wasSignalled = false; 144 | } 145 | } 146 | 147 | public void doNotify(){ 148 | synchronized(myMonitorObject){ 149 | wasSignalled = true; 150 | myMonitorObject.notify(); 151 | } 152 | } 153 | } 154 | 155 | 在空的常量字符串或者其它字符串的问题在于,在JVM内部会把常量字符串转换为同一个对象。这就是说,即使你有两个不同的MyWaitNotify实例,它们都指向同一个空字符串实例。或者说,你在第二个MyWaitNotify上调用doNotify()方法有可能会唤醒在第一个MyWaitNotify实例上调用doWait()方法的线程。 156 | 157 | ![empty_constant_string](http://tutorials.jenkov.com/images/java-concurrency/strings-wait-notify.png) 158 | 159 | 记住,虽然四个线程在同一个共享字符串实例上调用wait()notify方法,来自doWait()doNotify()方法的信号也会被分别存放在两个MyWaitNotify实例中。在MyWaitNofity1上调用doNotify()可能会唤醒等待在MyWaitNotify2上的线程,但是这个信号仅被存放在MyWaitNotify1中。 160 | 161 | 首先,这看起来可能不是一个大问题。After all, if doNotify() is called on the second MyWaitNotify instance all that can really happen is that Thread A and B are awakened by mistake. This awakened thread (A or B) will check its signal in the while loop, and go back to waiting because doNotify() was not called on the first MyWaitNotify instance, in which they are waiting. This situation is equal to a provoked spurious wakeup. Thread A or B awakens without having been signaled. But the code can handle this, so the threads go back to waiting. 162 | 163 | After all, if doNotify() is called on the second MyWaitNotify instance all that can really happen is that Thread A and B are awakened by mistake. This awakened thread (A or B) will check its signal in the while loop, and go back to waiting because doNotify() was not called on the first MyWaitNotify instance, in which they are waiting. This situation is equal to a provoked spurious wakeup. Thread A or B awakens without having been signaled. But the code can handle this, so the threads go back to waiting. 164 | 165 | If the doNotify() method had called notifyAll() instead of notify(), all waiting threads had been awakened and checked for signals in turn. Thread A and B would have gone back to waiting, but one of either C or D would have noticed the signal and left the doWait() method call. The other of C and D would go back to waiting, because the thread discovering the signal clears it on the way out of doWait(). 166 | 167 | You may be tempted then to always call notifyAll() instead notify(), but this is a bad idea performance wise. There is no reason to wake up all threads waiting when only one of them can respond to the signal. 168 | 169 | So: Don't use global objects, string constants etc. for wait() / notify() mechanisms. Use an object that is unique to the construct using it. For instance, each MyWaitNotify3 (example from earlier sections) instance has its own MonitorObject instance rather than using the empty string for wait() / notify() calls. -------------------------------------------------------------------------------- /线程池.md: -------------------------------------------------------------------------------- 1 | ###线程池 2 | 线程池(Thread Pool)对于限制应用程序中同一时刻运行的线程数很有用。因为每启动一个新线程都会有相应的性能开销,每个线程都需要给栈分配一些内存等等。 3 | 4 | 我们可以把并发执行的任务传递给一个线程池,来替代为每个并发执行的任务都启动一个新的线程。只要池里有空闲的线程,任务就会分配给一个线程执行。在线程池的内部,任务被插入一个阻塞队列(Blocking Queue ),线程池里的线程会去取这个队列里的任务。当一个新任务插入队列时,一个空闲线程就会成功的从队列中取出任务并且执行它。 5 | 6 | 7 | 线程池经常应用在多线程服务器上。每个通过网络到达服务器的连接都被包装成一个任务并且传递给线程池。线程池的线程会并发的处理连接上的请求。以后会再深入有关 Java 实现多线程服务器的细节。 8 | 9 | Java 5 在 java.util.concurrent 包中自带了内置的线程池,所以你不用非得实现自己的线程池。你可以阅读我写的 java.util.concurrent.ExecutorService 的文章以了解更多有关内置线程池的知识。不过无论如何,知道一点关于线程池实现的知识总是有用的。 10 | 11 | 下面是一个简单的线程池实现: 12 | 13 | public class ThreadPool{ 14 | private BlockingQueue taskQueue = null; 15 | private List threads = new ArrayList(); 16 | private boolean isStopped = false; 17 | 18 | public ThreadPool(int noOfThreads, int maxNoOfTasks){ 19 | taskQueue = new BlockingQueue(maxNoOfTasks); 20 | 21 | for(int i = 0; i < noOfThreads; i++){ 22 | threads.add(new PoolThread(taskQueue)); 23 | } 24 | 25 | for(PoolThread thread : threads){ 26 | thread.start(); 27 | } 28 | } 29 | 30 | 31 | public synchronized void execute(Runnable task)throws Exception{ 32 | if(this.isStopped) 33 | throw new IllegalStateException("ThreadPool is stopped"); 34 | this.taskQueue.enqueue(task); 35 | } 36 | 37 | public synchronized void stop(){ 38 | this.isStopped = true; 39 | for(PoolThread thread : threads){ 40 | thread.stop(); 41 | } 42 | } 43 | } 44 | 45 | public class PoolThread extends Thread{ 46 | private BlockingQueue taskQueue = null; 47 | private boolean isStopped = false; 48 | 49 | public PoolThread(BlockingQueue queue){ 50 | taskQueue = queue; 51 | } 52 | 53 | public void run(){ 54 | while(!isStopped){ 55 | try{ 56 | Runnable runnable = (Runnable)taskQueue.dequeue(); 57 | runnable.run(); 58 | }catch(Exception e){ 59 | //log or otherwise report exception. 60 | //but keep pool thread alive 61 | } 62 | } 63 | } 64 | 65 | public synchronized void soStop(){ 66 | isStopped = true; 67 | this.interrupt(); //break pool thread of dequeue() call. 68 | } 69 | 70 | public synchronized boolean isStopped(){ 71 | return isStopped; 72 | } 73 | } 74 | 75 | 76 | 线程池的实现由两部分组成。类 ThreadPool 是线程池的公开接口,而类 PoolThread 用来实现执行任务的子线程。 77 | 78 | 为了执行一个任务,方法 ThreadPool.execute(Runnable r) 用 Runnable 的实现作为调用参数。在内部,Runnable 对象被放入 阻塞队列 (Blocking Queue),等待着被子线程取出队列。 79 | 80 | 一个空闲的 PoolThread 线程会把 Runnable 对象从队列中取出并执行。你可以在 PoolThread.run() 方法里看到这些代码。执行完毕后,PoolThread 进入循环并且尝试从队列中再取出一个任务,直到线程终止。 81 | 82 | 调用 ThreadPool.stop() 方法可以停止 ThreadPool。在内部,调用 stop 先会标记 isStopped 成员变量(为 true)。然后,线程池的每一个子线程都调用 PoolThread.stop() 方法停止运行。注意,如果线程池的 execute() 在 stop() 之后调用,execute() 方法会抛出 IllegalStateException 异常。 83 | 84 | 子线程会在完成当前执行的任务后停止。注意 PoolThread.stop() 方法中调用了 this.interrupt()。它确保阻塞在 taskQueue.dequeue() 里的 wait() 调用的线程能够跳出 wait() 调用(校对注:因为执行了中断interrupt,它能够打断这个调用),并且抛出一个 InterruptedException 异常离开 dequeue() 方法。这个异常在 PoolThread.run() 方法中被截获、报告,然后再检查 isStopped 变量。由于 isStopped 的值是 true, 因此 PoolThread.run() 方法退出,子线程终止。 85 | 86 | -------------------------------------------------------------------------------- /重入锁死.md: -------------------------------------------------------------------------------- 1 | ###重入锁死 2 | 3 | 重入锁死是一种类似于死锁和嵌套管程失败的情景。 4 | 5 | 如果一个线程重入获得了一个非重入的锁,读写锁或者一些其他的同步器就会发生重入锁死。重入意味着一个线程已经持有了一个锁可以再次持有它。Java的同步块是可以冲入的。因此,下面这段代码执行将不会出现问题。 6 | 7 | public class Reentrant{ 8 | public synchronized outer(){ 9 | inner(); 10 | } 11 | 12 | public synchronized inner(){ 13 | //do something 14 | } 15 | } 16 | 17 | outerinner方法都被声明为synchronized,这等同于一个synchronized(this)块。如果一个线程在outer()方法里面调用inner()方法将不会出现问题,因为这两个方法都被同步在同一个管程对象"this"上。如果一个线程已经持有了一个管程对象上的锁,它就可以访问同一个管程对象上所有的同步块。这被称作**可重入**。 18 | 19 | 下面Lock的实现是不可重入的: 20 | 21 | public class Lock{ 22 | private boolean isLocked = false; 23 | 24 | public synchronized void lock()throws InterruptedException{ 25 | while(isLocked){ 26 | wait(); 27 | } 28 | isLocked = true; 29 | } 30 | 31 | public synchronized void unlock(){ 32 | isLocked = false; 33 | notify(); 34 | } 35 | } 36 | 37 | 如果一个线程两次调用lock()方法而在两次调用之间没有调用unlock(),第二次调用lock()将会阻塞。一个重入锁死就发生了。 38 | 39 | 要避免重入锁死你有两种选择: 40 | - **编写代码避免获取已经持有的锁** 41 | - **使用可重入锁** 42 | 43 | 使用哪种方法更适合于你的程序取决于具体的情景。可重入锁的性能常常不如非重入锁,而且更难实现,可重入锁通常没有不可重入锁那么好的表现,而且实现起来复杂,但这些情况在你的项目中也许算不上什么问题。无论你的项目用锁来实现方便还是不用锁方便,可重入特性都需要根据具体问题具体分析。 44 | 45 | 46 | -------------------------------------------------------------------------------- /阻塞队列.md: -------------------------------------------------------------------------------- 1 | ###阻塞队列 2 | 阻塞队列与普通队列的区别在于,当队列是空的时,从队列中获取元素的操作将会被阻塞,或者当队列是满时,往队列里添加元素的操作会被阻塞。试图从空的阻塞队列中获取元素的线程将会被阻塞,直到其他的线程往空的队列插入新的元素。同样,试图往已满的阻塞队列中添加新元素的线程同样也会被阻塞,直到其他的线程使队列重新变得空闲起来,如从队列中移除一个或者多个元素,或者完全清空队列,下图展示了如何通过阻塞队列来合作: 3 | 4 | ![blocking queue](http://tutorials.jenkov.com/images/java-concurrency-utils/blocking-queue.png) 5 | 6 | 线程1往阻塞队列中添加元素,而线程2从阻塞队列中移除元素 7 | 8 | 从5.0开始,JDK在java.util.concurrent包里提供了阻塞队列的官方实现。尽管JDK中已经包含了阻塞队列的官方实现,但是熟悉其背后的原理还是很有帮助的。 9 | 10 | ####阻塞队列实现 11 | 12 | 阻塞队列的实现类似于有上限的Semaphore。下面是一个阻塞队列的简单实现: 13 | 14 | public class BlockingQueue{ 15 | private List queue = new LinkedList(); 16 | private int limit = 10; 17 | 18 | public BlockingQueue(int limit){ 19 | this.limit = limit; 20 | } 21 | 22 | public synchronized void enqueue(Object item)throws InterruptedExce{ 23 | while(queue.size() == limit){ 24 | wait(); 25 | } 26 | 27 | if(this.queue.size() == 0){ 28 | notifyAll(); 29 | } 30 | 31 | this.queue.add(item); 32 | } 33 | 34 | public synchronized Object dequeue()throws InterruptedException{ 35 | while(this.queue.size() == 0){ 36 | wait(); 37 | } 38 | 39 | if(this.queue.size() == this.limit){ 40 | notifyAll(); 41 | } 42 | 43 | return this.queue.remove(0); 44 | } 45 | } 46 | 47 | 48 | 必须注意到,在enqueue和dequeue方法内部,只有队列的大小等于上限(limit)或者下限(0)时,才调用notifyAll方法。如果队列的大小既不等于上限,也不等于下限,任何线程调用enqueue或者dequeue方法时,都不会阻塞,都能够正常的往队列中添加或者移除元素。 -------------------------------------------------------------------------------- /阿姆达尔定律.md: -------------------------------------------------------------------------------- 1 | ###阿姆达尔定律 2 | 阿姆达尔定律可以用来计算处理器平行运算之后效率提升的能力。阿姆达尔定律因Gene Amdal 在1967年提出这个定律而得名。绝大多数使用并行或并发系统的开发者有一种并发或并行可能会带来提速的感觉,甚至不知道阿姆达尔定律。不管怎样,了解阿姆达尔定律还是有用的。 3 | 4 | 我会首先以算术的方式介绍阿姆达尔定律定律,然后再用图表演示一下。 5 | 6 | ####阿姆达尔定律定义 7 | 8 | 一个程序(或者一个算法)可以按照是否可以被并行化分为下面两个部分: 9 | - **可以被并行化的部分** 10 | - **不可以被并行化的部分** 11 | 假设一个程序处理磁盘上的文件。这个程序的一小部分用来扫描路径和在内存中创建文件目录。做完这些后,每个文件交个一个单独的线程去处理。扫描路径和创建文件目录的部分不可以被并行化,不过处理文件的过程可以。 12 | 13 | 程序串行(非并行)执行的总时间我们记为T。时间T包括不可以被并行和可以被并行部分的时间。不可以被并行的部分我们记为B。那么可以被并行的部分就是T-B。下面的列表总结了这些定义: 14 | - **T = 串行执行的总时间** 15 | - **B = 不可以并行的总时间** 16 | - **T- B = 并行部分的总时间** 17 | 从上面可以得出: 18 | 19 | >T = B + (T - B) 20 | 21 | 首先,这个看起来可能有一点奇怪,程序的可并行部分在上面这个公式中并没有自己的标识。然而,由于这个公式中可并行可以用总时间T 和 B(不可并行部分)表示出来,这个公式实际上已经从概念上得到了简化,也即是指以这种方式减少了变量的个数。 22 | 23 | T- B 是可并行化的部分,以并行的方式执行可以提高程序的运行速度。可以提速多少取决于有多少线程或者多少个CPU来执行。线程或者CPU的个数我们记为N。可并行化部分被执行的最快时间可以通过下面的公式计算出来: 24 | >(T - B ) / N 25 | 26 | 或者通过这种方式 27 | >(1 / N) * (T - B) 28 | 29 | 维基中使用的是第二种方式。 30 | 31 | 根据阿姆达尔定律,当一个程序的可并行部分使用N个线程或CPU执行时,执行的总时间为: 32 | >T(N) = B + ( T - B ) / N 33 | 34 | T(N)指的是在并行因子为N时的总执行时间。因此,T(1)就执行在并行因子为1时程序的总执行时间。使用T(1)代替T,阿姆达尔定律定律看起来像这样: 35 | >T(N) = B + (T(1) - B) / N 36 | 37 | 表达的意思都是是一样的。 38 | 39 | ####一个计算例子 40 | 41 | 为了更好的理解阿姆达尔定律,让我们来看一个计算的例子。执行一个程序的总时间设为1.程序的不可并行化占40%,按总时间1计算,就是0.4.可并行部分就是1 - 0.4 = 0.6. 42 | 43 | 在并行因子为2的情况下,程序的执行时间将会是: 44 | 45 | T(2) = 0.4 + ( 1 - 0.4 ) / 2 46 | = 0.4 + 0.6 / 2 47 | = 0.4 + 0.3 48 | = 0.7 49 | 50 | 在并行因子为5的情况下,程序的执行时间将会是: 51 | 52 | T(5) = 0.4 + ( 1 - 0.4 ) / 5 53 | = 0.4 + 0.6 / 6 54 | = 0.4 + 0.12 55 | = 0.52 56 | 57 | ####阿姆达尔定律图示 58 | 59 | 为了更好地理解阿姆达尔定律,我会尝试演示这个定定律是如何诞生的。 60 | 61 | 首先,一个程序可以被分割为两部分,一部分为不可并行部分B,一部分为可并行部分1 - B。如下图: 62 | 63 | ![amdahl' s law1](http://tutorials.jenkov.com/images/java-concurrency/amdahls-law-1.png) 64 | 65 | 在顶部被带有分割线的那条直线代表总时间 T(1)。 66 | 67 | 下面你可以看到在并行因子为2的情况下的执行时间: 68 | 69 | ![amdahl's law2](http://tutorials.jenkov.com/images/java-concurrency/amdahls-law-2.png) 70 | 71 | 并行因子为3的情况: 72 | 73 | ![amdahl' s law3](http://tutorials.jenkov.com/images/java-concurrency/amdahls-law-3.png) 74 | 75 | ####优化算法 76 | 77 | 从阿姆达尔定律可以看出,程序的可并行化部分可以通过使用更多的硬件(更多的线程或CPU)运行更快。对于不可并行化的部分,只能通过优化代码来达到提速的目的。因此,你可以通过优化不可并行化部分来提高你的程序的运行速度和并行能力。你可以对不可并行化在算法上做一点改动,如果有可能,你也可以把一些移到可并行化放的部分。 78 | 79 | #####优化串行分量 80 | 81 | 如果你优化一个程序的串行化部分,你也可以使用阿姆达尔定律来计算程序优化后的执行时间。如果不可并行部分通过一个因子O来优化,那么阿姆达尔定律看起来就像这样: 82 | 83 | T(O, N) = B / O + (1 - B / O) / N 84 | 85 | 记住,现在程序的不可并行化部分占了B / O的时间,所以,可并行化部分就占了1 - B / O的时间. 86 | 87 | 如果B为0.1,O为2,N为5,计算看起来就像这样: 88 | 89 | T(2,5) = 0.4 / 2 + (1 - 0.4 / 2) / 5 90 | = 0.2 + (1 - 0.4 / 2) / 5 91 | = 0.2 + (1 - 0.2) / 5 92 | = 0.2 + 0.8 / 5 93 | = 0.2 + 0.16 94 | = 0.36 95 | 96 | ####运行时间 vs. 加速 97 | 98 | 到目前为止,我们只用阿姆达尔定律计算了一个程序或算法在优化后或者并行化后的执行时间。我们也可以使用阿姆达尔定律计算加速比(speedup),也就是经过优化后或者串行化后的程序或算法比原来快了多少。 99 | 100 | 如果旧版本的程序或算法的执行时间为T,那么增速比就是: 101 | 102 | Speedup = T / T(O , N); 103 | 104 | 为了计算执行时间,我们常常把T设为1,加速比为原来时间的一个分数。公式大致像下面这样: 105 | 106 | Speedup = 1 / T(O,N) 107 | 如果我们使用阿姆达尔定律来代替T(O,N),我们可以得到下面的公式: 108 | 109 | Speedup = 1 / ( B / O + (1 - B / O) / N) 110 | 111 | 如果B = 0.4, O = 2, N = 5, 计算变成下面这样: 112 | 113 | Speedup = 1 / ( 0.4 / 2 + (1 - 0.4 / 2) / 5) 114 | = 1 / ( 0.2 + (1 - 0.4 / 2) / 5) 115 | = 1 / ( 0.2 + (1 - 0.2) / 5 ) 116 | = 1 / ( 0.2 + 0.8 / 5 ) 117 | = 1 / ( 0.2 + 0.16 ) 118 | = 1 / 0.36 119 | = 2.77777 ... 120 | 121 | 上面的计算结果可以看出,如果你通过一个因子2来优化不可并行化部分,一个因子5来并行化可并行化部分,这个程序或算法的最新优化版本最多可以比原来的版本快2.77777倍。 122 | 123 | ####测量,不要仅是计算 124 | 125 | 虽然阿姆达尔定律允许你并行化一个算法的理论加速比,但是不要过度依赖这样的计算。在实际场景中,当你优化或并行化一个算法时,可以有很多的因子可以被考虑进来。 126 | 127 | 内存的速度,CPU缓存,磁盘,网卡等可能都是一个限制因子。如果一个算法的最新版本是并行化的,但是导致了大量的CPU缓存浪费,你可能不会再使用x N个CPU来获得x N的期望加速。如果你的内存总线(memory bus),磁盘,网卡或者网络连接都处于高负载状态,也是一样的情况。 128 | 129 | 我们的建议是,使用阿姆达尔定律定律来指导我们优化程序,而不是用来测量优化带来的实际加速比。记住,有时候一个高度串行化的算法胜过一个并行化的算法,因为串行化版本不需要进行协调管理(上下文切换),而且一个单个的CPU在底层硬件工作(CPU管道、CPU缓存等)上的一致性可能更好。 -------------------------------------------------------------------------------- /非阻塞算法.md: -------------------------------------------------------------------------------- 1 | ###非阻塞算法 2 | 在并发上下文中,非阻塞算法是一种允许线程在阻塞其他线程的情况下访问共享状态的算法。在绝大多数项目中,在算法中如果一个线程的挂起没有导致其它的线程挂起,我们就说这个算法是非阻塞的。 3 | 4 | 为了更好的理解阻塞算法和非阻塞算法之间的区别,我会先讲解阻塞算法然后再讲解非阻塞算法。 5 | 6 | ####阻塞并发算法 7 | 8 | 一个阻塞并发算法一般分下面两步: 9 | 10 | - **执行线程请求的操作** 11 | - **阻塞线程直到可以安全地执行操作** 12 | 13 | 很多算法和并发数据结构都是阻塞的。例如,java.util.concurrent.BlockingQueue的不同实现都是阻塞数据结构。如果一个线程要往一个阻塞队列中插入一个元素,队列中没有足够的空间,执行插入操作的线程就会阻塞直到队列中有了可以存放插入元素的空间。 14 | 15 | 下图演示了一个阻塞算法保证一个共享数据结构的行为: 16 | 17 | ![concurrency](http://tutorials.jenkov.com/images/java-concurrency/non-blocking-algorithms-1.png) 18 | 19 | ####非阻塞并发算法 20 | 21 | 一个非阻塞并发算法一般包含下面两步: 22 | 23 | - **执行线程请求的操作** 24 | - **通知请求线程操作不能被执行** 25 | 26 | Java也包含几个非阻塞数据结构。AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference都是非阻塞数据结构的例子。 27 | 28 | 下图演示了一个非阻塞算法保证一个共享数据结构的行为: 29 | 30 | ![Non-concurrency](http://tutorials.jenkov.com/images/java-concurrency/non-blocking-algorithms-2.png) 31 | 32 | ####非阻塞算法 vs 阻塞算法 33 | 34 | 阻塞算法和非阻塞算法的主要不同在于上面两部分描述的它们的行为的第二步。换句话说,它们之间的不同在于当请求操作不能够执行时阻塞算法和非阻塞算法会怎么做。 35 | 36 | 阻塞算法会阻塞线程知道请求操作可以被执行。非阻塞算法会通知请求线程操作不能够被执行,并返回。 37 | 38 | 一个使用了阻塞算法的线程可能会阻塞直到有可能去处理请求。通常,其它线程的动作使第一个线程执行请求的动作成为了可能。 如果,由于某些原因线程被阻塞在程序某处,因此不能让第一个线程的请求动作被执行,第一个线程会阻塞——可能一直阻塞或者直到其他线程执行完必要的动作。 39 | 40 | 例如,如果一个线程产生往一个已经满了的阻塞队列里插入一个元素,这个线程就会阻塞,直到其他线程从这个阻塞队列中取走了一些元素。如果由于某些原因,从阻塞队列中取元素的线程假定被阻塞在了程序的某处,那么,尝试往阻塞队列中添加新元素的线程就会阻塞,要么一直阻塞下去,要么知道从阻塞队列中取元素的线程最终从阻塞队列中取走了一个元素。 41 | 42 | ####非阻塞并发数据结构 43 | 44 | 在一个多线程系统中,线程间通常通过一些数据结构”交流“。例如可以是任何的数据结构,从变量到更高级的俄数据结构(队列,栈等)。为了确保正确,并发线程在访问这些数据结构的时候,这些数据结构必须由一些并发算法来保证。这些并发算法让这些数据结构成为**并发数据结构**。 45 | 46 | 如果某个算法确保一个并发数据结构是阻塞的,它就被称为是一个**阻塞算法**。这个数据结构也被称为是一个**阻塞,并发数据结构**。 47 | 48 | 如果某个算法确保一个并发数据结构是非阻塞的,它就被称为是一个**非阻塞算法**。这个数据结构也被称为是一个**非阻塞,并发数据结构**。 49 | 50 | 每个并发数据结构被设计用来支持一个特定的通信方法。使用哪种并发数据结构取决于你的通信需要。在接下里的部分,我会引入一些非阻塞并发数据结构,并讲解它们各自的适用场景。通过这些并发数据结构工作原理的讲解应该能在非阻塞数据结构的设计和实现上一些启发。 51 | 52 | ####Volatile 变量 53 | 54 | Java中的volatile变量是直接从主存中读取值的变量。当一个新的值赋给一个volatile变量时,这个值总是会被立即写回到主存中去。这样就确保了,一个volatile变量最新的值总是对跑在其他CPU上的线程可见。其他线程每次会从主存中读取变量的值,而不是比如线程所运行CPU的CPU缓存中。 55 | 56 | colatile变量是非阻塞的。修改一个volatile变量的值是一耳光原子操作。它不能够被中断。不过,在一个volatile变量上的一个 read-update-write 顺序的操作不是原子的。因此,下面的代码如果由多个线程执行可能导致**竞态条件**。 57 | 58 | volatile myVar = 0; 59 | ... 60 | int temp = myVar; 61 | temp++; 62 | myVar = temp; 63 | 64 | 首先,myVar这个volatile变量的值被从主存中读出来赋给了temp变量。然后,temp变量自增1。然后,temp变量的值又赋给了myVar这个volatile变量这意味着它会被写回到主存中。 65 | 66 | 如果两个线程执行这段代码,然后它们都读取myVar的值,加1后,把它的值写回到主存。这样就存在myVar仅被加1,而没有被加2的风险。 67 | 68 | 你可能认为你不会写像上面这样的代码,但是在实践中上面的代码等同于如下的代码: 69 | 70 | myVar++; 71 | 72 | 执行上面的代码时,myVar的值读到一个CPU寄存器或者一个本地CPU缓存中,myVar加1,然后这个CPU寄存器或者CPU缓存中的值被写回到主存中。 73 | 74 | ####单个写线程的情景 75 | 76 | 在一些场景下,你仅有一个线程在向一个共享变量写,多个线程在读这个变量。当仅有一个线程在更新一个变量,不管有多少个线程在读这个变量,都不会发生竞态条件。因此,无论时候当仅有一个线程在写一个共享变量时,你可以把这个变量声明为volatile。 77 | 78 | 当多个线程在一个共享变量上执行一个 read-update-write 的顺序操作时才会发生竞态条件。如果你只有一个线程在执行一个 raed-update-write 的顺序操作,其他线程都在执行读操作,将不会发生竞态条件。 79 | 80 | 下面是一个单个写线程的例子,它没有采取同步手段但任然是并发的。 81 | 82 | public class SingleWriterCounter{ 83 | private volatile long count = 0; 84 | 85 | /** 86 | *Only one thread may ever call this method 87 | *or it will lead to race conditions 88 | */ 89 | public void inc(){ 90 | this.count++; 91 | } 92 | 93 | /** 94 | *Many reading threads may call this method 95 | *@return 96 | */ 97 | public long count(){ 98 | return this.count; 99 | } 100 | } 101 | 102 | 多个线程访问同一个Counter实例,只要仅有一个线程调用inc()方法,这里,我不是说在某一时刻一个线程,我的意思是,仅有相同的,单个的线程被允许去调用inc()>方法。多个线程可以调用count()方法。这样的场景将不会发生任何竞态条件。 103 | 104 | 下图,说明了线程是如何访问count这个volatile变量的。 105 | 106 | ![single_writer](http://tutorials.jenkov.com/images/java-concurrency/non-blocking-algorithms-3.png) 107 | 108 | ####基于volatile变量更高级的数据结构 109 | 110 | 使用多个volatile变量去创建数据结构是可以的,构建出的数据结构中每一个volatile变量仅被一个单个的线程写,被多个线程读。每个volatile变量可能被一个不同的线程写(但仅有一个)。使用像这样的数据结构多个线程可以使用这些volatile变量以一个非阻塞的方法彼此发送信息。 111 | 112 | 下面是一个简单的例子: 113 | 114 | public class DoubleWriterCounter{ 115 | private volatile long countA = 0; 116 | private volatile long countB = 0; 117 | 118 | /** 119 | *Only one (and the same from thereon) thread may ever call this method, 120 | *or it will lead to race conditions. 121 | */ 122 | public void incA(){ 123 | this.countA++; 124 | } 125 | 126 | /** 127 | *Only one (and the same from thereon) thread may ever call this method, 128 | *or it will lead to race conditions. 129 | */ 130 | public void incB(){ 131 | this.countB++; 132 | } 133 | 134 | /** 135 | *Many reading threads may call this method 136 | */ 137 | public long countA(){ 138 | return this.countA; 139 | } 140 | 141 | /** 142 | *Many reading threads may call this method 143 | */ 144 | public long countB(){ 145 | return this.countB; 146 | } 147 | } 148 | 149 | 如你所见,DoubleWriterCoounter现在包含两个volatile变量以及两对自增和读方法。在某一时刻,仅有一个单个的线程可以调用inc(),仅有一个单个的线程可以访问incB()。不过不同的线程可以同时调用incA()incB()countA()countB()可以被多个线程调用。这将不会引发竞态条件。 150 | 151 | DoubleWriterCoounter可以被用来比如线程间通信。countA和countB可以分别用来存储生产的任务数和消费的任务数。下图,展示了两个线程通过类似于上面的一个数据结构进行通信的。 152 | 153 | ![volatile](http://tutorials.jenkov.com/images/java-concurrency/non-blocking-algorithms-4.png) 154 | 155 | 聪明的读者应该已经意识到使用两个SingleWriterCounter可以达到使用DoubleWriterCoounter的效果。如果需要,你甚至可以使用多个线程和SingleWriterCounter实例。 156 | 157 | ####使用CAS的乐观锁 158 | 159 | 如果你确实需要多个线程区写同一个共享变量,volatile变量是不合适的。你将会需要一些类型的排它锁(悲观锁)访问这个变量。下面代码演示了使用Java中的同步块进行排他访问的。 160 | 161 | public class SynchronizedCounter{ 162 | long count = 0; 163 | 164 | public void inc(){ 165 | synchronized(this){ 166 | count++; 167 | } 168 | } 169 | 170 | public long count(){ 171 | synchronized(this){ 172 | return this.count; 173 | } 174 | } 175 | } 176 | 177 | 注意,,inc()count()方法都包含一个同步块。这也是我们像避免的东西——同步块和 wait()-notify 调用等。 178 | 179 | 我们可以使用一种Java的原子变量来代替这两个同步块。在这个例子是AtomicLong。下面是SynchronizedCounter类的AtomicLong实现版本。 180 | 181 | import java.util.concurrent.atomic.AtomicLong; 182 | 183 | public class AtomicLong{ 184 | private AtomicLong count = new AtomicLong(0); 185 | 186 | public void inc(){ 187 | boolean updated = false; 188 | while(!updated){ 189 | long prevCount = this.count.get(); 190 | updated = this.count.compareAndSet(prevCount, prevCount + 1); 191 | } 192 | } 193 | 194 | public long count(){ 195 | return this.count.get(); 196 | } 197 | } 198 | 199 | 这个版本仅仅是上一个版本的线程安全版本。这一版我们感兴趣的是inc()方法的实现。inc()方法中不再含有一个同步块。而是被下面这些代码替代: 200 | 201 | boolean updated = false; 202 | while(!updated){ 203 | long prevCount = this.count.get(); 204 | updated = this.count.compareAndSet(prevCount, prevCount + 1); 205 | } 206 | 207 | 上面这些代码并不是一个原子操作。也就是说,对于两个不同的线程去调用inc()方法,然后执行long prevCount = this.count.get()语句,因此获得了这个计数器的上一个count。但是,上面的代码并没有包含任何的竞态条件。 208 | 209 | 秘密就在于while循环里的第二行代码。compareAndSet()方法调用是一个原子操作。它用一个期望值和AtomicLong 内部的值去比较,如果这两个值相等,就把AtomicLong内部值替换为一个新值。compareAndSet()通常被CPU中的compare-and-swap指令直接支持。因此,不需要去同步,也不需要去挂起线程。 210 | 211 | 假设,这个AtomicLong的内部值是20,。然后,两个线程去读这个值,都尝试调用compareAndSet(20, 20 + 1)。尽管compareAndSet()是一个原子操作,这个方法也会被这两个线程相继执行(某一个时刻只有一个)。 212 | 213 | 第一个线程会使用期望值20(这个计数器的上一个值)与AtomicLong的内部值进行比较。由于两个值是相等的,AtomicLong会更新它的内部值至21(20 + 1 )。变量updated被修改为true,while循环结束。 214 | 215 | 现在,第二个线程调用compareAndSet(20, 20 + 1)。由于AtomicLong的内部值不再是20,这个调用将不会成功。AtomicLong的值不会再被修改为21。变量,updated被修改为false,线程将会再次在while循环外自旋。这段时间,它会读到值21并企图把值更新为22。如果在此期间没有其它线程调用inc()。第二次迭代将会成功更新AtomicLong的内部值到22。 216 | 217 | #####为什么称它为乐观锁 218 | 219 | 上一部分展现的代码被称为**乐观锁**(optimistic locking)。乐观锁区别于传统的锁,有时也被称为**悲观锁**。传统的锁会使用同步块或其他类型的锁阻塞对临界区域的访问。一个同步块或锁可能会导致线程挂起。 220 | 221 | 乐观锁允许所有的线程在不发生阻塞的情况下创建一份共享内存的拷贝。这些线程接下来可能会对它们的拷贝进行修改,并企图把它们修改后的版本写回到共享内存中。如果没有其它线程对共享内存做任何修改, CAS操作就允许线程将它的变化写回到共享内存中去。如果,另一个线程已经修改了共享内存,这个线程将不得不再次获得一个新的拷贝,在新的拷贝上做出修改,并尝试再次把它们写回到共享内存中去。 222 | 223 | 称之为“乐观锁”的原因就是,线程获得它们想修改的数据的拷贝并做出修改,在乐观的假在此期间没有线程对共享内存做出修改的情况下。当这个乐观假设成立时,这个线程仅仅在无锁的情况下完成共享内存的更新。当这个假设不成立时,线程所做的工作就会被丢弃,但任然不使用锁。 224 | 225 | 乐观锁使用于共享内存竞用不是非常高的情况。如果共享内存上的内容非常多,仅仅因为更新共享内存失败,就用浪费大量的CPU周期用在拷贝和修改上。但是,如果砸共享内存上有大量的内容,无论如何,你都要把你的代码设计的产生的争用更低。 226 | 227 | #####乐观锁是非阻塞的 228 | 229 | 我们这里提到的乐观锁机制是非阻塞的。如果一个线程获得了一份共享内存的拷贝,当尝试修改时,发生了阻塞,其它线程去访问这块内存区域不会发生阻塞。 230 | 231 | 对于一个传统的加锁/解锁模式,当一个线程持有一个锁时,其它所有的线程都会一直阻塞直到持有锁的线程再次释放掉这个锁。如果持有锁的这个线程被阻塞在某处,这个锁将很长一段时间不能被释放,甚至可能一直不能被释放。 232 | 233 | ####不可替换的数据结构 234 | 235 | 简单的CAS乐观锁可以用于共享数据结果,这样一来,整个数据结构都可以通过一个单个的CAS操作被替换成为一个新的数据结构。尽管,使用一个修改后的拷贝来替换真个数据结构并不总是可行的。 236 | 237 | 假设,这个共享数据结构是队列。每当线程尝试从向队列中插入或从队列中取出元素时,都必须拷贝这个队列然后在拷贝上做出期望的修改。我们可以通过使用一个AtomicReference来达到同样的目的。拷贝引用,拷贝和修改队列,尝试替换在AtomicReference中的引用让它指向新创建的队列。 238 | 239 | 然而,一个大的数据结构可能会需要大量的内存和CPU周期来复制。这会使你的程序占用大量的内存和浪费大量的时间再拷贝操作上。这将会降低你的程序的性能,特别是这个数据结构的竞用非常高情况下。更进一步说,一个线程花费在拷贝和修改这个数据结构上的时间越长,其它线程在此期间修改这个数据结构的可能性就越大。如你所知,如果另一个线程修改了这个数据结构在它被拷贝后,其它所有的线程都不等不再次执行 拷贝-修改 操作。这将会增大性能影响和内存浪费,甚至更多。 240 | 241 | 接下来的部分将会讲解一种实现非阻塞数据结构的方法,这种数据结构可以被并发修改,而不仅仅是拷贝和修改。 242 | 243 | ####共享预期的修改 244 | 245 | 用来替换拷贝和修改整个数据结构,一个线程可以共享它们对共享数据结构预期的修改。一个线程向对修改某个数据结构的过程变成了下面这样: 246 | - **检查是否另一个线程已经提交了对这个数据结构提交了修改** 247 | - **如果没有其他线程提交了一个预期的修改,创建一个预期的修改,然后向这个数据结构提交预期的修改** 248 | - **执行对共享数据结构的修改** 249 | - **移除对这个预期的修改的引用,向其它线程发送信号,告诉它们这个预期的修改已经被执行** 250 | 251 | 如你所见,第二步可以阻塞其他线程提交一个预期的修改。因此,第二步实际的工作是作为这个数据结构的一个锁。如果一个线程已经成功提交了一个预期的修改,其他线程就不可以再提交一个预期的修改直到第一个预期的修改执行完毕。 252 | 253 | 如果一个线程提交了一个预期的修改,然后做一些其它的工作时发生阻塞,这时候,这个共享数据结构实际上是被锁住的。其它线程可以检测到它们不能够提交一个预期的修改,然后回去做一些其它的事情。很明显,我们需要解决这个问题。 254 | 255 | #####可完成的预期修改 256 | 257 | 为了避免一个已经提交的预期修改可以锁住共享数据结构,一个已经提交的预期修改必须包含足够的信息让其他线程来完成这次修改。因此,如果一个提交了预期修改的线程从未完成这次修改,其他线程可以在它的支持下完成这次修改,保证这个共享数据结构对其他线程可用。 258 | 259 | 下图说明了上面描述的非阻塞算法的蓝图: 260 | 261 | ![non-blocking](http://tutorials.jenkov.com/images/java-concurrency/non-blocking-algorithms-5.png) 262 | 263 | 修改必须被当做一个或多个CAS操作来执行。因此,如果两个线程尝试去完成同一个预期修改,仅有一个线程可以所有的CAS操作。一旦一条CAS操作完成后,再次企图完成这个CAS操作都不会“得逞”。 264 | 265 | ####A-B-A问题 266 | 267 | 上面演示的算法可以称之为**A-B-A问题**。A-B-A问题指的是一个变量被从A修改到了B,然后又被修改回A的一种情景。其他线程对于这种情况却一无所知。 268 | 269 | 如果线程A检查正在进行的数据更新,拷贝,被线程调度器挂起,一个线程B在此期可能可以访问这个共享数据结构。如果线程对这个数据结构执行了全部的更新,移除了它的预期修改,这样看起来,好像线程A自从拷贝了这个数据结构以来没有对它做任何的修改。然而,一个修改确实已经发生了。当线程A继续基于现在已经过期的数据拷贝执行它的更新时,这个数据修改已经被线程B的修改破坏。 270 | 271 | 下图说明了上面提到的A-B-A问题: 272 | 273 | ![a-b-a](http://tutorials.jenkov.com/images/java-concurrency/non-blocking-algorithms-6.png) 274 | 275 | #####A-B-A问题的解决方案 276 | 277 | A-B-A通常的解决方法就是不再仅仅替换指向一个预期修改对象的指针,而是指针结合一个计数器,然后使用一个单个的CAS操作来替换指针 + 计数器。这在支持指针的语言像C和C++中是可行的。因此,尽管当前修改指针被设置回指向 “不是正在进行的修改”(no ongoing modification),指针 + 计数器的计数器部分将会被自增,使修改对其它线程是可见的。 278 | 279 | 在Java中,你不能将一个引用和一个计数器归并在一起形成一个单个的变量。不过Java提供了AtomicStampedReference类,利用这个类可以使用一个CAS操作自动的替换一个引用和一个标记(stamp)。 280 | 281 | ####一个非阻塞算法模板 282 | 283 | 下面的代码意在在如何实现非阻塞算法上一些启发。这个模板基于这篇教程所讲的东西。 284 | 285 | **注意**:在非阻塞算法方面,我并不是一位专家,所以,下面的模板可能错误。不要基于我提供的模板实现自己的非阻塞算法。这个模板意在给你一个关于非阻塞算法大致是什么样子的一个idea。如果,你想实现自己的非阻塞算法,首先学习一些实际的工业水平的非阻塞算法的时间,在实践中学习更多关于非阻塞算法实现的知识。 286 | 287 | import java.util.concurrent.atomic.AtomicBoolean; 288 | import java.util.concurrent.atomic.AtomicStampedReference; 289 | 290 | public class NonblockingTemplate{ 291 | public static class IntendedModification{ 292 | public AtomicBoolean completed = new AtomicBoolean(false); 293 | } 294 | 295 | private AtomicStampedReference ongoinMod = new AtomicStampedReference(null, 0); 296 | //declare the state of the data structure here. 297 | 298 | public void modify(){ 299 | while(!attemptModifyASR()); 300 | } 301 | 302 | 303 | public boolean attemptModifyASR(){ 304 | boolean modified = false; 305 | 306 | IntendedMOdification currentlyOngoingMod = ongoingMod.getReference(); 307 | int stamp = ongoingMod.getStamp(); 308 | 309 | if(currentlyOngoingMod == null){ 310 | //copy data structure - for use 311 | //in intended modification 312 | 313 | //prepare intended modification 314 | IntendedModification newMod = new IntendModification(); 315 | 316 | boolean modSubmitted = ongoingMod.compareAndSet(null, newMod, stamp, stamp + 1); 317 | 318 | if(modSubmitted){ 319 | //complete modification via a series of compare-and-swap operations. 320 | //note: other threads may assist in completing the compare-and-swap 321 | // operations, so some CAS may fail 322 | modified = true; 323 | } 324 | }else{ 325 | //attempt to complete ongoing modification, so the data structure is freed up 326 | //to allow access from this thread. 327 | modified = false; 328 | } 329 | 330 | return modified; 331 | } 332 | } 333 | 334 | ####非阻塞算法是不容易实现的 335 | 336 | 正确的设计和实现非阻塞算法是不容易的。在尝试设计你的非阻塞算法之前,看一看是否已经有人设计了一种非阻塞算法正满足你的需求。 337 | 338 | Java已经提供了一些非阻塞实现(比如 ConcurrentLinkedQueue),相信在Java未来的版本中会带来更多的非阻塞算法的实现。 339 | 340 | 除了Java内置非阻塞数据结构还有很多开源的非阻塞数据结构可以使用。例如,LAMX Disrupter和Cliff Click实现的非阻塞 HashMap。查看我的[Java concurrency references page](http://tutorials.jenkov.com/java-concurrency/references.html)查看更多的资源。 341 | 342 | ####使用非阻塞算法的好处 343 | 344 | 非阻塞算法和阻塞算法相比有几个好处。下面让我们分别看一下: 345 | 346 | #####选择 347 | 348 | 非阻塞算法的第一个好处是,给了线程一个选择当它们请求的动作不能够被执行时做些什么。不再是被阻塞在那,请求线程关于做什么有了一个选择。有时候,一个线程什么也不能做。在这种情况下,它可以选择阻塞或自我等待,像这样把CPU的使用权让给其它的任务。不过至少给了请求线程一个选择的机会。 349 | 350 | 在一个单个的CPU系统可能会挂起一个不能执行请求动作的线程,这样可以让其它线程获得CPU的使用权。不过即使在一个单个的CPU系统阻塞可能导致死锁,线程饥饿等并发问题。 351 | 352 | #####没有死锁 353 | 354 | 非阻塞算法的第二个好处是,一个线程的挂起不能导致其它线程挂起。这也意味着不会发生死锁。两个线程不能互相彼此等待来获得被对方持有的锁。因为线程不会阻塞当它们不能执行它们的请求动作时,它们不能阻塞互相等待。非阻塞算法任然可能产生活锁(live lock),两个线程一直请求一些动作,但一直被告知不能够被执行(因为其他线程的动作)。 355 | 356 | #####没有线程挂起 357 | 358 | 挂起和恢复一个线程的代价是昂贵的。没错,随着时间的推移,操作系统和线程库已经越来越高效,线程挂起和恢复的成本也不断降低。不过,线程的挂起和户对任然需要付出很高的代价。 359 | 360 | 无论什么时候,一个线程阻塞,就会被挂起。因此,引起了线程挂起和恢复过载。由于使用非阻塞算法线程不会被挂起,这种过载就不会发生。这就意味着CPU有可能花更多时间在执行实际的业务逻辑上而不是上下文切换。 361 | 362 | 在一个多个CPU的系统上,阻塞算法会对阻塞算法产生重要的影响。运行在CPUA上的一个线程阻塞等待运行在CPU B上的一个线程。这就降低了程序天生就具备的并行水平。当然,CPU A可以调度其他线程去运行,但是挂起和激活线程(上下文切换)的代价是昂贵的。需要挂起的线程越少越好。 363 | 364 | #####降低线程延迟 365 | 366 | 在这里我们提到的延迟指的是一个请求产生到线程实际的执行它之间的时间。因为在非阻塞算法中线程不会被挂起,它们就不需要付昂贵的,缓慢的线程激活成本。这就意味着当一个请求执行时可以得到更快的响应,减少它们的响应延迟。 367 | 368 | 非阻塞算法通常忙等待直到请求动作可以被执行来降低延迟。当然,在一个非阻塞数据数据结构有着很高的线程争用的系统中,CPU可能在它们忙等待期间停止消耗大量的CPU周期。这一点需要牢牢记住。非阻塞算法可能不是最好的选择如果你的数据结构哦有着很高的线程争用。不过,也常常存在通过重构你的程序来达到更低的线程争用。 369 | -------------------------------------------------------------------------------- /预防死锁.md: -------------------------------------------------------------------------------- 1 | ####预防死锁 2 | #####锁排序 3 | 当多个线程获取同一个锁但是以不同的顺序,就会发生死锁。 4 | 5 | 如果你确保所有的锁一直以相同的顺序被其他线程获取,死锁就不会发生。看下面这例子: 6 | 7 | Thread 1: 8 | 9 | lock A 10 | lock B 11 | 12 | 13 | Thread 2: 14 | 15 | wait for A 16 | lock C (when A locked) 17 | 18 | 19 | Thread 3: 20 | 21 | wait for A 22 | wait for B 23 | wait for C 24 | 25 | 如果一个线程,像线程3,需要几个锁,就必须规定其获得锁的顺序。在它获得序列中靠前的锁之前不能够获得靠后的锁。 26 | 27 | 比如,线程2或者线程2首先要获得锁A,才能够获得锁C。因为,线程A持有锁A,线程2或者线程3首先必须等待直到锁A被释放。然后,在它们能够获得锁B或者C之前,必须成功获得锁A。 28 | 29 | 锁排序是一个简单但很有效的预防死锁的机制。但是,它仅适用于你事先知道所有的锁的情况下。它并不适用于所有的场景。 30 | 31 | #####锁超时 32 | 33 | 另一个预防死锁的机制是在请求锁时设置超时时长,也就说一个线程在设置的超时时长内如果没有获得锁就会放弃。如果一个线程在给定时长内没有成功获取所有必要的锁,它将会回退,释放所有的锁请求,随机等待一段时间,然后重试。随机等待的过程中给了其他线程获取这个锁的一个机会,因此,这也可以让程序在没有锁的情况下继续运行。 34 | 35 | Thread 1 locks A 36 | Thread 2 locks B 37 | 38 | Thread 1 attempts to lock B but is blocked 39 | Thread 2 attempts to lock A but is blocked 40 | 41 | Thread 1's lock attempt on B times out 42 | Thread 1 backs up and releases A as well 43 | Thread 1 waits randomly (e.g. 257 millis) before retrying. 44 | 45 | Thread 2's lock attempt on A times out 46 | Thread 2 backs up and releases B as well 47 | Thread 2 waits randomly (e.g. 43 millis) before retrying. 48 | 49 | 在上面的例子中,线程2将会在线程之前大约200毫秒重试去获得锁,所以,大体上将会获得所有的锁。已经在等待的线程A一直在尝试获取锁A。当线程2完成时,线程1也将会获得所有的锁。 50 | 51 | 我们需要记住一个问题,上面提到的仅仅是因为一个锁超时了,而不是说线程发生;了死锁,这也仅仅是说这个线程获取这个锁花费了多少时间去完成任务。 52 | 53 | 另外,如果线程足够多,尽管设置了超时和重试,也是会有发生死锁的风险。2个线程各自在重试前等待0~500毫秒也许不会发生死锁,但如果10或者20个线程情况就不同了。这种情况发生死锁的概率要比两个线程的情况要高得多。 54 | 55 | 锁超时机制存在的一个问题是在Java中在进入一个同步代码块时设置时长是不可能的。你不得不创建一个自定义的锁相关的类或者使用在Java5中java.util.concurrency包中的并发结构之一。 56 | 57 | #####死锁检测 58 | 59 | 死锁检测是一个重量级的死锁预防机制,主要用于在锁排序和锁超时都不可用的场景中。 60 | 61 | 当一个线程请求一个锁当时请求被禁止时,这个线程可以遍历锁图(lock graph)检查是否发生了死锁。例如,如果一个线程A请求锁7,但是锁7被线程B持有,然后,线程A可以检测线程B是否有请求任何线程A持有的锁。如果有,就会发生一个死锁。 62 | 63 | 当然,一个死锁场景可能比两个对象分别持有对方的锁要复杂的锁。线程A可能等待线程B,线程B等待线程C,线程C等待线程D,线程D等待线程A。为了检测死锁,线程A必须一次测试所有的被线程B请求的锁。从线程B的锁请求线程A到达线程C,然后又到达线程D,从上面的检测中,线程A找到线程A自身持有的一个锁。这样,线程A就会知道发生了死锁。 64 | 65 | 下面是一个被四个线程持有和请求锁的图。类似于这样的一个数据结构可以用来检测死锁。 66 | 67 | ![detect_deadlocks](http://tutorials.jenkov.com/images/java-concurrency/deadlock-detection-graph.png) 68 | 69 | 那么,如果检测到一个死锁,这些线程可以做些什么? 70 | 71 | 一个可能的做法就是释放所有的锁,回退,随机等待一段时间然后重试。这种做法与锁超时机制非常相似除了只有发生死锁时线程才会回退(backup)。而不仅仅是因为锁请求超时。然而,如果大量的线程去请求同一个锁,可能重复的发生死锁,尽管存在回退和等待机制。 72 | 73 | 一个更好的做法就是为这些线程设置优先级,这样一来,就会只有一个或者一些线程在遇到死锁时发生回退。剩下的线程继续请求锁假如没有死锁再发生。如果赋予线程的优先级是固定的,同样的线程总是拥有更高的优先级。为了避免这种情况,我们可以在发生死锁时,随机的为线程设置优先级。 -------------------------------------------------------------------------------- /饥饿和公平.md: -------------------------------------------------------------------------------- 1 | ####饥饿和公平 2 | 如果一个线程因为其他线程抢占了所有的CPU时间,它就被称为“饥饿”(starvation)。解决饥饿的方案被称为“公平”(fairness)——所有的线程公平的获得执行的机会。 3 | 4 | #####引发饥饿 5 | 6 | 在Java中,通常由下面三个主要的原因导致线程饥饿 7 | 8 | - **高优先级的线程占用了所有的CPU时间** 9 | - **线程无限期的等待进入一个同步代码块,因为其他线程一直被允许进入这个同步代码块** 10 | - **线程持续的在一个对象上等待,由于其他线程持续的被唤醒** 11 | 12 | #####在Java中线程间实现公平竞争 13 | 14 | 在Java中,100%实现线程间公平竞争是不可能的,但是我们任然可以通过实现我们的同步结果增加线程间的公平性。 15 | 16 | 首先,让我们学习一个简单的同步代码块: 17 | 18 | public class Synchronizer{ 19 | public synchronized void doSynchronized(){ 20 | // do a lot of work which takes a long time 21 | } 22 | } 23 | 24 | 如果,多个线程调用doSynchronized()方法,其中一些线程将会阻塞等待知道第一个进入这个方法的线程离开这个方法。在等待的多个线程中,接下来谁会获得进入同步代码块的机会是不确定的。 25 | 26 | #####使用锁代替同步代码块 27 | 28 | public class Synchronizer{ 29 | Lock lock = new Lock(); 30 | 31 | public void doSynchronized()throws InterruptedException{ 32 | this.lock.lock(); 33 | //critical section, do a lot of work which takes a long time 34 | this.lock.unlock(); 35 | } 36 | } 37 | 38 | 注意,现在doSynchronized()不再被声明为synchronized。 39 | 40 | Lock类的一个简单的实现: 41 | 42 | public class Lock{ 43 | private boolean isLocked = false; 44 | private Thread lockingThread = null; 45 | 46 | 47 | public synchronized void lock()throws InterruptedException{ 48 | while(isLocked){ 49 | wait(); 50 | } 51 | isLocked = true; 52 | lockingThread = Thread.currentThread(); 53 | } 54 | 55 | 56 | public synchronized void unlock(){ 57 | if(this.lockingThread != Thread.currentThread()){ 58 | throw new IllegalMonitorException("Calling thread has not lock this lock"); 59 | 60 | } 61 | 62 | isLocked = false; 63 | lockingThread = null; 64 | notify(); 65 | } 66 | } 67 | 68 | 如果你看上面Synchronizer类中锁的实现,你会发现,如果多个线程同时调用lock()方法,尝试访问lock()方法的线程将会阻塞。第二,如果这个锁被锁住了,这个线程将被阻塞在lock()方法的while循环里的wait()方法中。 69 | 70 | Remember that a thread calling wait() releases the synchronization lock on the Lock instance, so threads waiting to enter lock() can now do so. The result is that multiple threads can end up having called wait() inside lock(). 71 | 72 | 73 | If you look back at the doSynchronized() method you will notice that the comment between lock() and unlock() states, that the code in between these two calls take a "long" time to execute. Let us further assume that this code takes long time to execute compared to entering the lock() method and calling wait() because the lock is locked. This means that the majority of the time waited to be able to lock the lock and enter the critical section is spent waiting in the wait() call inside the lock() method, not being blocked trying to enter the lock() method. 74 | 75 | 76 | As stated earlier synchronized blocks makes no guarantees about what thread is being granted access if more than one thread is waiting to enter. Nor does wait() make any guarantees about what thread is awakened when notify() is called. So, the current version of the Lock class makes no different guarantees with respect to fairness than synchronized version of doSynchronized(). But we can change that. 77 | 78 | The current version of the Lock class calls its own wait() method. If instead each thread calls wait() on a separate object, so that only one thread has called wait() on each object, the Lock class can decide which of these objects to call notify() on, thereby effectively selecting exactly what thread to awaken. 79 | 80 | #####公平锁 81 | 82 | 如果将上面的Lock类改为一个公平的锁,我们称之为公平锁(FairLock)。你会发现,仅做了一点小小的修改。 83 | 84 | public class FairLock{ 85 | private boolean isLocked = false; 86 | private Thread lockingThread = null; 87 | private List waitingThreads = new ArrayList(); 88 | 89 | 90 | public void lock()throws InterruptedException{ 91 | QueueObject queueObject = new QueueObject(); 92 | boolean isLockedForThisThread = true; 93 | 94 | synchronized(this){ 95 | waitingThreads.add(queueObject); 96 | } 97 | 98 | while(isLockedForThisThread){ 99 | synchronized(this){ 100 | isLockedForThisThread = isLocked || waitingThreads.get(0) != queueObject; 101 | if(!isLockedForThisThread){ 102 | isLocked = true; 103 | waitingThreads.remove(queueObject); 104 | lockingThread = Thread.currentThread(); 105 | return; 106 | } 107 | } 108 | 109 | try{ 110 | queueObject.doWait(); 111 | }catch(InterruptedException e){ 112 | synchronized(this){ 113 | waitingThreads.remove(queueObject); 114 | throw e; 115 | } 116 | } 117 | } 118 | } 119 | 120 | public synchronized void unlock(){ 121 | if(this.lockingThread != Thread.currentThread){ 122 | throw new IllegalMonitorException("Calling thread has not locked this lock"); 123 | } 124 | 125 | isLocked = false; 126 | lockingThread = null; 127 | if(waitingThreads.size() > 0){ 128 | waitingThreads.get(0).doNotify(); 129 | } 130 | } 131 | } 132 | 133 | 134 | public class QueueObject{ 135 | private boolean isNotified = false; 136 | 137 | public synchronized void doWait()throws InterruptedException{ 138 | while(!isNotified){ 139 | this.wait(); 140 | } 141 | 142 | this.isNotified = false; 143 | } 144 | 145 | public synchronized void doNotify(){ 146 | this.isNotified = true; 147 | this.notify(); 148 | } 149 | 150 | public boolean equals(Object o){ 151 | return this == o; 152 | } 153 | } 154 | 155 | 首先,你可能注意到lock()方法不再被声明为synchronized。只有需要同步的代码块才被嵌套在同步代码块。 156 | 157 | FairLock创建了一个新的QueueObject实例,每一个线程调用lock()时将其入队。当线程调用unlock()方法时,将会取出队列顶部的QueueObject实例,然后用它调用doNotify()方法,去唤醒等待在该对象上线程。这种方式,在某一时刻,仅唤醒一个线程,而不是所有的线程。 158 | 159 | 注意,锁的状态任然在同一个同步代码块中被测试和设置,为了避免slipped conditions。 160 | 161 | QueueObject确实是一个信号量。doWait()doNotify()方法在QueueObject内部存放信号。这么做是为了避免由于一个线程在调用queueObject.doWait()之前,另一个线程调用了unlock(),进而调用queueObject.doNotify()而被抢占带来的丢失信号的现象。queueObject.doWait()调用为了避免nested monitor lockout而被放在了同步代码块的外面,因此,其他线程在没有线程运行在locvk()方法内的synchronized(this)代码块中时可以真正的调用unlock。 162 | 163 | 最后,注意queueObject.doWait()如何在一个try-catch块中被调用的。 164 | 165 | #####性能比较 166 | 167 | 如果你比较Lock类和FairLock类,你会注意到在fairLock类中的lock()unlock方法中多一些代码。这些额外的代码将会导致FairLock变成一个比Lock稳定一些的同步机制。这种影响的大小取决于你的程序执行被FairLock监视的临界区里的代码花费的时间。 The longer this takes to execute, the less significant the added overhead of the synchronizer is. It does of course also depend on how often this code is called. --------------------------------------------------------------------------------