├── .gitignore ├── 01并发编程线程基础.md ├── 02并发编程的其他基础知识.md ├── 03Java并发包中的ThreadLocalRandom类原理剖析.md ├── 04Java并发包中原子操作类原理剖析.md ├── 05Java并发包中并发List源码剖析.md ├── 06Java并发包中锁原理剖析.md ├── 07Java并发包中并发队列原理剖析.md ├── 08Java并发包中线程池ThreadPoolExecutor原理探究.md ├── 09Java并发包中ScheduledThreadPoolExecutor原理探究.md ├── 10Java并发包中线程同步器原理剖析.md ├── 11并发编程实践.md ├── README.md └── images ├── 01.png ├── 02.png ├── 03.png ├── 04.png ├── 05.png ├── 06.png ├── 07.png ├── 08.png ├── 09.png ├── 10.png ├── 11.png ├── 12.png ├── 13.png ├── 14.png ├── 15.png └── 16.png /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | /Tests -------------------------------------------------------------------------------- /01并发编程线程基础.md: -------------------------------------------------------------------------------- 1 | # 第1章:并发编程线程基础 2 | 3 | ## 目录 4 | 5 | - [wait()](#wait) 6 | - [notify()和notifyAll()](#notify和notifyall) 7 | - [join()](#join) 8 | - [sleep()](#sleep) 9 | - [yield()](#yield) 10 | - [线程中断](#线程中断) 11 | - [void interrupt()](#void-interrupt) 12 | - [boolean isInterrupted()](#boolean-isinterrupted) 13 | - [boolean interrupted()](#boolean-interrupted) 14 | - [示例](#示例) 15 | - [守护进程与用户进程](#守护进程与用户进程) 16 | - [示例](#示例-1) 17 | - [更多](#更多) 18 | 19 | ## wait() 20 | 21 | 如果调用wait()方法的线程没有实现获取该对象的监视器锁,则调用wait()方法时线程会抛出IllegalMonitorStateException异常。 22 | 23 | 一个线程获取一个共享变量的监视器锁的方法 24 | - 执行synchronized同步代码块时,使用该共享变量作为参数: 25 | ```java 26 | synchronized(共享变量) { 27 | //do something 28 | } 29 | ``` 30 | - 调用该共享变量的方法,并且该方法使用了synchronized修饰: 31 | ```java 32 | synchronized void add(int a, int b) { 33 | //do something 34 | } 35 | ``` 36 | 37 | > 注意:一个线程可以挂起状态变成运行状态(也就是被唤醒),即使该线程没有被其他线程调用notify()、notifyAll()方法进行通知,或者被中断,或者等待超时,这就是所谓的**虚假唤醒**。 38 | 39 | 虚假唤醒在实践中很少发生,但要防患于未然,如下: 40 | ```java 41 | synchronized(obj) { 42 | while(条件不满足) { 43 | obj.wait(); 44 | } 45 | } 46 | ``` 47 | ## notify()和notifyAll() 48 | 49 | notify()会唤醒被阻塞到该共享变量上的一个线程,notifyAll()会唤醒所有在该共享变量上由于调用wait系列方法而被挂起的线程。 50 | 51 | ## join() 52 | 53 | ```java 54 | ... 55 | public static void main(String[] args){ 56 | ... 57 | thread1.join(); 58 | thread2.join(); 59 | System.out.println("all child thread over!"); 60 | } 61 | ``` 62 | 主线程首先会在调用thread1.join()后被阻塞,等待thread1执行完毕后,thread2开始阻塞,以此类推,最终会等所有子线程都结束后main函数才会返回。 63 | 64 | ## sleep() 65 | 66 | sleep()会使线程暂时让出指定时间的执行权,也就是在这期间不参与CPU的调度,但不会释放锁。 67 | 68 | ## yield() 69 | 70 | 线程调用yield()方法时,实际上是暗示线程调度器当前线程请求让出自己的CPU使用(告诉线程调度器可以进行下一轮的线程调度),但线程调度器可以无条件忽略这个暗示。 71 | 72 | ## 线程中断 73 | 74 | ### void interrupt() 75 | 设置线程的中断标志为true并立即返回,但线程实际上并没有被中断而会继续向下执行;如果线程因为调用了wait系列函数、join方法或者sleep方法而被阻塞挂起,其他线程调用该线程的interrupt()方法会使该线程抛出InterruptedException异常而返回。 76 | 77 | ### boolean isInterrupted() 78 | 检测当前线程是否被中断,是则返回true,否则返回false。 79 | 80 | ### boolean interrupted() 81 | 检测当前线程是否被中断,返回值同上,但如果发现当前线程被中断,会清除中断标志;该方法是**static**方法,内部是获取当前调用线程的中断标志而不是调用interrupted()方法的实例对象的中断标志。 82 | 83 | ### 示例 84 | ```java 85 | public static void main(String[] args){ 86 | 87 | Thread threadOne = new Thread(new Runnable(){ 88 | 89 | @Override 90 | public void run() { 91 | for(;;) { 92 | 93 | } 94 | } 95 | }); 96 | 97 | //启动线程 98 | threadOne.start(); 99 | 100 | //设置中断标志 101 | threadOne.interrupt(); 102 | 103 | //获取中断标志 104 | System.out.println("isInterrupted:" + threadOne.isInterrupted() ); 105 | 106 | //获取中断标志并重置 107 | System.out.println("isInterrupted:" + threadOne.interrupted() ); 108 | 109 | //获取中断标志并重置 110 | System.out.println("isInterrupted:" + Thread.interrupted() ); 111 | 112 | //获取中断标志 113 | System.out.println("isInterrupted:" + threadOne.isInterrupted() ); 114 | 115 | try{ 116 | threadOne.join(); 117 | }catch(InterruptedException e) { 118 | System.out.println("interrupted!!!"); 119 | } 120 | 121 | System.out.println("main thread is over"); 122 | } 123 | ``` 124 | 输出为 125 | ```java 126 | isInterrupted:true 127 | isInterrupted:false 128 | isInterrupted:false 129 | isInterrupted:true 130 | ``` 131 | 解析 132 | threadOne.interrupted()一句,由于interrupted()为static方法,相当于执行Thread.interrupted()。 133 | 134 | ## 守护进程与用户进程 135 | 136 | 当最后一个用户进程结束时,JVM会正常退出,而不管当前是否有守护进程。 137 | 138 | ### 示例 139 | 140 | ```java 141 | public static void main(String[] args){ 142 | 143 | Thread t = new Thread(new Runnable(){ 144 | 145 | @Override 146 | public void run() { 147 | for(;;) {} 148 | } 149 | }); 150 | 151 | // t.setDaemon(true); 152 | t.start(); 153 | System.out.println("main is over"); 154 | } 155 | ``` 156 | 运行后发现虽然输出了"main is over",但ide运行状态红框仍亮着,说明JVM进程未结束,如图: 157 | 158 | ![image01](/images/01.png) 159 | 160 | 打开注释后程序则快速退出。 161 | 162 | ## 更多 163 | 164 | 相关笔记:[《Java并发编程之美》阅读笔记](/README.md) 165 | -------------------------------------------------------------------------------- /02并发编程的其他基础知识.md: -------------------------------------------------------------------------------- 1 | # 第2章 并发编程的其他基础知识 2 | 3 | ## 目录 4 | 5 | - [并行与并发区别](#并行与并发区别) 6 | - [Java中的线程安全问题](#java中的线程安全问题) 7 | - [Java中共享变量的内存可见性问题](#java中共享变量的内存可见性问题) 8 | - [synchronized关键字](#synchronized关键字) 9 | - [示例](#示例) 10 | - [volatile关键字](#volatile关键字) 11 | - [示例](#示例-1) 12 | - [volatile不保证原子性示例](#volatile不保证原子性示例) 13 | - [使用场景](#使用场景) 14 | - [Java中的CAS操作](#java中的cas操作) 15 | - [示例](#示例-2) 16 | - [ABA问题](#aba问题) 17 | - [问题描述](#问题描述) 18 | - [解决方案](#解决方案) 19 | - [Unsafe类](#unsafe类) 20 | - [Java指令重排序](#java指令重排序) 21 | - [锁](#锁) 22 | - [乐观锁与悲观锁](#乐观锁与悲观锁) 23 | - [公平锁与非公平锁](#公平锁与非公平锁) 24 | - [独占锁与共享锁](#独占锁与共享锁) 25 | - [可重入锁](#可重入锁) 26 | - [自旋锁](#自旋锁) 27 | - [更多](#更多) 28 | 29 | ## 并行与并发区别 30 | 31 | 并发指同一**时间段**多个任务同时都在进行,并且都没有执行结束,而并行是说在**单位时间**内多个任务在同时运行。 32 | 33 | 并发任务强调在一个时间段内同时进行,而一个时间段有多个单位i时间构成,所以说并发的多个任务在单位时间内不一定同时在执行。 34 | 35 | 一个CPU同时只能执行一个任务,所以单CPU时代多个任务都是并发执行的。 36 | 37 | > 注:在多线程时间中,线程的个数往往多于CPU个数,所以即使存在并行任务,一般还是称为多线程并发编程而非多线程并行编程。 38 | 39 | ## Java中的线程安全问题 40 | 41 | 示例:计数器问题 42 | 43 | | | t1 | t2 | t3 | t4 | 44 | | ----- | --------------------------- | --------------------------- | --------------------- | ---------- | 45 | | 线程A | 从主内存读取count值到本线程 | 递增本地线程count的值 | 写回主内存 | | 46 | | 线程B | | 从主内存读取count值到本线程 | 递增本地线程count的值 | 写回主内存 | 47 | 48 | 假设count初始值为0,线程A在t1和t2时间读取了主内存中的count并在本地将其递增为1,t2时线程B从主内存中读取了count的值0并于t3时将其递增为1,t3时线程A将count的新值1更新到主内存,t4时线程B进行了同样的操作,最终主内存中count的值为1而非我们想要的2。 49 | 50 | ## Java中共享变量的内存可见性问题 51 | 52 | ![](/images/02.png) 53 | 54 | 55 | 如图是一个双核CPU模型,每个核都有自己的一级缓存,有些架构里还有一个所有CPU共享的二级缓存。 56 | 57 | 现假设线程A和线程B同时处理一个共享变量,由于Cache的存在,将会出现**内存不可见**问题,原因如下: 58 | 59 | - 线程A先获取共享变量X的值(假设X=0),由于L1和L2缓存中都没有X的值,线程A会直接从主存中去取X的值并将其缓存到L1和L2中。然后A将X的值递增为1,将其写入缓存L1和L2,并刷新到主存。 60 | - 线程B也要获取X的值,由于Core2中1级缓存没有X的值,B会从L2缓存取到X的值1。然后B修改X为2,将其更新至L1、L2和主存。 61 | - 线程A又需要获取X的值,发现L1中已经有了,但此时A获得的X=1,与主存中X=2不同了! 62 | 63 | ## synchronized关键字 64 | 65 | synchronized关键字是一种原子性内置锁,线程进入synchronized代码块前会自动获取监视器锁,这时其他线程再访问该同步代码块是会被阻塞挂起。 66 | 67 | 前面的共享变量内存可见性问题主要是线程的工作内存导致的,而synchronized的内存语义可以解决此问题。 68 | 69 | synchronized的内存语义: 70 | - 进入synchronized块:把synchronized块中使用到的变量从线程的工作内存中清除,这样当synchronized块中要使用该变量时,就会从主存中去取。 71 | - 离开synchronized块:把synchronized块中对共享变量的修改刷新到主内存。 72 | 73 | ### 示例 74 | 75 | ```java 76 | public class ThreadSafeInteger { 77 | 78 | private int value; 79 | 80 | public synchronized int get() { 81 | return value; 82 | } 83 | 84 | public synchronized void set(int value) { 85 | this.value = value; 86 | } 87 | } 88 | ``` 89 | 90 | > 注1:get()方法虽然只是读操作,但仍要加上synchronized来实现value的内存可见性。 91 | > 92 | > 注2:使用synchronized虽然解决了共享变量value的内存可见性问题,但由于synchronized是独占锁,同时只能有一个线程调用get()方法,其他调用线程则会被阻塞,同时存在线程切换、调度的开销,效率并不高。 93 | 94 | ## volatile关键字 95 | 96 | 当一个变量声明为volatile时,线程在写入变量时就不会把值缓存,而是直接把值刷新到主存中;当其他线程读取该变量时,会从主存中重新获得最新值,而不是使用当前工作内存中的值。 97 | 98 | ### 示例 99 | 100 | ```java 101 | public class ThreadSafeInteger { 102 | 103 | private volatile int value; 104 | 105 | public int get() { 106 | return value; 107 | } 108 | 109 | public void set(int value) { 110 | this.value = value; 111 | } 112 | } 113 | ``` 114 | 115 | > 注:volatile虽然保证了可见性,但并不保证操作的原子性。 116 | 117 | ### volatile不保证原子性示例 118 | 119 | ```java 120 | public class Test { 121 | 122 | private static volatile long _longVal = 0; 123 | 124 | public static void main(String[] args) { 125 | 126 | Thread t1 = new Thread(new LoopVolatile1()); 127 | t1.start(); 128 | 129 | Thread t2 = new Thread(new LoopVolatile2()); 130 | t2.start(); 131 | 132 | try{ 133 | t1.join(); 134 | t2.join(); 135 | }catch(Exception e){ 136 | e.printStackTrace(); 137 | } 138 | 139 | 140 | System.out.println("final val is: " + _longVal); 141 | } 142 | 143 | private static class LoopVolatile1 implements Runnable { 144 | public void run() { 145 | long val = 0; 146 | while (val < 100000) { 147 | _longVal++; 148 | val++; 149 | } 150 | } 151 | } 152 | 153 | 154 | private static class LoopVolatile2 implements Runnable { 155 | public void run() { 156 | long val = 0; 157 | while (val < 100000) { 158 | _longVal++; 159 | val++; 160 | } 161 | } 162 | } 163 | } 164 | ``` 165 | 166 | 运行上述代码,发现每次输出不同。 167 | 168 | ### 使用场景 169 | 170 | - 写入变量值不依赖当前值。如果依赖当前值,将是获取-计算-写入三步,这三步并非原子性,volatile也不保证原子性。 171 | - 读写变量值时没有加锁。因为加锁本身已经保证了内存可见性,再使用volatile就是画蛇添足。 172 | 173 | ## Java中的CAS操作 174 | 175 | CAS即Compare And Swap,JDK中Unsafe类提供了一系列的compareAndSwap*方法。 176 | 177 | ### 示例 178 | 下面以compareAndSwapLong(Object obj, long valueOffset, long expect, long update)方法为例进行介绍 179 | 180 | boolean compareAndSwapLong(Object obj, long valueOffset, long expect, long update)方法:如果obj对象中内存偏移值为valueOffset的变量值为expect,则使用新的值update替换之。这是处理器提供的一个原子性指令。 181 | 182 | ### ABA问题 183 | 184 | #### 问题描述 185 | 186 | 假如线程A要去通过CAS修改变量X,要先判断X当前值是否改变过,如果“未改变”,则更新之。但这并不能保证X没有被改变过:假如A修改X前,线程B修改了X的值,然后又修改回来,A的CAS操作仍能成功,但X实际上发生过改变。 187 | 188 | #### 解决方案 189 | 190 | JDK中的AtomicStampedReference类给每个变量都配备了一个时间戳,从而避免了ABA问题的产生。 191 | 192 | ## Unsafe类 193 | 194 | JDK的rt.jar 包中的UnSafe类提供了硬件级别的原子性操作,Unsafe类中的方法都是native方法,使用JNI的方式访问本地C++实现库。 195 | 196 | Unsafe类可以直接操作内存,这是不安全的,如果是普通的调用的话,它会抛出一个SecurityException异常;只有由主类加载器加载的类才能调用这个方法。 197 | 198 | 见下: 199 | 200 | ```java 201 | public static Unsafe getUnsafe() { 202 | Class localClass = Reflection.getCallerClass(); 203 | if(!VM.isSystemDomainLoader(localClass.getClassLoader())) { 204 | throw new SecurityException("Unsafe"); 205 | } else { 206 | return theUnsafe; 207 | } 208 | } 209 | ``` 210 | 211 | 但通过万能的反射,还是可以使用到Unsafe类的: 212 | ```java 213 | Field f = Unsafe.class.getDeclaredField("theUnsafe"); 214 | f.setAccessible(true); 215 | Unsafe unsafe = (Unsafe) f.get(null); 216 | ``` 217 | 218 | ## Java指令重排序 219 | 220 | Java内存模型允许编译器和处理器对**不存在数据依赖性的指令**进行重排序以提高性能。 221 | 222 | 如下: 223 | ```java 224 | int a = 1; //(1) 225 | int b = 2; //(2) 226 | int c = a + b; //(3) 227 | ``` 228 | 如果有必要,JVM完全可以将(2)放在(1)前执行,但这并不会影响最终结果。 229 | 230 | 单线程下这样做是ok的,但多线程下就会出现问题: 231 | ```java 232 | private static int num = 0; 233 | private static boolean ready = false; 234 | 235 | public static void main(String[] args){ 236 | 237 | Thread t1 = new Thread(new ReadTask()); 238 | Thread t2 = new Thread(new WriteTask()); 239 | 240 | t1.start(); 241 | t2.start(); 242 | 243 | try{ 244 | Thread.sleep(100); 245 | }catch(InterruptedException e) { 246 | e.printStackTrace(); 247 | } 248 | 249 | System.out.println("main exit!"); 250 | } 251 | 252 | 253 | 254 | public static class ReadTask implements Runnable { 255 | 256 | @Override 257 | public void run() { 258 | while(true) { 259 | if(ready) { 260 | System.out.println(num); 261 | return; 262 | } 263 | } 264 | } 265 | } 266 | 267 | public static class WriteTask implements Runnable { 268 | 269 | @Override 270 | public void run() { 271 | num = 1; //(1) 272 | ready = true; //(2) 273 | } 274 | } 275 | ``` 276 | 277 | 理论上这段代码并不一定输出1,因为进行指令重排序后,WriteTask中的(2)语句有可能会先于(1)执行,导致输出为0。 278 | 279 | ## 锁 280 | 281 | ### 乐观锁与悲观锁 282 | 283 | - 乐观锁:认为数据在一般情况下不会造成冲突,所以在访问记录前不会加排他锁,而是在进行数据提交更新时才会对数据冲突进行检测并返回一定状态。用户需根据状态判断是否更新成功并进行相应操作。 284 | - 悲观锁:对数据被外界修改持保守态度,认为数据很容易被其他线程更改,所以在数据被处理前进行加锁。 285 | 286 | ### 公平锁与非公平锁 287 | 288 | - 公平锁:线程获取锁的顺序是按照请求锁的时间顺序决定的,先请求先得。 289 | - 非公平锁:不保证先请求的线程先获得锁。 290 | 291 | > 注:在没有公平性需求的前提下尽量使用非公平锁,因为公平锁会带来额外性能开销。 292 | 293 | ### 独占锁与共享锁 294 | 295 | - 独占锁:任何时候都只有一个线程能得到锁,如ReentrantLock。 296 | - 共享锁:可以多个线程持有,如ReadWriteLock读写锁。 297 | 298 | ### 可重入锁 299 | 300 | 一个线程要再次获取自己已经获取了的锁时如果不被阻塞,则该锁为可重入锁。 301 | 302 | ```java 303 | public class Test{ 304 | 305 | public synchronized void f1() { 306 | System.out.println("f1..."); 307 | } 308 | 309 | public synchronized void f2() { 310 | System.out.println("f2..."); 311 | f1(); 312 | } 313 | } 314 | ``` 315 | 316 | 上述代码中,调用f2()并不会造成阻塞,说明synchronized内部锁是可重入锁。 317 | 318 | ### 自旋锁 319 | 320 | 当前线程获取锁时,如果发现锁已经被其他线程占有,并不会马上阻塞自己,而是在不放弃CPU使用权的情况下,多次尝试获取,到一定次数后才放弃。目的是使用CPU时间换取线程阻塞与调度的开销。 321 | 322 | ## 更多 323 | 324 | 相关笔记:[《Java并发编程之美》阅读笔记](/README.md) 325 | -------------------------------------------------------------------------------- /03Java并发包中的ThreadLocalRandom类原理剖析.md: -------------------------------------------------------------------------------- 1 | # 第3章 Java并发包中的ThreadLocalRandom类原理剖析 2 | 3 | ## 目录 4 | 5 | - [Random类及其局限性](#random类及其局限性) 6 | - [示例](#示例) 7 | - [分析](#分析) 8 | - [ThreadLocalRandom](#threadlocalrandom) 9 | - [示例](#示例-1) 10 | - [原理](#原理) 11 | - [源码分析](#源码分析) 12 | - [更多](#更多) 13 | 14 | ## Random类及其局限性 15 | 16 | 一般情况下,我们都会使用java.util.Random来生成随机数(Math.random()也是使用Random实例生成随机数)。 17 | 18 | ### 示例 19 | 20 | ```java 21 | public static void main(String[] args) { 22 | 23 | Random random = new Random(); 24 | for (int i = 0; i < 10; i++) { 25 | System.out.println(random.nextInt(10)); 26 | } 27 | 28 | } 29 | ``` 30 | 31 | ### 分析 32 | 33 | 下面以nextInt(int bound) 方法为例来分析Random的源码 34 | 35 | ```java 36 | public int nextInt(int bound) { 37 | //边界检测 38 | if (bound <= 0) 39 | throw new IllegalArgumentException(BadBound); 40 | 41 | //获取下一随机数 42 | int r = next(31); 43 | 44 | //(*)此处以特定算法根据r计算出最终结果 45 | ... 46 | 47 | return r; 48 | } 49 | 50 | protected int next(int bits) { 51 | long oldseed, nextseed; 52 | AtomicLong seed = this.seed; 53 | //CAS操作更新seed 54 | do { 55 | oldseed = seed.get(); 56 | //根据老的种子计算新的种子 57 | nextseed = (oldseed * multiplier + addend) & mask; 58 | } while (!seed.compareAndSet(oldseed, nextseed)); 59 | return (int)(nextseed >>> (48 - bits)); 60 | } 61 | ``` 62 | 63 | 由此可见,生成新的随机数需要两步: 64 | 65 | - 根据老的种子生成新的种子 66 | - 由新的种子计算出新的随机数 67 | 68 | 单线程下每次调用nextInt都会根据老的种子计算出新的种子,可以保证随机性。 69 | 70 | 但多线程下,不同线程可能拿着同一个老的种子去计算新种子,**如果**next方法因此返回相同的值的话,由于(*)处的算法是固定的,这会导致不同线程生成相同的随机数,这并非我们想要的。所以next方法使用CAS操作保证每次只有一个线程可以更新老的种子,失败的线程则重新获取,这样就解决了上述问题。 71 | 72 | 但这样处理仍有一个缺陷:当多个线程同时计算随机数来计算新的种子时,多个线程会竞争同一个原子变量的更新操作,由于该操作为CAS操作,同时只有一个线程会成功,这样会造成大量的自旋重试,导致并发性能降低。而ThreadLocalRandom可以完美解决此问题。 73 | 74 | ## ThreadLocalRandom 75 | 76 | ### 示例 77 | 78 | ```java 79 | public static void main(String[] args) { 80 | 81 | Random random = ThreadLocalRandom.current(); 82 | for (int i = 0; i < 10; i++) { 83 | System.out.println(random.nextInt(10)); 84 | } 85 | 86 | } 87 | ``` 88 | 89 | ### 原理 90 | 91 | Random的缺点在于多个线程会使用同一个原子性变量,从而导致对原子变量的竞争;而ThreadLocalRandom保证每个线程都维护一个种子变量,每个线程根据自己老的种子生成新的种子,避免了竞争问题,大大提高了并发性能。 92 | 93 | ### 源码分析 94 | 95 | ```java 96 | static final ThreadLocalRandom instance = new ThreadLocalRandom(); 97 | 98 | public static ThreadLocalRandom current() { 99 | //检测是否初始化过 100 | //PROBE为Thread类中threadLocalRandomProb偏移 101 | if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0) 102 | localInit(); 103 | return instance; 104 | } 105 | 106 | static final void localInit() { 107 | int p = probeGenerator.addAndGet(PROBE_INCREMENT); 108 | int probe = (p == 0) ? 1 : p; // skip 0 109 | long seed = mix64(seeder.getAndAdd(SEEDER_INCREMENT)); 110 | Thread t = Thread.currentThread(); 111 | //SEED为Thread类中threadLocalRandomSeed内存偏移 112 | UNSAFE.putLong(t, SEED, seed); 113 | UNSAFE.putInt(t, PROBE, probe); 114 | } 115 | ``` 116 | 如果线程中第一次调用current()方法,则调用localInit()进行初始化设置当前线程中的threadLocalRandomProb和threadLocalRandomSeed变量。 117 | 118 | 下面来看int nextInt(int bound)方法 119 | 120 | ```java 121 | public int nextInt(int bound) { 122 | if (bound <= 0) 123 | throw new IllegalArgumentException(BadBound); 124 | //根据当前Thread中的threadLocalRandomSeed变量生成新种子 125 | int r = mix32(nextSeed()); 126 | int m = bound - 1; 127 | if ((bound & m) == 0) // power of two 128 | r &= m; 129 | else { // reject over-represented candidates 130 | for (int u = r >>> 1; 131 | u + m - (r = u % bound) < 0; 132 | u = mix32(nextSeed()) >>> 1) 133 | ; 134 | } 135 | return r; 136 | } 137 | 138 | final long nextSeed() { 139 | Thread t; long r; 140 | //生成并存入新种子 141 | UNSAFE.putLong(t = Thread.currentThread(), SEED, 142 | r = UNSAFE.getLong(t, SEED) + GAMMA); 143 | return r; 144 | } 145 | ``` 146 | 如上,首先调用nextSeed()根据当前Thread中的threadLocalRandomSeed变量生成并存入新种子,然后经过特定算法得出了nextInt的值。 147 | 148 | ## 更多 149 | 150 | 相关笔记:[《Java并发编程之美》阅读笔记](/README.md) 151 | -------------------------------------------------------------------------------- /04Java并发包中原子操作类原理剖析.md: -------------------------------------------------------------------------------- 1 | # 第4章 Java并发包中原子操作类原理剖析 2 | 3 | ## 目录 4 | 5 | - [原子变量操作类](#原子变量操作类) 6 | - [递增和递减操作代码](#递增和递减操作代码) 7 | - [compareAndSet方法](#compareandset方法) 8 | - [AtomicLong使用示例](#atomiclong使用示例) 9 | - [JDK8中新增的原子操作类LongAdder](#jdk8中新增的原子操作类longadder) 10 | - [原理](#原理) 11 | - [源码分析](#源码分析) 12 | - [LongAccumulator](#longaccumulator) 13 | - [更多](#更多) 14 | 15 | ## 原子变量操作类 16 | 17 | JUC包中有AtomicInteger、AtomicLong和AtomicBoolean等原子性操作类,它们原理类似,下面以AtomicLong为例进行讲解。 18 | 19 | ### 递增和递减操作代码 20 | 21 | ```java 22 | public final long getAndIncrement() { 23 | return unsafe.getAndAddLong(this, valueOffset, 1L); 24 | } 25 | 26 | public final long getAndDecrement() { 27 | return unsafe.getAndAddLong(this, valueOffset, -1L); 28 | } 29 | 30 | public final long incrementAndGet() { 31 | return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L; 32 | } 33 | 34 | public final long decrementAndGet() { 35 | return unsafe.getAndAddLong(this, valueOffset, -1L) - 1L; 36 | } 37 | ``` 38 | 上述代码中,valueOffset为AtomicLong在static语句块中进行初始化时通过Unsafe类获得的本类中value属性的内存偏移值。 39 | 40 | 可以看到,上述四个方法都是基于Unsafe类中的getAndAddLong方法实现的。 41 | 42 | getAndAddLong源码如下 43 | 44 | ```java 45 | public final long getAndAddLong(Object var1, long var2, long var4) { 46 | long var6; 47 | //CAS操作设置var1对象偏移为var2处的值增加var4 48 | do { 49 | var6 = this.getLongVolatile(var1, var2); 50 | } while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4)); 51 | 52 | return var6; 53 | } 54 | ``` 55 | 56 | ### compareAndSet方法 57 | 58 | ```java 59 | public final boolean compareAndSet(long expect, long update) { 60 | return unsafe.compareAndSwapLong(this, valueOffset, expect, update); 61 | } 62 | ``` 63 | 64 | 可见,内部还是调用了Unsafe类中的CAS方法。 65 | 66 | ### AtomicLong使用示例 67 | 68 | ```java 69 | public class AtomicLongDemo { 70 | private static AtomicLong al = new AtomicLong(0); 71 | 72 | public static long addNext() { 73 | return al.getAndIncrement(); 74 | } 75 | 76 | public static void main(String[] args) { 77 | for (int i = 0; i < 100; i++) { 78 | new Thread() { 79 | @Override 80 | public void run() { 81 | AtomicLongDemo.addNext(); 82 | } 83 | }.start(); 84 | } 85 | 86 | // 等待线程运行完 87 | try { 88 | TimeUnit.SECONDS.sleep(1); 89 | } catch (InterruptedException e) { 90 | e.printStackTrace(); 91 | } 92 | 93 | System.out.println("final result is " + AtomicLongDemo.addNext()); 94 | } 95 | } 96 | ``` 97 | AtomicLong使用CAS非阻塞算法,性能比使用synchronized等的阻塞算法实现同步好很多。但在高并发下,大量线程会同时去竞争更新同一个原子变量,由于同时只有一个线程的CAS会成功,会造成大量的自旋尝试,十分浪费CPU资源。因此,JDK8中新增了原子操作类LongAdder。 98 | 99 | ## JDK8中新增的原子操作类LongAdder 100 | 101 | 由上可知,AtomicLong的性能瓶颈是多个线程同时去竞争一个变量的更新权导致的。而LongAdder通过将一个变量分解成多个变量,让同样多的线程去竞争多个资源解决了此问题。 102 | 103 | ### 原理 104 | 105 | ![](images/03.png) 106 | 107 | 如图,LongAdder内部维护了多个Cell,每个Cell内部有一个初始值为0的long类型变量,这样,在同等并发下,对单个变量的争夺会变少。此外,多个线程争夺同一个变量失败时,会到另一个Cell上去尝试,增加了重试成功的可能性。当LongAdder要获取当前值时,将所有Cell的值于base相加返回即可。 108 | 109 | LongAdder维护了一个初始值为null的Cell数组和一个基值变量base。当一开始Cell数组为空且并发线程较少时,仅使用base进行累加。当并发增大时,会动态地增加Cell数组的容量。 110 | 111 | Cell类中使用了@sun.misc.Contented注解进行了字节填充,解决了由于连续分布于数组中且被多个线程操作可能造成的**伪共享**问题(关于伪共享,可查看[《伪共享(false sharing),并发编程无声的性能杀手》](https://www.cnblogs.com/cyfonly/p/5800758.html)这篇文章)。 112 | 113 | ### 源码分析 114 | 115 | 先看LongAdder的定义 116 | 117 | ```java 118 | public class LongAdder extends Striped64 implements Serializable 119 | ``` 120 | 121 | Striped64类中有如下三个变量: 122 | ```java 123 | 124 | transient volatile Cell[] cells; 125 | 126 | transient volatile long base; 127 | 128 | transient volatile int cellsBusy; 129 | ``` 130 | 131 | cellsBusy用于实现自旋锁,状态值只有0和1,当创建Cell元素、扩容Cell数组或初始化Cell数组时,使用CAS操作该变量来保证同时只有一个变量可以进行其中之一的操作。 132 | 133 | 下面看Cell的定义: 134 | 135 | ```java 136 | @sun.misc.Contended static final class Cell { 137 | volatile long value; 138 | Cell(long x) { value = x; } 139 | final boolean cas(long cmp, long val) { 140 | return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val); 141 | } 142 | 143 | // Unsafe mechanics 144 | private static final sun.misc.Unsafe UNSAFE; 145 | private static final long valueOffset; 146 | static { 147 | try { 148 | UNSAFE = sun.misc.Unsafe.getUnsafe(); 149 | Class ak = Cell.class; 150 | valueOffset = UNSAFE.objectFieldOffset 151 | (ak.getDeclaredField("value")); 152 | } catch (Exception e) { 153 | throw new Error(e); 154 | } 155 | } 156 | } 157 | ``` 158 | 159 | 将value声明伪volatile确保了内存可见性,CAS操作保证了value值的原子性,@sun.misc.Contented注解的使用解决了伪共享问题。 160 | 161 | 下面来看LongAdder中的几个方法: 162 | 163 | - long Sum() 164 | 165 | ```java 166 | public long sum() { 167 | Cell[] as = cells; Cell a; 168 | long sum = base; 169 | if (as != null) { 170 | for (int i = 0; i < as.length; ++i) { 171 | if ((a = as[i]) != null) 172 | sum += a.value; 173 | } 174 | } 175 | return sum; 176 | } 177 | ``` 178 | sum的结果并非一个精确值,因为计算总和时并没有对Cell数组加锁,累加过程中Cell的值可能被更改。 179 | 180 | - void reset() 181 | 182 | ```java 183 | public void reset() { 184 | Cell[] as = cells; Cell a; 185 | base = 0L; 186 | if (as != null) { 187 | for (int i = 0; i < as.length; ++i) { 188 | if ((a = as[i]) != null) 189 | a.value = 0L; 190 | } 191 | } 192 | } 193 | ``` 194 | reset非常简单,将base和Cell数组中非空元素的值置为0. 195 | 196 | - long sumThenRest() 197 | 198 | ```java 199 | public long sumThenReset() { 200 | Cell[] as = cells; Cell a; 201 | long sum = base; 202 | base = 0L; 203 | if (as != null) { 204 | for (int i = 0; i < as.length; ++i) { 205 | if ((a = as[i]) != null) { 206 | sum += a.value; 207 | a.value = 0L; 208 | } 209 | } 210 | } 211 | return sum; 212 | } 213 | ``` 214 | sumThenReset同样非常简单,将某个Cell的值加到sum中后随即重置。 215 | 216 | - void add(long x) 217 | 218 | ```java 219 | public void add(long x) { 220 | Cell[] as; long b, v; int m; Cell a; 221 | // 判断cells是否为空,如果不为空则直接进入内层判断, 222 | // 否则尝试通过CAS在base上进行add操作,若CAS成功则结束,否则进入内层 223 | if ((as = cells) != null || !casBase(b = base, b + x)) { 224 | // 记录cell上的CAS操作是否失败 225 | boolean uncontended = true; 226 | if (as == null || (m = as.length - 1) < 0 || 227 | // 计算当前线程应该访问cells数组的哪个元素 228 | (a = as[getProbe() & m]) == null || 229 | // 尝试通过CAS操作在对应cell上add 230 | !(uncontended = a.cas(v = a.value, v + x))) 231 | longAccumulate(x, null, uncontended); 232 | } 233 | } 234 | ``` 235 | 236 | add方法会判断cells数组是否为空,非空则进入内层,否则尝试直接通过CAS操作在base上进行add。内层代码中,声明了一个uncontented变量来记录调用longAccumulate方法前在相应cell上是否进行了失败的CAS操作。 237 | 238 | 下面重点来看longAccumelate方法: 239 | 240 | longAccumulate时Striped64类中定义的,其中包含了初始化cells数组,改变cells数组长度,新建cell等逻辑。 241 | 242 | ```java 243 | final void longAccumulate(long x, LongBinaryOperator fn, 244 | boolean wasUncontended) { 245 | int h; 246 | if ((h = getProbe()) == 0) { 247 | ThreadLocalRandom.current(); // 初始化当前线程的probe,以便于找到线程对应的cell 248 | h = getProbe(); 249 | wasUncontended = true; // 标记执行longAccumulate前对相应cell的CAS操作是否失败,失败为false 250 | } 251 | boolean collide = false; // 是否冲突,如果当前线程尝试访问的cell元素与其他线程冲突,则为true 252 | for (;;) { 253 | Cell[] as; Cell a; int n; long v; 254 | // 当前cells不为空且元素个数大于0则进入内层,否则尝试初始化 255 | if ((as = cells) != null && (n = as.length) > 0) { 256 | if ((a = as[(n - 1) & h]) == null) { 257 | if (cellsBusy == 0) { // 尝试添加新的cell 258 | Cell r = new Cell(x); 259 | if (cellsBusy == 0 && casCellsBusy()) { 260 | boolean created = false; 261 | try { // Recheck under lock 262 | Cell[] rs; int m, j; 263 | if ((rs = cells) != null && 264 | (m = rs.length) > 0 && 265 | rs[j = (m - 1) & h] == null) { 266 | rs[j] = r; 267 | created = true; 268 | } 269 | } finally { 270 | cellsBusy = 0; 271 | } 272 | if (created) 273 | break; 274 | continue; 275 | } 276 | } 277 | collide = false; 278 | } 279 | else if (!wasUncontended) // 如果已经进行了失败的CAS操作 280 | wasUncontended = true; // 则不调用下面的a.cas()函数(反正肯定是失败的),而是重新计算probe值来尝试 281 | else if (a.cas(v = a.value, ((fn == null) ? v + x :fn.applyAsLong(v, x)))) 282 | break; 283 | else if (n >= NCPU || cells != as) 284 | collide = false; // 如果当前cells长度大于CPU个数则不进行扩容,因为每个cell都使用一个CPU处理时性能才是最高的 285 | // 如果当前cells已经过时(其他线程对cells执行了扩容操作,改变了cells指向),也不会扩容 286 | else if (!collide) 287 | collide = true; // 执行到此处说明a.cas()执行失败,即有冲突,将collide置为true, 288 | // 跳过扩容阶段,重新获取probe,到cells不同位置尝试cas,再次失败则扩容 289 | // 扩容 290 | else if (cellsBusy == 0 && casCellsBusy()) { 291 | try { 292 | if (cells == as) { 293 | Cell[] rs = new Cell[n << 1]; 294 | for (int i = 0; i < n; ++i) 295 | rs[i] = as[i]; 296 | cells = rs; 297 | } 298 | } finally { 299 | cellsBusy = 0; 300 | } 301 | collide = false; 302 | continue; // 扩容后再次尝试(扩容后cells长度改变, 303 | // 根据(n - 1) & h计算当前线程在cells中对应元素下标会变化,减少再次冲突的可能性) 304 | } 305 | h = advanceProbe(h); // 重新计算线程probe,减小下次访问cells元素时的冲突机会 306 | } 307 | // 初始化cells数组 308 | else if (cellsBusy == 0 && cells == as && casCellsBusy()) { 309 | boolean init = false; 310 | try { 311 | if (cells == as) { 312 | Cell[] rs = new Cell[2]; 313 | rs[h & 1] = new Cell(x); 314 | cells = rs; 315 | init = true; 316 | } 317 | } finally { 318 | cellsBusy = 0; 319 | } 320 | if (init) 321 | break; 322 | } 323 | // 尝试通过base的CAS操作进行add,成功则结束当前函数,否则再次循环 324 | else if (casBase(v = base, ((fn == null) ? v + x :fn.applyAsLong(v, x)))) 325 | break; 326 | } 327 | } 328 | 329 | ``` 330 | 代码比较复杂,细节的解释都写在注释中了。大体逻辑就是判断cells是否为空或者长度为0:如果空或者长度为0则尝试进行cells数组初始化,初始化失败的话则尝试通过CAS操作在base上进行add,仍然失败则重走一次流程;如果cells不为空且长度大于0,则获取当前线程对应于cells中的元素,如果该元素为null则尝试创建,否则尝试通过CAS操作在上面进行add,仍失败则扩容。 331 | 332 | ## LongAccumulator 333 | 334 | LongAdder是LongAccumulator的特例,两者都继承自Striped64。 335 | 336 | 看如下代码: 337 | 338 | ```java 339 | public LongAccumulator(LongBinaryOperator accumulatorFunction, 340 | long identity) { 341 | this.function = accumulatorFunction; 342 | base = this.identity = identity; 343 | } 344 | 345 | public interface LongBinaryOperator { 346 | long applyAsLong(long left, long right); 347 | } 348 | ``` 349 | LongAccumulator构造器允许传入一个双目运算符接口用于自定义加法规则,还允许传入一个初始值。 350 | 351 | 自定义的加法函数是如何被应用的呢?以上提到的longAccumulate()方法中有如下代码: 352 | 353 | ```java 354 | a.cas(v = a.value, ((fn == null) ? v + x :fn.applyAsLong(v, x))) 355 | ``` 356 | 357 | LongAdder的add()方法中调用longAccumulate()方法时传入的是null,而LongAccumulator的accumulate()方法传入的是this.function,即自定义的加法函数。 358 | 359 | ## 更多 360 | 361 | 相关笔记:[《Java并发编程之美》阅读笔记](/README.md) 362 | -------------------------------------------------------------------------------- /05Java并发包中并发List源码剖析.md: -------------------------------------------------------------------------------- 1 | # 第5章 Java并发包中并发List源码剖析 2 | 3 | ## 目录 4 | 5 | - [介绍](#介绍) 6 | - [源码解析](#源码解析) 7 | - [初始化](#初始化) 8 | - [添加元素](#添加元素) 9 | - [获取指定位置元素](#获取指定位置元素) 10 | - [修改指定元素](#修改指定元素) 11 | - [删除元素](#删除元素) 12 | - [弱一致性的迭代器](#弱一致性的迭代器) 13 | - [更多](#更多) 14 | 15 | ## 介绍 16 | 17 | JUC包中的并发List只有CopyOnWriteArrayList。CopyOnWriteArrayList是一个线程安全的ArrayList,使用了写时复制策略,对其进行的修改操作都是在底层的一个复制的数组上进行的。 18 | 19 | ## 源码解析 20 | 21 | ### 初始化 22 | 23 | CopyOnWriteArrayList内部包含一个array: 24 | 25 | ```java 26 | /** The array, accessed only via getArray/setArray. */ 27 | private transient volatile Object[] array; 28 | ``` 29 | 30 | 无参构造函数在内部创建了一个大小为0的object数组作为array的初始值 31 | 32 | ```java 33 | public CopyOnWriteArrayList() { 34 | setArray(new Object[0]); 35 | } 36 | ``` 37 | 38 | 下面看有参构造函数: 39 | ```java 40 | // 根据传入数组创建array对象 41 | public CopyOnWriteArrayList(E[] toCopyIn) { 42 | setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class)); 43 | } 44 | 45 | // 根据集合创建array对象 46 | public CopyOnWriteArrayList(Collection c) { 47 | Object[] elements; 48 | if (c.getClass() == CopyOnWriteArrayList.class) 49 | elements = ((CopyOnWriteArrayList)c).getArray(); 50 | else { 51 | elements = c.toArray(); 52 | // c.toArray might (incorrectly) not return Object[] (see 6260652) 53 | if (elements.getClass() != Object[].class) 54 | elements = Arrays.copyOf(elements, elements.length, Object[].class); 55 | } 56 | setArray(elements); 57 | } 58 | ``` 59 | 60 | 关于“c.toArray might (incorrectly) not return Object[] (see 6260652)”的注释可参考[《JDK1.6集合框架bug:c.toArray might (incorrectly) not return Object[] (see 6260652)》](https://blog.csdn.net/aitangyong/article/details/30274749)。 61 | 62 | ### 添加元素 63 | 64 | CopyOnWriteList中用来添加元素的函数有add(E e)、add(int index, E element)、addIfAbsent(E e)等,其原理类似,下面以add(E e)为例进行讲解。 65 | 66 | ```java 67 | public boolean add(E e) { 68 | // 获取独占锁 69 | final ReentrantLock lock = this.lock; 70 | lock.lock(); 71 | try { 72 | // 获取array 73 | Object[] elements = getArray(); 74 | // 复制array到新数组,并将新元素添加到新数组 75 | int len = elements.length; 76 | Object[] newElements = Arrays.copyOf(elements, len + 1); 77 | newElements[len] = e; 78 | // 用新数组代替原来的数组 79 | setArray(newElements); 80 | return true; 81 | } finally { 82 | lock.unlock(); 83 | } 84 | } 85 | ``` 86 | 调用add方法的线程会首先获取独占锁,保证同时最多有一个线程调用此方法,其他线程会被阻塞直到锁被释放。 87 | 88 | 获取array后将array复制到一个新数组(从代码可知新数组的长度比原长度大1,所以CopyOnWriteArrayList时无界list),并把新增的元素添加到新数组。 89 | 90 | ### 获取指定位置元素 91 | 92 | 使用E get(int index)获取下标为index的元素,如果元素不存在则抛出IndexOutOfBoundException异常。 93 | 94 | ```java 95 | public E get(int index) { 96 | return get(getArray(), index); 97 | } 98 | 99 | private E get(Object[] a, int index) { 100 | return (E) a[index]; 101 | } 102 | 103 | final Object[] getArray() { 104 | return array; 105 | } 106 | ``` 107 | 获取指定位置的元素需要两步:首先获取array,然后通过下标访问指定位置的元素。整个过程没有加锁,在多线程下会出现**弱一致性**问题。 108 | 109 | 假设某一时刻CopyOnWriteArrayList中有1,2,3中三个元素,如下图所示: 110 | 111 | ![](images/04.png) 112 | 113 | 由于整个过程未加锁,可能导致一个线程x在获取array后,另一个线程y进行了remove操作,假设要删除的元素为3。remove操作首先会获取独占锁,然后进行写时复制操作,也就是复制一份当前array数组,然后再复制的数组里面删除线程x通过get方法要访问的元素3,之后让array指向复制的数组。而这时线程x仍持有对原来的array的引用,导致虽然线程y删除了元素3,线程x仍能获得3这个元素,如图: 114 | 115 | ![](images/05.png) 116 | 117 | ### 修改指定元素 118 | 119 | 使用E set(int index, E element)方法修改指定元素的值,如果指定位置的元素不存在则抛出IndexOutOfBoundsException异常: 120 | 121 | ```java 122 | public E set(int index, E element) { 123 | final ReentrantLock lock = this.lock; 124 | lock.lock(); 125 | try { 126 | Object[] elements = getArray(); 127 | E oldValue = get(elements, index); 128 | 129 | if (oldValue != element) { 130 | int len = elements.length; 131 | Object[] newElements = Arrays.copyOf(elements, len); 132 | newElements[index] = element; 133 | setArray(newElements); 134 | } else { 135 | // Not quite a no-op; ensures volatile write semantics 136 | setArray(elements); 137 | } 138 | return oldValue; 139 | } finally { 140 | lock.unlock(); 141 | } 142 | } 143 | ``` 144 | 145 | 首先获取独占锁,从而阻止其他线程对array数组进行修改,然后获取当前数组,并调用get方法获取指定位置的元素,如果指定位置的元素值与新值不一致就创建新数组并复制元素,然后在新数组上修改指定位置的元素值并设置新数组到array。即使指定位置的元素值与新值一样,为了保证volatile语义,也需要重新设置array(此处可参看[《CopyOnWriteArrayList与java内存模型》](https://blog.csdn.net/cumtwyc/article/details/52267414))。 146 | 147 | ### 删除元素 148 | 149 | 删除list里的元素,可以使用E remove(int index)、boolean remove(Object o)和boolean remove(Object o, Object[] snapshot, int index)等方法,其原理类似,下面以remove(int index)为例进行讲解。 150 | 151 | ```java 152 | public E remove(int index) { 153 | final ReentrantLock lock = this.lock; 154 | lock.lock(); 155 | try { 156 | Object[] elements = getArray(); 157 | int len = elements.length; 158 | E oldValue = get(elements, index); 159 | int numMoved = len - index - 1; 160 | if (numMoved == 0) 161 | setArray(Arrays.copyOf(elements, len - 1)); 162 | else { 163 | Object[] newElements = new Object[len - 1]; 164 | System.arraycopy(elements, 0, newElements, 0, index); 165 | System.arraycopy(elements, index + 1, newElements, index, 166 | numMoved); 167 | setArray(newElements); 168 | } 169 | return oldValue; 170 | } finally { 171 | lock.unlock(); 172 | } 173 | } 174 | ``` 175 | 176 | 首先获取独占锁以保证线程安全,然后获取要被删除的元素,并把剩余的元素复制到新数组,之后使用新数组替换原来的数组,最后在返回前释放锁。 177 | 178 | ### 弱一致性的迭代器 179 | 180 | 弱一致性指返回迭代器后,其他线程对list的改动对迭代器时不可见的。 181 | 182 | ```java 183 | public Iterator iterator() { 184 | return new COWIterator(getArray(), 0); 185 | } 186 | 187 | static final class COWIterator implements ListIterator { 188 | 189 | // array的快照 190 | private final Object[] snapshot; 191 | // 数组下标 192 | private int cursor; 193 | 194 | private COWIterator(Object[] elements, int initialCursor) { 195 | cursor = initialCursor; 196 | snapshot = elements; 197 | } 198 | 199 | public boolean hasNext() { 200 | return cursor < snapshot.length; 201 | } 202 | 203 | public E next() { 204 | if (! hasNext()) 205 | throw new NoSuchElementException(); 206 | return (E) snapshot[cursor++]; 207 | } 208 | } 209 | ``` 210 | 调用iterator()方法时实际上会返回一个COWIterator对象,COWIterator对象的snapshot变量保存了当前list的内容。之所以说snapshot是list的快照是因为虽然snapshot获得了array的引用,但当其他线程修改了list时,array会指向新复制出来的数组,而snapshot仍指向原来array指向的数组,两者操作不同的数组,这就是弱一致性。 211 | 212 | 以下为弱一致性的示例: 213 | ```java 214 | public class CopyListTest { 215 | private static volatile CopyOnWriteArrayList arrayList = new CopyOnWriteArrayList<>(); 216 | 217 | public static void main(String[] args) throws InterruptedException { 218 | arrayList.add("Java"); 219 | arrayList.add("Scala"); 220 | arrayList.add("Groovy"); 221 | arrayList.add("Kotlin"); 222 | 223 | Thread threadOne = new Thread(new Runnable() { 224 | @Override 225 | public void run() { 226 | arrayList.set(0, "hello"); 227 | arrayList.remove(2); 228 | } 229 | }); 230 | 231 | // 在修改之前获取迭代器 232 | Iterator it = arrayList.iterator(); 233 | 234 | threadOne.start(); 235 | 236 | // 等待子线程执行完毕 237 | threadOne.join(); 238 | 239 | // 迭代 240 | while(it.hasNext()) { 241 | System.out.println(it.next()); 242 | } 243 | 244 | System.out.println("========================================="); 245 | 246 | // 再次迭代 247 | it = arrayList.iterator(); 248 | 249 | // 迭代 250 | while(it.hasNext()) { 251 | System.out.println(it.next()); 252 | } 253 | } 254 | } 255 | ``` 256 | 257 | 输出如图: 258 | 259 | ![](images/06.png) 260 | 261 | 由上可知,对list的修改对于首次迭代是不可见的,这即是弱一致性的体现。 262 | 263 | ## 更多 264 | 265 | 相关笔记:[《Java并发编程之美》阅读笔记](/README.md) 266 | -------------------------------------------------------------------------------- /06Java并发包中锁原理剖析.md: -------------------------------------------------------------------------------- 1 | # 第6章 Java并发包中锁原理剖析 2 | 3 | ## 目录 4 | - [LockSupport工具类](#locksupport工具类) 5 | - [1. void park()](#1-void-park) 6 | - [2. void unpark(Thread thread)](#2-void-unparkthread-thread) 7 | - [3. void parkNanos(long nanos)](#3-void-parknanoslong-nanos) 8 | - [抽象同步队列AQS概述](#抽象同步队列aqs概述) 9 | - [AQS——锁的底层支持](#aqs锁的底层支持) 10 | - [AQS——条件变量的支持](#aqs条件变量的支持) 11 | - [基于AQS实现自定义同步器](#基于aqs实现自定义同步器) 12 | - [ReentrantLock的原理](#reentrantlock的原理) 13 | - [类图结构](#类图结构) 14 | - [获取锁](#获取锁) 15 | - [void lock()](#void-lock) 16 | - [void lockInterruptibly()](#void-lockinterruptibly) 17 | - [boolean tryLock()](#boolean-trylock) 18 | - [boolean tryLock(long timeout, TimeUnit unit)](#boolean-trylocklong-timeout-timeunit-unit) 19 | - [释放锁](#释放锁) 20 | - [void unlock()](#void-unlock) 21 | - [读写锁ReentrantReadWriteLock原理](#读写锁reentrantreadwritelock原理) 22 | - [类图结构](#类图结构-1) 23 | - [写锁的获取与释放](#写锁的获取与释放) 24 | - [void lock()](#void-lock-1) 25 | - [void lockInterruptibly()](#void-lockinterruptibly-1) 26 | - [boolean tryLock()](#boolean-trylock-1) 27 | - [void unlock()](#void-unlock-1) 28 | - [读锁的获取与释放](#读锁的获取与释放) 29 | - [void lock()](#void-lock-2) 30 | - [void unlock()](#void-unlock-2) 31 | - [案例介绍](#案例介绍) 32 | - [更多](#更多) 33 | 34 | ## LockSupport工具类 35 | 36 | LockSupport是创建锁和其他同步类的基础。 37 | 38 | LockSupport类与每个使用它的线程都会关联一个许可证,默认情况下调用LockSupport类的方法的线程是不持有许可证的。 39 | 40 | 下面介绍LockSupport类中的几个主要函数。 41 | 42 | ### 1. void park() 43 | 44 | 如果park方法拿到了与LockSupport关联的许可证,则调用LockSupport.park()时会马上返回,否则调用线程会被禁止参与线程的调度,也就是会被阻塞挂起。 45 | 46 | 如下代码直接在main函数里面调用park方法,最终只会输出 "begin park!",然后当前线程被挂起,这时因为在默认情况下调用线程是不持有许可证的。 47 | 48 | ```java 49 | public static void main(String[] args) { 50 | System.out.println("begin park!"); 51 | LockSupport.park(); 52 | System.out.println("end park!"); 53 | } 54 | ``` 55 | 在其他线程调用unpark(Thread thread)方法并且将当前线程作为参数时,调用park方法而被阻塞的线程会返回。另外,如果其他线程调用了阻塞线程的interrupt()方法,设置了中断标志或者线程被虚假唤醒,则线程也会返回。所以在调用park方法时最好也使用循环条件判断方式。 56 | 57 | > 注意: 58 | > 因调用park方法而被阻塞的线程被其他线程中断而返回时并不会抛出InterruptedException异常。 59 | 60 | ### 2. void unpark(Thread thread) 61 | 62 | 当一个线程调用unpark时,如果参数thread线程没有持有thread与LockSupport类相关联的许可证,则让thread线程持有。如果thread因调用park()而被挂起,则unpark方法会使其被唤醒。如果thread之前没有调用park,则调用unpark方法后再调用park方法会立即返回,代码如下。 63 | 64 | ```java 65 | public static void main(String[] args) { 66 | System.out.println("begin park!"); 67 | LockSupport.unpark(Thread.currentThread()); 68 | LockSupport.park(); 69 | System.out.println("end park!"); 70 | } 71 | ``` 72 | 输出如下: 73 | 74 | begin park! 75 | end park! 76 | 77 | 78 | 下面再来看一个例子来加深对park和unpark的理解。 79 | 80 | ```java 81 | public static void main(String[] args) throws InterruptedException { 82 | Thread thread = new Thread(new Runnable() { 83 | @Override 84 | public void run() { 85 | System.out.println("child thread begin park!"); 86 | // 挂起自己 87 | LockSupport.park(); 88 | System.out.println("child thread unpark!"); 89 | } 90 | }); 91 | 92 | thread.start(); 93 | 94 | // 确保调用unpark前子线程已经将自己挂起 95 | Thread.sleep(1000); 96 | 97 | System.out.println("main thread begin unpark!"); 98 | 99 | LockSupport.unpark(thread); 100 | } 101 | ``` 102 | 103 | 子线程将自己挂起,主线程中调用了unpark方法使得子线程得以继续运行。 104 | 105 | ### 3. void parkNanos(long nanos) 106 | 107 | 和park方法类似,如果调用park方法的线程已经拿到了与LockkSupport关联的许可证,则调用LockSupport.parkNanos(long nanos)方法会立即返回。不同之处在于,如果没有拿到许可证,则调用线程会被挂起nanos时间后自动返回。 108 | 109 | 110 | ## 抽象同步队列AQS概述 111 | 112 | ### AQS——锁的底层支持 113 | 114 | AbstractQueuedSynchronizer抽象同步队列简称AQS,是实现同步器的基础组件。 115 | 116 | 以下为AQS的类结构图: 117 | 118 | ![](images/07.png) 119 | 120 | AQS是一个FIFO的双向队列,内部通过head和tail两个节点来对队列进行维护。 121 | 122 | Node是AQS的一个静态内部类,属性SHARED和EXCLUSIVE分别代表用来标识线程是获取共享资源和独占资源时被阻塞挂起放入AQS队列的。thread为Node持有的Thread;waitStatus用于记录当前线程的状态,CANCELLED表示线程被取消,SIGNAL表示线程需要唤醒,CONDITION表示线程在条件队列里面等待,PROPAGATE表示释放共享资源时需要通知其他节点。 123 | 124 | AQS维护了一个单一的状态信息state,可以通过getState、setState、compareAndSetState函数修改其值。 125 | 126 | AQS内部类ConditionObject用来结合锁实现线程同步。 127 | 128 | AQS实现线程同步的关键是对state进行操作,根据state是否属于一个线程,操作state的方式可分为独占方式和共享方式。 129 | 130 | 独占方式下获取和释放资源的方法为: 131 | > void acquire(int arg) 132 | > void acauireInterruptibly(int arg) 133 | > boolean release(int arg) 134 | 135 | 共享方式下获取和释放资源的方法为: 136 | > void acauireShared(int arg) 137 | > void acauireSharedInterruptibly(int arg) 138 | > boolean releaseShared(int arg) 139 | 140 | 独占方式下,获取和释放资源的流程如下: 141 | 142 | 当一个线程调用acquire(int arg)获取独占资源时,会首先使用tryAcquire方法进行尝试,具体就是设置state的值,成功则世界返回,失败则将当前线程封装为类型为Node.EXCLUSIVE的Node节点后插入到AQS阻塞队列的尾部,并调用LockSupport.park(this)挂起自己。 143 | 144 | ```java 145 | public final void acquire(int arg) { 146 | if (!tryAcquire(arg) && 147 | acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 148 | selfInterrupt(); 149 | } 150 | ``` 151 | 但一个线程调用release(int arg)会尝试使用tryRelease操作释放资源,这里也是改变state的值,然后调用LockSupport.unpark(thread)方法激活AQS队列里面被阻塞的一个线程(thread)。被阻塞的线程使用tryAcquire尝试,看当前state的值是否满足自己的需要,满足则该线程被激活,继续向下运行,否则还是会被放入AQS队列并被挂起。 152 | 153 | ```java 154 | public final boolean release(int arg) { 155 | if (tryRelease(arg)) { 156 | Node h = head; 157 | if (h != null && h.waitStatus != 0) 158 | unparkSuccessor(h); 159 | return true; 160 | } 161 | return false; 162 | } 163 | ``` 164 | > 注意; 165 | > AQS类并没有提供tryAcquire和tryRelease方法的实现,因为AQS是一个基础框架,这两个方法需要由子类自己实现来实现自己的特性。 166 | 167 | 共享方式下,获取和释放资源的流程如下; 168 | 169 | 当线程调用acquireShared(int arg)获取共享资源时,首先使用tryAcquireShared尝试获取资源并修改state,成功则直接放回,否则将当前线程封装为Node.SHARED类型的节点插入到AQS阻塞队列的尾部,并使用LockSupport.park(this)方法挂起自己。 170 | 171 | ```java 172 | public final void acquireShared(int arg) { 173 | if (tryAcquireShared(arg) < 0) 174 | doAcquireShared(arg); 175 | } 176 | ``` 177 | 178 | 当一个线程调用releaseShared(int arg)时会尝试使用tryReleasedShared操作释放资源并修改state,然后使用LockSupport.unpark(thread)激活AQS队列中的一个线程(thread)。被激活的线程会调用tryReleaseShared查看当前state是否满足自己需求,满足则该线程被激活,否则继续挂起。 179 | 180 | ```java 181 | public final boolean releaseShared(int arg) { 182 | if (tryReleaseShared(arg)) { 183 | doReleaseShared(); 184 | return true; 185 | } 186 | return false; 187 | } 188 | ``` 189 | 190 | > 注意: 191 | > 同上,AQS没有提供tryAcquiredShared和tryReleaseShared方法的实现,这两个方法也需要由子类实现。 192 | 193 | ### AQS——条件变量的支持 194 | 195 | 以下是使用条件变量的例子: 196 | 197 | ```java 198 | ReentrantLock lock = new ReentrantLock(); 199 | Condition condition = lock.newCondition(); 200 | 201 | lock.lock(); 202 | try{ 203 | System.out.println("begin wait"); 204 | condition.await(); 205 | System.out.println("end wait"); 206 | } catch (InterruptedException e) { 207 | e.printStackTrace(); 208 | }finally { 209 | lock.unlock(); 210 | } 211 | 212 | lock.lock(); 213 | try{ 214 | System.out.println("begin signal"); 215 | condition.signal(); 216 | System.out.println("end signal"); 217 | }catch (Exception e){ 218 | e.printStackTrace(); 219 | }finally { 220 | lock.unlock(); 221 | } 222 | ``` 223 | 上述代码中,condition是由Lock对象调用newCondition方法创建的条件变量,一个Lock对象可以创建多个条件变量。 224 | 225 | lock.lock()方法相当于进入synchronized同步代码块,用于获取独占锁;await()方法相当于Object.wait()方法,用于阻塞挂起当前线程,当其他线程调用了signal方法(相当于Object.notify()方法)时,被阻塞的线程才会从await处返回。 226 | 227 | lock.newCondition()作用是new一个在AQS内部类ConditionObject对象。每个条件变量内部都维护了一个条件队列,用来存放调用该条件变量的await方法时被阻塞的线程。 228 | 229 | > 注意: 230 | > 这个条件队列和AQS队列不是一回事。 231 | 232 | 以下是await的源码: 233 | 234 | ```java 235 | public final void await() throws InterruptedException { 236 | if (Thread.interrupted()) 237 | throw new InterruptedException(); 238 | // 创建新的node节点,并插入到条件队列末尾 239 | Node node = addConditionWaiter(); 240 | // 释放当前线程的锁 241 | int savedState = fullyRelease(node); 242 | int interruptMode = 0; 243 | // 调用park方法阻塞挂起当前线程 244 | while (!isOnSyncQueue(node)) { 245 | LockSupport.park(this); 246 | if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) 247 | break; 248 | } 249 | ... 250 | } 251 | ``` 252 | 首先会构造一个类型为Node.CONDITION的node节点,然后将该节点处插入条件队列末尾,之后当前线程会释放获取的锁,并被阻塞挂起。这时如果有其他线程调用lock.lock()方法尝试获取锁,就会有一个线程获取到锁。 253 | 254 | 再来看signal源码: 255 | 256 | ```java 257 | public final void signal() { 258 | if (!isHeldExclusively()) 259 | throw new IllegalMonitorStateException(); 260 | Node first = firstWaiter; 261 | if (first != null) 262 | // 将条件队列头元素移动到AQS队列等待执行 263 | doSignal(first); 264 | } 265 | ``` 266 | 调用signal时,会把条件队列队首元素放入AQS中并激活队首元素对应的线程。 267 | 268 | 269 | ### 基于AQS实现自定义同步器 270 | 271 | 下面基于AQS实现一个不可重入的独占锁。自定义AQS重写一系列函数,还需要定义原子变量state的含义。这里定义state为0表示目前锁没有被线程持有,state为1表示锁已经被某一个线程持有。 272 | 273 | ```java 274 | public class NonReentrantLock implements Lock, Serializable { 275 | 276 | // 内部帮助类 277 | private static class Sync extends AbstractQueuedSynchronizer { 278 | 279 | // 锁是否被持有 280 | @Override 281 | protected boolean isHeldExclusively() { 282 | return getState() == 1; 283 | } 284 | 285 | // 尝试获取锁 286 | @Override 287 | protected boolean tryAcquire(int arg) { 288 | if (compareAndSetState(0, 1)) { 289 | setExclusiveOwnerThread(Thread.currentThread()); 290 | return true; 291 | } 292 | return false; 293 | } 294 | 295 | // 尝试释放锁 296 | @Override 297 | protected boolean tryRelease(int arg) { 298 | if(getState() == 0) { 299 | throw new IllegalMonitorStateException(); 300 | } 301 | setExclusiveOwnerThread(null); 302 | setState(0); 303 | return true; 304 | } 305 | 306 | // 提供条件变量接口 307 | Condition newCondition() { 308 | return new ConditionObject(); 309 | } 310 | 311 | } 312 | 313 | // 创建一个Sync来做具体工作 314 | private final Sync sync = new Sync(); 315 | 316 | @Override 317 | public void lock() { 318 | sync.acquire(1); 319 | } 320 | 321 | 322 | @Override 323 | public boolean tryLock() { 324 | return sync.tryAcquire(1); 325 | } 326 | 327 | @Override 328 | public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { 329 | return sync.tryAcquireNanos(1, unit.toNanos(time)); 330 | } 331 | 332 | 333 | @Override 334 | public void unlock() { 335 | sync.tryRelease(1); 336 | } 337 | 338 | 339 | @Override 340 | public void lockInterruptibly() throws InterruptedException { 341 | sync.acquireInterruptibly(1); 342 | } 343 | 344 | 345 | @Override 346 | public Condition newCondition() { 347 | return sync.newCondition(); 348 | } 349 | } 350 | ``` 351 | 352 | NonReentrantLock定义了一个内部类Sync用来实现具体的锁的操作,Sync继承了AQS。由于是独占锁,:Sync只重写了tryAcquire、tryRelease、isHeldExclusively。此外,Sync提供了newCondition方法来支持条件变量。 353 | 354 | 下面使用自定义的锁来实现简单的生产-消费模型 355 | 356 | ```java 357 | final static NonReentrantLock lock = new NonReentrantLock(); 358 | final static Condition notFull = lock.newCondition(); 359 | final static Condition notEmpty = lock.newCondition(); 360 | 361 | final static Queue queue = new LinkedBlockingQueue<>(); 362 | final static int queueSize = 10; 363 | 364 | public static void main(String[] args) { 365 | Thread producer = new Thread(new Runnable() { 366 | @Override 367 | public void run() { 368 | // 获取独占锁 369 | lock.lock(); 370 | try { 371 | while (true) { 372 | // 队列满了则等待 373 | while (queue.size() >= queueSize) { 374 | notEmpty.await(); 375 | } 376 | queue.add("ele"); 377 | System.out.println("add..."); 378 | notFull.signalAll(); 379 | } 380 | } catch (InterruptedException e) { 381 | e.printStackTrace(); 382 | } finally { 383 | // 释放锁 384 | lock.unlock(); 385 | } 386 | } 387 | }); 388 | 389 | Thread consumer = new Thread(new Runnable() { 390 | @Override 391 | public void run() { 392 | // 获取独占锁 393 | lock.lock(); 394 | try { 395 | while (true) { 396 | // 队列空则等待 397 | while (queue.size() == 0) { 398 | notFull.await(); 399 | } 400 | String ele = queue.poll(); 401 | System.out.println("poll..."); 402 | notEmpty.signalAll(); 403 | } 404 | } catch (InterruptedException e) { 405 | e.printStackTrace(); 406 | } finally { 407 | // 释放锁 408 | lock.unlock(); 409 | } 410 | } 411 | }); 412 | 413 | producer.start(); 414 | consumer.start(); 415 | } 416 | ``` 417 | 418 | 代码使用了NonReentrantLock来创建lock,并调用lock.newCondition创建了两个条件变量用来实现生产者和消费者线程的同步。 419 | 420 | ## ReentrantLock的原理 421 | 422 | ### 类图结构 423 | 424 | ![](images/08.png) 425 | 426 | 构造函数如下: 427 | 428 | ```java 429 | public ReentrantLock() { 430 | sync = new NonfairSync(); 431 | } 432 | 433 | public ReentrantLock(boolean fair) { 434 | sync = fair ? new FairSync() : new NonfairSync(); 435 | } 436 | ``` 437 | 438 | 可以看到,ReentrantLock最终还是依赖AQS,并且根据传入的参数来决定其内部是一个公平锁还是非公平锁(默认为公平锁)。 439 | 440 | Sync类直接继承自AQS,它的子类NonfairSync和FairSync分别实现了获取锁的非公平与公平策略。 441 | 442 | AQS的state表示线程获取锁的可重入次数。state为0表示当前锁没有被任何线程持有。当一个线程第一次获取该所是会尝试使用CAS设置state为1,成功后记录该锁的持有者为当前线程。以后每一次加锁state就增加1,表示可重入次数。当该线程释放该锁时,state减1,如果减1后state为0,则当前线程释放该锁。 443 | 444 | ### 获取锁 445 | 446 | #### void lock() 447 | 448 | 当一个线程调用该方法时,如果锁当前没有被其他线程占有并且当前线程之前没有获取过该锁,则当前线程会获取到该锁,然后设置当前锁的拥有者为当前线程,并且将state置为1;如果当前线程已经获取过该锁,则将state的值增加1;如果该锁已经被其他线程持有,则调用该方法的线程会被放入AQS队列中阻塞挂起等待, 449 | 450 | ```java 451 | public void lock() { 452 | sync.lock(); 453 | } 454 | ``` 455 | 456 | ReentrantLock的lock()委托给了sync,根据创建ReentrantLock构造函数选择sync的实现时NonfairSync还是FairSync,这个锁是一个公平锁或者非公平锁。 457 | 458 | 先来看非公平锁的情况: 459 | 460 | ```java 461 | final void lock() { 462 | // CAS设置state为1 463 | if (compareAndSetState(0, 1)) 464 | setExclusiveOwnerThread(Thread.currentThread()); 465 | else 466 | // 调用AQS的acquire方法 467 | acquire(1); 468 | } 469 | ``` 470 | 471 | 默认state为0,所以第一个调用Lock的吸纳成会通过CAS设置状态值为1,CAS成功则表示当前线程获取到了锁,然后设置该锁持有者为当前线程。 472 | 473 | 如果此时有其他线程企图过去该锁,CAS会失败,然后会调用AQS的acquire方法。 474 | 475 | 再贴下acquire的源码: 476 | 477 | ```java 478 | public final void acquire(int arg) { 479 | if (!tryAcquire(arg) && 480 | acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 481 | selfInterrupt(); 482 | } 483 | ``` 484 | 485 | 之前说过,AQS并没有提供可用的tryAcquire方法,tryAcquire方法需要子类自己定制。这里会调用ReentrantLock重写的tryAcquire方法。下面先看非公平锁的代码。 486 | 487 | ```java 488 | protected final boolean tryAcquire(int acquires) { 489 | return nonfairTryAcquire(acquires); 490 | } 491 | 492 | final boolean nonfairTryAcquire(int acquires) { 493 | final Thread current = Thread.currentThread(); 494 | int c = getState(); 495 | // (1)锁未被持有 496 | if (c == 0) { 497 | if (compareAndSetState(0, acquires)) { 498 | // 设置锁的持有者为当前线程 499 | setExclusiveOwnerThread(current); 500 | return true; 501 | } 502 | } 503 | // (2)锁已经被某个线程持有,如果该线程为当前线程 504 | else if (current == getExclusiveOwnerThread()) { 505 | int nextc = c + acquires; 506 | if (nextc < 0) // overflow 507 | throw new Error("Maximum lock count exceeded"); 508 | // 增加重入数 509 | setState(nextc); 510 | return true; 511 | } 512 | return false; 513 | } 514 | ``` 515 | 516 | 源码比较简单,分析见注释。 517 | 518 | 下面来看非公平性体现在哪儿。首先非公平指的是先尝试获取锁的线程并不一定首先获取该锁。 519 | 520 | 假设线程A执行到代码(1)发现线程已经被持有然后执行到(2)发现当前线程不是锁持有者,则返回false被放入AQS中进行等待。假设这时线程B也执行到了代码(1),发现state为0(假设占有该锁的其他线程释放了该锁), 就成功获取了锁,而比B先请求锁的线程A还在等待,这就是非公平性的体现。 521 | 522 | 下面看FairSync重写的tryAcquire方法。 523 | 524 | ```java 525 | protected final boolean tryAcquire(int acquires) { 526 | final Thread current = Thread.currentThread(); 527 | int c = getState(); 528 | if (c == 0) { 529 | // 公平性策略 530 | if (!hasQueuedPredecessors() && 531 | compareAndSetState(0, acquires)) { 532 | setExclusiveOwnerThread(current); 533 | return true; 534 | } 535 | } 536 | else if (current == getExclusiveOwnerThread()) { 537 | int nextc = c + acquires; 538 | if (nextc < 0) 539 | throw new Error("Maximum lock count exceeded"); 540 | setState(nextc); 541 | return true; 542 | } 543 | return false; 544 | } 545 | ``` 546 | 547 | 由代码可知,公平的tryAcquire方法与非公平的区别在于增加了一个hasQueuedPredecessors方法来判断是否有线程在当前线程前尝试获取锁。 548 | 549 | 下面是hasQueuedPredecessors的具体实现。 550 | 551 | ```java 552 | public final boolean hasQueuedPredecessors() { 553 | Node t = tail; // Read fields in reverse initialization order 554 | Node h = head; 555 | Node s; 556 | return h != t && 557 | ((s = h.next) == null || s.thread != Thread.currentThread()); 558 | } 559 | ``` 560 | 561 | 如果当前线程节点有前驱节点则返回true,否则如果当前AQS队列为空或者当前线程节点是AQS的第一个节点则返回false。其中h==t说明当前队列为空,直接返回false;如果h!=t并且s==null说明有一个元素将要作为AQS的第一个节点入队(AQS入队包含两步操作:首先创建一个哨兵头节点,然后将第一个元素插入哨兵节点后面),那么返回true;如果h!=t并且s!=null且s.thread!=Thread.currentThread()说明队列里面的第一个元素不是当前线程,那么返回true。 562 | 563 | #### void lockInterruptibly() 564 | 565 | 与lock()方法类似,不同之处在于,它对中断进行相应,就是当前线程在调用该方法时,如果其他线程调用了当前线程的interrupt方法,则当前线程会抛出inetrruptedException 566 | 异常,然后返回。 567 | 568 | ```java 569 | public void lockInterruptibly() throws InterruptedException { 570 | sync.acquireInterruptibly(1); 571 | } 572 | 573 | public final void acquireInterruptibly(int arg) 574 | throws InterruptedException { 575 | // 如果当前线程被中断,则直接抛出异常并返回 576 | if (Thread.interrupted()) 577 | throw new InterruptedException(); 578 | if (!tryAcquire(arg)) 579 | doAcquireInterruptibly(arg); 580 | } 581 | ``` 582 | 583 | #### boolean tryLock() 584 | 585 | 尝试获取锁,如果当前该锁没有被其他线程持有,则当前线程获取该锁并返回true,否则返回false。该方法不会引起当前线程阻塞。 586 | 587 | ```java 588 | public boolean tryLock() { 589 | return sync.nonfairTryAcquire(1); 590 | } 591 | 592 | final boolean nonfairTryAcquire(int acquires) { 593 | final Thread current = Thread.currentThread(); 594 | int c = getState(); 595 | if (c == 0) { 596 | // 非公平策略 597 | if (compareAndSetState(0, acquires)) { 598 | setExclusiveOwnerThread(current); 599 | return true; 600 | } 601 | } 602 | else if (current == getExclusiveOwnerThread()) { 603 | int nextc = c + acquires; 604 | if (nextc < 0) // overflow 605 | throw new Error("Maximum lock count exceeded"); 606 | setState(nextc); 607 | return true; 608 | } 609 | return false; 610 | } 611 | ``` 612 | 613 | 上述代码与非公平锁的tryAcquire方法类似,所以tryLock使用的是非公平策略。 614 | 615 | #### boolean tryLock(long timeout, TimeUnit unit) 616 | 617 | ```java 618 | public boolean tryLock(long timeout, TimeUnit unit) 619 | throws InterruptedException { 620 | return sync.tryAcquireNanos(1, unit.toNanos(timeout)); 621 | } 622 | ``` 623 | 624 | 设置了超时时间,如果超时时间到了还没有获取到该锁则返回false。 625 | 626 | 627 | 628 | ### 释放锁 629 | 630 | #### void unlock() 631 | 632 | 尝试释放锁,如果当前线程持有该锁,则调用该方法会让该线程对该线程持有的AQS状态值减1,如果减去1后状态值为0,则当前线程会释放该锁。 633 | 634 | ```java 635 | public void unlock() { 636 | sync.release(1); 637 | } 638 | 639 | protected final boolean tryRelease(int releases) { 640 | int c = getState() - releases; 641 | // 如果当前线程不是该锁持有者直接抛出异常 642 | if (Thread.currentThread() != getExclusiveOwnerThread()) 643 | throw new IllegalMonitorStateException(); 644 | boolean free = false; 645 | // 若state变为0,则清空锁持有线程 646 | if (c == 0) { 647 | free = true; 648 | setExclusiveOwnerThread(null); 649 | } 650 | // 设置可重入次数减1 651 | setState(c); 652 | return free; 653 | } 654 | ``` 655 | 656 | ## 读写锁ReentrantReadWriteLock原理 657 | 658 | 解决线程安全问题使用ReentrantLock就可以,但是ReentrantLock是独占锁,同一时间只能有一个线程获取该锁,而实际中会出现读多写少的情况,显然使用ReentrantLock满足不了这个需求,这时就需要用到ReentrantReadWriteLock。ReentrantReadWriteLock采用读写分离的策略,允许多个线程同时获取写锁。 659 | 660 | ### 类图结构 661 | 662 | ![](images/09.png) 663 | 664 | 读写锁内部维护了一个ReadLock和一个WriteLock,它们依赖Sync实现具体功能。而Sync继承自AQS,并且也提供了公平与非公平的实现。下面只介绍非公平读写锁的实现。 665 | 666 | 我们知道AQS中只维护了一个state状态,ReentrantReadWriteLock巧妙地使用state的高16位表示读状态,也就是获取到读锁的次数;使用低16位表示获取到写锁的线程的可重入次数。 667 | 668 | ```java 669 | static final int SHARED_SHIFT = 16; 670 | // 读锁状态值65536 671 | static final int SHARED_UNIT = (1 << SHARED_SHIFT); 672 | // 写锁状态值65535 673 | static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1; 674 | // 写锁掩码,二进制,16个1 675 | static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; 676 | // 读锁线程数 677 | static int sharedCount(int c) { return c >>> SHARED_SHIFT; } 678 | // 写锁可重入数 679 | static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; } 680 | ``` 681 | 682 | 读写锁中的firstReader用来记录第一个获取到读锁的线程,firstReaderHoldCount记录第一个和获取到读锁的线程获取读锁的可重入次数。HoldCounter类型的cachedHoldCounter用来记录最后一个获取读锁的线程获取读锁的可重入次数。 683 | 684 | ```java 685 | static final class HoldCounter { 686 | int count = 0; 687 | // Use id, not reference, to avoid garbage retention 688 | final long tid = getThreadId(Thread.currentThread()); 689 | } 690 | ``` 691 | 692 | readHolds是ThreadLocal变量,用来存放除去第一个获取读锁线程外的其他线程获取读锁的可重入次数。ThreadLocalHoldCounter继承了ThreadLocal,因此initialValue方法返回一个HoldCounter对象。 693 | 694 | ```java 695 | static final class ThreadLocalHoldCounter 696 | extends ThreadLocal { 697 | public HoldCounter initialValue() { 698 | return new HoldCounter(); 699 | } 700 | } 701 | ``` 702 | 703 | ### 写锁的获取与释放 704 | 705 | #### void lock() 706 | 707 | 写锁与写锁、写锁与读锁是互斥的,如果当前已经有线程获取了读锁或写锁,则请求获取写锁的线程会被阻塞挂起。写锁是可重入锁,如果当前线程已经获取了该锁,再次获取只是简单地把可重入次数加1后返回。 708 | 709 | ```java 710 | public void lock() { 711 | sync.acquire(1); 712 | } 713 | 714 | public final void acquire(int arg) { 715 | if (!tryAcquire(arg) && 716 | acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 717 | selfInterrupt(); 718 | } 719 | ``` 720 | lock()内部调用了acquire方法,其中tryAcquire是ReentrantReadWriteLock内部的Sync类重写的。 721 | 722 | ```java 723 | protected final boolean tryAcquire(int acquires) { 724 | Thread current = Thread.currentThread(); 725 | int c = getState(); // 总状态 726 | int w = exclusiveCount(c); // 读锁状态 727 | // (1)c!=0说明读锁或写锁已经被获取 728 | if (c != 0) { 729 | //(2)w==0说明已经有线程获取了读锁,w!=0并且当前线程不是写锁拥有者,则返回false 730 | if (w == 0 || current != getExclusiveOwnerThread()) 731 | return false; 732 | // (3)当前线程已经获取了写锁,判断可重入次数 733 | if (w + exclusiveCount(acquires) > MAX_COUNT) 734 | throw new Error("Maximum lock count exceeded"); 735 | // (4)设置可重入次数 736 | setState(c + acquires); 737 | return true; 738 | } 739 | // (5)c==0说明锁还没有被获取,此处第一次获取 740 | if (writerShouldBlock() || 741 | !compareAndSetState(c, c + acquires)) 742 | return false; 743 | setExclusiveOwnerThread(current); 744 | return true; 745 | } 746 | ``` 747 | 748 | (1)如果当前AQS状态值不为0,说明当前已经有线程获取到了读锁或写锁。如果w==0说明state的低16位为0,而state不为0,那么高16位必不为0,说明有线程获取了读锁,所以直接返回false(读写互斥,保障数据一致性)。 749 | 750 | (2)如果w!=0说明当前已经有线程获取了该写锁,再看当前线程是不是该锁的持有者,不是则返回false。 751 | 752 | 执行到(3)说明当前线程已经获取到了该锁,所以判断该线程的可重入次数是否超过了最大值,是则抛出异常,否则执行(4)增加可重入次数。 753 | 754 | 如果state为0说明目前没有线程获取到读锁和写锁,所以执行(5)。对于writerShouldBlock(),非公平锁的实现为 755 | 756 | ```java 757 | final boolean writerShouldBlock() { 758 | return false; 759 | } 760 | ``` 761 | 762 | 说明(5)抢占式地执行CAS尝试获取写锁。 763 | 764 | 公平锁的实现为 765 | 766 | ```java 767 | final boolean writerShouldBlock() { 768 | return hasQueuedPredecessors(); 769 | } 770 | ``` 771 | 772 | 还是使用hasQueuedPredecessors来判断当前线程节点是否有前驱节点。 773 | 774 | #### void lockInterruptibly() 775 | 776 | 会对中断进行相应 777 | 778 | ```java 779 | public void lockInterruptibly() throws InterruptedException { 780 | sync.acquireSharedInterruptibly(1); 781 | } 782 | ``` 783 | 784 | #### boolean tryLock() 785 | 786 | 非阻塞方法,尝试获取写锁,如果当前没有其他线程持有读锁或写锁,则当前线程获取写锁并返回true,否则返回false。如果当前线程已经持有了该写锁则增加state的值并返回true。 787 | 788 | ```java 789 | public boolean tryLock( ) { 790 | return sync.tryWriteLock(); 791 | } 792 | 793 | final boolean tryWriteLock() { 794 | Thread current = Thread.currentThread(); 795 | int c = getState(); 796 | if (c != 0) { 797 | int w = exclusiveCount(c); 798 | if (w == 0 || current != getExclusiveOwnerThread()) 799 | return false; 800 | if (w == MAX_COUNT) 801 | throw new Error("Maximum lock count exceeded"); 802 | } 803 | // 非公平策略 804 | if (!compareAndSetState(c, c + 1)) 805 | return false; 806 | setExclusiveOwnerThread(current); 807 | return true; 808 | } 809 | ``` 810 | 此处代码于tryAcquire方法类似,只是使用了非公平策略。 811 | 812 | #### void unlock() 813 | 814 | 使state减1,如果减1后state为0,则当前线程会释放锁。 815 | 816 | ```java 817 | public void unlock() { 818 | sync.release(1); 819 | } 820 | 821 | public final boolean release(int arg) { 822 | if (tryRelease(arg)) { 823 | Node h = head; 824 | // 激活AQS队列里面的一个线程 825 | if (h != null && h.waitStatus != 0) 826 | unparkSuccessor(h); 827 | return true; 828 | } 829 | return false; 830 | } 831 | 832 | protected final boolean tryRelease(int releases) { 833 | // 检查是否使锁持有者调用的unlock 834 | if (!isHeldExclusively()) 835 | throw new IllegalMonitorStateException(); 836 | // 获取可重入值,没有考虑高16位,因为获取写锁时读锁状态值 肯定为。 837 | int nextc = getState() - releases; 838 | boolean free = exclusiveCount(nextc) == 0; 839 | if (free) 840 | setExclusiveOwnerThread(null); 841 | setState(nextc); 842 | return free; 843 | } 844 | ``` 845 | 846 | ### 读锁的获取与释放 847 | 848 | 读锁通过ReadLock来实现。 849 | 850 | #### void lock() 851 | 852 | 获取的锁,如果写锁没有被其他线程持有,则可以获取读锁,并将state的高16位加1;否则阻塞。 853 | 854 | ```java 855 | public void lock() { 856 | sync.acquireShared(1); 857 | } 858 | 859 | // 来自AQS 860 | public final void acquireShared(int arg) { 861 | if (tryAcquireShared(arg) < 0) 862 | doAcquireShared(arg); 863 | } 864 | ``` 865 | 866 | lock方法调用了AQS的acquireShared方法,其内部又调用了Sync重写的tryAcquireShared方法。 867 | 868 | ```java 869 | protected final int tryAcquireShared(int unused) { 870 | Thread current = Thread.currentThread(); 871 | int c = getState(); 872 | // 如果有其他线程获取了写锁,返回-1 873 | // 如果写锁被当前线程持有,那么也可以获取读锁,因为同一个线程同时最多只能执行读或写中的一个操作 874 | if (exclusiveCount(c) != 0 && 875 | getExclusiveOwnerThread() != current) 876 | return -1; 877 | int r = sharedCount(c); 878 | // 公平策略 879 | if (!readerShouldBlock() && 880 | r < MAX_COUNT && 881 | compareAndSetState(c, c + SHARED_UNIT)) { 882 | // 读锁被第一次获取 883 | if (r == 0) { 884 | firstReader = current; 885 | firstReaderHoldCount = 1; 886 | // 读锁被获取过,且当前线程就是第一次获取读锁的线程 887 | } else if (firstReader == current) { 888 | firstReaderHoldCount++; 889 | } else { 890 | // 记录最后一个获取读锁的线程或记录其他线程读锁的可重入次数 891 | HoldCounter rh = cachedHoldCounter; 892 | // 如果rh为空或者rh不是当前线程,需要通过get方法创建一个新的HoldCounter用来记录当前线程的可重入次数 893 | // 并将其设为cachedHoldCounter 894 | if (rh == null || rh.tid != getThreadId(current)) 895 | cachedHoldCounter = rh = readHolds.get(); 896 | // 运行到此处说明当前线程已经被设为最后一个获取读锁的线程,rh.count==0说明当前线程已经完全释放了读锁, 897 | // 现在又要获取读锁,需要更新自己对应的HoldCounter 898 | else if (rh.count == 0) 899 | readHolds.set(rh); 900 | // 增加重入数 901 | rh.count++; 902 | } 903 | return 1; 904 | } 905 | // 尝试一次失败后自旋获取 906 | return fullTryAcquireShared(current); 907 | } 908 | ``` 909 | 代码中readerShouldBlock用于决定代码公平与否。非公平锁的实现如下。 910 | 911 | ```java 912 | final boolean readerShouldBlock() { 913 | return apparentlyFirstQueuedIsExclusive(); 914 | } 915 | 916 | final boolean apparentlyFirstQueuedIsExclusive() { 917 | Node h, s; 918 | return (h = head) != null && 919 | (s = h.next) != null && 920 | !s.isShared() && 921 | s.thread != null; 922 | } 923 | ``` 924 | 925 | 仅当AQS队列存在元素且第一个元素在尝试获取写锁时才会阻塞当前线程,否则就算有线程在尝试获取读锁也不会让步(非公平性的体现)。 926 | 927 | #### void unlock() 928 | 929 | ```java 930 | public void unlock() { 931 | sync.releaseShared(1); 932 | } 933 | ``` 934 | 935 | 具体操作委托给sync。 936 | 937 | ```java 938 | public final boolean releaseShared(int arg) { 939 | if (tryReleaseShared(arg)) { 940 | doReleaseShared(); 941 | return true; 942 | } 943 | return false; 944 | } 945 | 946 | protected final boolean tryReleaseShared(int unused) { 947 | Thread current = Thread.currentThread(); 948 | ... 949 | for (;;) { 950 | int c = getState(); 951 | int nextc = c - SHARED_UNIT; 952 | if (compareAndSetState(c, nextc)) 953 | return nextc == 0; 954 | } 955 | } 956 | ``` 957 | 将state减去一个单位,如果结果为0,则返回true,调用doReleaseShared方法释放一个由于获取读锁而被阻塞的线程;如果不为0,说明仍有线程持有读锁,返回false。 958 | 959 | ### 案例介绍 960 | 961 | 下面基于ReentrantLock实现线程安全的list,适用于读多写少的情况 962 | 963 | ```java 964 | public class ReentrantLockList { 965 | 966 | private ArrayList array = new ArrayList<>(); 967 | private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); 968 | private final Lock readLock; 969 | private final Lock writeLock; 970 | 971 | public ReentrantLockList() { 972 | readLock = lock.readLock(); 973 | writeLock = lock.writeLock(); 974 | } 975 | 976 | public void add(String e) { 977 | writeLock.lock(); 978 | try{ 979 | array.add(e); 980 | }finally { 981 | writeLock.unlock(); 982 | } 983 | } 984 | 985 | public void remove(String e) { 986 | writeLock.lock(); 987 | try{ 988 | array.remove(e); 989 | }finally { 990 | writeLock.unlock(); 991 | } 992 | } 993 | 994 | public String get(int index) { 995 | readLock.lock(); 996 | try{ 997 | return array.get(index); 998 | }finally { 999 | readLock.unlock(); 1000 | } 1001 | } 1002 | 1003 | public int size() { 1004 | return array.size(); 1005 | } 1006 | } 1007 | ``` 1008 | 1009 | ## 更多 1010 | 1011 | 相关笔记:[《Java并发编程之美》阅读笔记](/README.md) 1012 | -------------------------------------------------------------------------------- /07Java并发包中并发队列原理剖析.md: -------------------------------------------------------------------------------- 1 | # 第7章 Java并发包中并发队列原理剖析 2 | 3 | ## 目录 4 | 5 | - [PriorityBlockingQueue](#priorityblockingqueue) 6 | - [类图结构](#类图结构) 7 | - [原理讲解](#原理讲解) 8 | - [boolean offer()](#boolean-offer) 9 | - [E poll()](#e-poll) 10 | - [void put(E e)](#void-pute-e) 11 | - [E take()](#e-take) 12 | - [DelayQueue](#delayqueue) 13 | - [类图结构](#类图结构-1) 14 | - [原理讲解](#原理讲解-1) 15 | - [boolean offer(E e)](#boolean-offere-e) 16 | - [E take()](#e-take-1) 17 | - [E poll()](#e-poll-1) 18 | - [int size()](#int-size) 19 | - [更多](#更多) 20 | 21 | LinkedBlockingQueue和ArrayBlockingQueue比较简单,不进行讲解了。下面只介绍PriorityBlockingQueue和DelayQueue。 22 | 23 | ## PriorityBlockingQueue 24 | 25 | PriorityBlockingQueue是带优先级的无界阻塞队列,每次出队都返回优先级最高或最低的元素。内部使用二叉堆实现。 26 | 27 | ### 类图结构 28 | 29 | ![](images/10.png) 30 | 31 | PriorityBlockingQueue内部有一个数组queue,用来存放队列元素。allocationSpinLock是个自旋锁,通过CAS操作来保证同时只有一个线程可以扩容队列,状态为0或1。 32 | 33 | 由于这是一个优先队列,所以有一个comparator用来比较元素大小。 34 | 35 | 下面为构造函数: 36 | 37 | ```java 38 | private static final int DEFAULT_INITIAL_CAPACITY = 11; 39 | 40 | public PriorityBlockingQueue() { 41 | this(DEFAULT_INITIAL_CAPACITY, null); 42 | } 43 | 44 | public PriorityBlockingQueue(int initialCapacity) { 45 | this(initialCapacity, null); 46 | } 47 | ``` 48 | 49 | 可知默认队列容量为11,默认比较器为null,也就是使用元素的compareTo方法进行比较来确定元素的优先级,这意味着队列元素必须实现Comparable接口。 50 | 51 | ### 原理讲解 52 | 53 | #### boolean offer() 54 | 55 | ```java 56 | public boolean offer(E e) { 57 | if (e == null) 58 | throw new NullPointerException(); 59 | // 获取独占锁 60 | final ReentrantLock lock = this.lock; 61 | lock.lock(); 62 | int n, cap; 63 | Object[] array; 64 | // 扩容 65 | while ((n = size) >= (cap = (array = queue).length)) 66 | tryGrow(array, cap); 67 | try { 68 | Comparator cmp = comparator; 69 | if (cmp == null) 70 | // 通过对二叉堆的上浮操作保证最大或最小的元素总在根节点 71 | siftUpComparable(n, e, array); 72 | else 73 | // 使用了自定义比较器 74 | siftUpUsingComparator(n, e, array, cmp); 75 | size = n + 1; 76 | // 激活因调用take()方法被阻塞的线程 77 | notEmpty.signal(); 78 | } finally { 79 | // 释放锁 80 | lock.unlock(); 81 | } 82 | return true; 83 | } 84 | ``` 85 | 86 | 流程比较简单,下面主要看扩容和建堆操作。 87 | 88 | 先看扩容。 89 | 90 | ```java 91 | private void tryGrow(Object[] array, int oldCap) { 92 | // 由前面的代码可知,调用tryGrow函数前先获取了独占锁, 93 | // 由于扩容比较费时,此处先释放锁, 94 | // 让其他线程可以继续操作(如果满足可操作的条件的话), 95 | // 以提升并发性能 96 | lock.unlock(); 97 | Object[] newArray = null; 98 | // 通过allocationSpinLock保证同时最多只有一个线程进行扩容操作。 99 | if (allocationSpinLock == 0 && 100 | UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset,0, 1)) { 101 | try { 102 | // 当容量比较小时,一次只增加2容量 103 | // 比较大时增加一倍 104 | int newCap = oldCap + ((oldCap < 64) ?(oldCap + 2) : (oldCap >> 1)); 105 | // 溢出检测 106 | if (newCap - MAX_ARRAY_SIZE > 0) { 107 | int minCap = oldCap + 1; 108 | if (minCap < 0 || minCap > MAX_ARRAY_SIZE) 109 | throw new OutOfMemoryError(); 110 | newCap = MAX_ARRAY_SIZE; 111 | } 112 | if (newCap > oldCap && queue == array) 113 | newArray = new Object[newCap]; 114 | } finally { 115 | // 释放锁,没用CAS是因为同时最多有一个线程操作allocationSpinLock 116 | allocationSpinLock = 0; 117 | } 118 | } 119 | // 如果当前线程发现有其他线程正在对队列进行扩容, 120 | // 则调用yield方法尝试让出CPU资源促使扩容操作尽快完成 121 | if (newArray == null) 122 | Thread.yield(); 123 | lock.lock(); 124 | if (newArray != null && queue == array) { 125 | queue = newArray; 126 | System.arraycopy(array, 0, newArray, 0, oldCap); 127 | } 128 | } 129 | ``` 130 | 131 | 下面来看建堆算法 132 | 133 | ```java 134 | private static void siftUpComparable(int k, T x, Object[] array) { 135 | Comparable key = (Comparable) x; 136 | while (k > 0) { 137 | // 获取父节点,设子节点索引为k, 138 | // 则由二叉堆的性质可知,父节点的索引总为(k - 1) >>> 1 139 | int parent = (k - 1) >>> 1; 140 | // 获取父节点对应的值 141 | Object e = array[parent]; 142 | // 只有子节点的值小于父节点的值时才上浮 143 | if (key.compareTo((T) e) >= 0) 144 | break; 145 | array[k] = e; 146 | k = parent; 147 | } 148 | array[k] = key; 149 | } 150 | ``` 151 | 152 | 如果了解二叉堆的话,此处代码是十分容易理解的。关于二叉堆,可参看[《数据结构之二叉堆》](https://blog.csdn.net/u010960184/article/details/82717074)。 153 | 154 | #### E poll() 155 | 156 | ```java 157 | public E poll() { 158 | final ReentrantLock lock = this.lock; 159 | lock.lock(); 160 | try { 161 | // 出队 162 | return dequeue(); 163 | } finally { 164 | lock.unlock(); 165 | } 166 | } 167 | 168 | private E dequeue() { 169 | int n = size - 1; 170 | if (n < 0) 171 | return null; 172 | else { 173 | Object[] array = queue; 174 | E result = (E) array[0]; 175 | // 获取尾节点,在实现对二叉堆的下沉操作时要用到 176 | E x = (E) array[n]; 177 | array[n] = null; 178 | Comparator cmp = comparator; 179 | if (cmp == null) 180 | // 下沉操作,保证取走最小的节点(根节点)后,新的根节点仍时最小的,二叉堆的性质依然满足 181 | siftDownComparable(0, x, array, n); 182 | else 183 | // 使用自定义比较器 184 | siftDownUsingComparator(0, x, array, n, cmp); 185 | size = n; 186 | return result; 187 | } 188 | } 189 | ``` 190 | 191 | poll方法通过调用dequeue方法使最大或最小的节点出队并将其返回。 192 | 193 | 下面来看二叉堆的下沉操作。 194 | 195 | ```java 196 | private static void siftDownComparable(int k, T x, Object[] array, int n) { 197 | if (n > 0) { 198 | Comparable key = (Comparable)x; 199 | int half = n >>> 1; 200 | while (k < half) { 201 | // child为两个子节点(如果有的话)中较小的那个对应的索引 202 | int child = (k << 1) + 1; 203 | Object c = array[child]; 204 | int right = child + 1; 205 | // 通过比较保证child对应的为较小值的索引 206 | if (right < n && 207 | ((Comparable) c).compareTo((T) array[right]) > 0) 208 | c = array[child = right]; 209 | if (key.compareTo((T) c) <= 0) 210 | break; 211 | // 下沉,将较小的子节点换到父节点位置 212 | array[k] = c; 213 | k = child; 214 | } 215 | array[k] = key; 216 | } 217 | } 218 | ``` 219 | 220 | 同上,对下沉操作有疑问的话可参考上述文章。 221 | 222 | #### void put(E e) 223 | 224 | 调用了offer 225 | 226 | ```java 227 | public void put(E e){ 228 | offer(e); 229 | } 230 | ``` 231 | 232 | #### E take() 233 | 234 | take操作的作用是获取二叉堆的根节点元素,如果队列为空则阻塞。 235 | 236 | ```java 237 | public E take() throws InterruptedException { 238 | final ReentrantLock lock = this.lock; 239 | // 阻塞可被中断 240 | lock.lockInterruptibly(); 241 | E result; 242 | try { 243 | // 队列为空就将当前线程放入notEmpty条件队列 244 | // 使用while循环判断是为了避免虚假唤醒 245 | while ( (result = dequeue()) == null) 246 | notEmpty.await(); 247 | } finally { 248 | lock.unlock(); 249 | } 250 | return result; 251 | } 252 | ``` 253 | 254 | ## DelayQueue 255 | 256 | DelayQueue并发队列是一个无界阻塞延迟队列,队列中的每一个元素都有一个过期时间,当从队列中获取元素时只有过期元素才会出列。队列头元素是最快要过期的元素。 257 | 258 | ### 类图结构 259 | 260 | ![](images/11.png) 261 | 262 | DelayQueue内部使用PriorityQueue存放数据,使用ReentrantLock实现线程同步。 263 | 队列里的元素要实现Delayed接口(Delayed接口继承了Comparable接口),用以得到过期时间并进行过期时间的比较。 264 | 265 | ```java 266 | public interface Delayed extends Comparable { 267 | long getDelay(TimeUnit unit); 268 | } 269 | ``` 270 | 271 | available是由lock生成的条件变量,用以实现线程间的同步。 272 | 273 | leader是leader-follower模式的变体,用于减少不必要的线程等待。当一个线程调用队列的take方法变为leader线程后,它会调用条件变量available.waitNanos(delay)等待delay时间,但是其他线程(follower)则会调用available.await()进行无限等待。leader线程延迟时间过期后,会退出take方法,并通过调用available.signal()方法唤醒一个follower线程,被唤醒的线程会被选举为新的leader线程。 274 | 275 | ### 原理讲解 276 | 277 | #### boolean offer(E e) 278 | 279 | ```java 280 | public boolean offer(E e) { 281 | final ReentrantLock lock = this.lock; 282 | lock.lock(); 283 | try { 284 | // 添加新元素 285 | q.offer(e); 286 | // 查看新添加的元素是否为最先过期的 287 | if (q.peek() == e) { 288 | leader = null; 289 | available.signal(); 290 | } 291 | return true; 292 | } finally { 293 | lock.unlock(); 294 | } 295 | } 296 | ``` 297 | 298 | 上述代码首先获取独占锁,然后添加元素到优先级队列,由于q是优先级队列,所以添加元素后,调用q.peek()方法返回的并不一定是当前添加的元素。当如果q.peek() == e,说明当前元素是最先要过期的,那么重置leader线程为null并激活available条件队列里的一个线程,告诉它队列里面有元素了。 299 | 300 | #### E take() 301 | 302 | 获取并移除队列里面过期的元素,如果队列里面没有过期元素则等待。 303 | 304 | ```java 305 | public E take() throws InterruptedException { 306 | final ReentrantLock lock = this.lock; 307 | // 可中断 308 | lock.lockInterruptibly(); 309 | try { 310 | for (;;) { 311 | E first = q.peek(); 312 | // 为空则等待 313 | if (first == null) 314 | available.await(); 315 | else { 316 | long delay = first.getDelay(NANOSECONDS); 317 | // 过期则成功获取 318 | if (delay <= 0) 319 | return q.poll(); 320 | // 执行到此处,说明头元素未过期 321 | first = null; // don't retain ref while waiting 322 | // follower无限等待,直到被唤醒 323 | if (leader != null) 324 | available.await(); 325 | else { 326 | Thread thisThread = Thread.currentThread(); 327 | leader = thisThread; 328 | try { 329 | // leader等待lelay时间,则头元素必定已经过期 330 | available.awaitNanos(delay); 331 | } finally { 332 | // 重置leader,给follower称为leader的机会 333 | if (leader == thisThread) 334 | leader = null; 335 | } 336 | } 337 | } 338 | } 339 | } finally { 340 | if (leader == null && q.peek() != null) 341 | // 唤醒一个follower线程 342 | available.signal(); 343 | lock.unlock(); 344 | } 345 | } 346 | ``` 347 | 348 | 一个线程调用take方法时,会首先查看头元素是否为空,为空则直接等待,否则判断是否过期。 349 | 若头元素已经过期,则直接通过poll获取并移除,否则判断是否有leader线程。 350 | 若有leader线程则一直等待,否则自己成为leader并等待头元素过期。 351 | 352 | #### E poll() 353 | 354 | 获取并移除头过期元素,如果没有过期元素则返回null。 355 | 356 | ```java 357 | public E poll() { 358 | final ReentrantLock lock = this.lock; 359 | lock.lock(); 360 | try { 361 | E first = q.peek(); 362 | // 若队列为空或没有元素过期则直接返回null 363 | if (first == null || first.getDelay(NANOSECONDS) > 0) 364 | return null; 365 | else 366 | return q.poll(); 367 | } finally { 368 | lock.unlock(); 369 | } 370 | } 371 | ``` 372 | 373 | #### int size() 374 | 375 | 计算队列元素个数,包含过期的和未过期的。 376 | 377 | ```java 378 | public int size() { 379 | final ReentrantLock lock = this.lock; 380 | lock.lock(); 381 | try { 382 | return q.size(); 383 | } finally { 384 | lock.unlock(); 385 | } 386 | } 387 | ``` 388 | 389 | ## 更多 390 | 391 | 相关笔记:[《Java并发编程之美》阅读笔记](/README.md) 392 | -------------------------------------------------------------------------------- /08Java并发包中线程池ThreadPoolExecutor原理探究.md: -------------------------------------------------------------------------------- 1 | # 第8章 Java并发包中线程池ThreadPoolExecutor原理探究 2 | 3 | ## 目录 4 | 5 | - [类图结构](#类图结构) 6 | - [源码分析](#源码分析) 7 | - [void execute(Runnable command)](#void-executerunnable-command) 8 | - [Worker的执行](#worker的执行) 9 | - [void shutdown()](#void-shutdown) 10 | - [List<Runnable> shutdownNow()](#listltrunnablegt-shutdownnow) 11 | - [boolean awaitTermination(long timeout, TimeUnit unit)](#boolean-awaitterminationlong-timeout-timeunit-unit) 12 | - [更多](#更多) 13 | 14 | ## 类图结构 15 | 16 | ![](images/12.png) 17 | 18 | 如图所示,Executors是个工具类,用来提供不同特性的线程池。ThreadPoolExecutor中的ctl是一个原子变量,用来记录线程池状态和线程池中的线程个数,类似于ReentrantReadWriteLock中使用一个变量来保存两种信息。 19 | 20 | 以下为与ctl相关的变量与函数: 21 | 22 | ```java 23 | private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); 24 | // 假设Integer为32位(不同平台下可能不同),则前3位用来表示线程运行状态, 25 | // 后29位用来表示线程个数 26 | private static final int COUNT_BITS = Integer.SIZE - 3; 27 | // 00011111111111111111111111111111 28 | private static final int CAPACITY = (1 << COUNT_BITS) - 1; 29 | 30 | // 11100000000000000000000000000000 31 | private static final int RUNNING = -1 << COUNT_BITS; 32 | // 00000000000000000000000000000000 33 | private static final int SHUTDOWN = 0 << COUNT_BITS; 34 | // 00100000000000000000000000000000 35 | private static final int STOP = 1 << COUNT_BITS; 36 | // 01000000000000000000000000000000 37 | private static final int TIDYING = 2 << COUNT_BITS; 38 | // 01100000000000000000000000000000 39 | private static final int TERMINATED = 3 << COUNT_BITS; 40 | 41 | // 取高3位的值 42 | private static int runStateOf(int c) { return c & ~CAPACITY; } 43 | // 低29位的值 44 | private static int workerCountOf(int c) { return c & CAPACITY; } 45 | // 通过指定的rs(Running State)和wc(Workers Count)生成新的ctl状态值 46 | private static int ctlOf(int rs, int wc) { return rs | wc; } 47 | ``` 48 | 49 | 线程池的状态含义如下: 50 | 51 | - RUNNING:接受新任务并处理阻塞队列里的任务。 52 | - SHUTDOWN:拒绝新任务但是处理阻塞队列里面的任务。 53 | - STOP:拒绝新任务并且抛弃阻塞队列里的任务,同时会中断正在处理的任务。 54 | - TIDYING:所有任务都执行完后当前线程池活动线程数为0,将要调用terminated方法(相当于一个过渡状态)。 55 | - TERMINATED: 终止状态,terminated方法调用完成后的状态。 56 | 57 | 线程池参数如下: 58 | 59 | - corePoolSize:核心线程池,通常情况下最多添加corePoolSize个Worker,当任务过多时(阻塞队列满了),会继续添加Worker直到Worker数达到maximumPoolSize 60 | - workQueue:用于保存等待执行的任务的阻塞队列。 61 | - maximumPoolSize:线程池最大线程数量(能添加的Worker的最大数量) 62 | - ThreadFactory:创建线程的工厂 63 | - RejectedExecutionHandler:饱和策略,当队列满并且线程个数达到maximumPoolSize后采取的策略。 64 | - keepAliveTime: 存活时间。如果当前线程池中的线程数量比核心线程数量多,并且是闲置状态,则为这些闲置的线程能存活的最大时间。 65 | 66 | mainLock是独占锁,用来控制新增Worker线程操作的原子性。termination是该锁对应的条件队列。 67 | 68 | Worker继承AQS并实现了Runnable接口,是具体承载任务的而对象。Worker继承了AQS,自己实现了简单不可重入独占锁,其中state=0表示锁未被获取,state=1表示锁已经被获取,state=-1是常见Worker的默认状态,是为了避免该线程在运行runWorker方法前被中断。 69 | 70 | 以下是对Executors中创建线程池的方法的介绍。 71 | 72 | - newFixedThreadPool 73 | 74 | ```java 75 | public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) { 76 | return new ThreadPoolExecutor(nThreads, nThreads, 77 | 0L, TimeUnit.MILLISECONDS, 78 | new LinkedBlockingQueue(), 79 | threadFactory); 80 | } 81 | 82 | ``` 83 | 84 | 创建一个核心线程数和做大线程数都是nThreads的线程池,并且阻塞队列长度为Integer.MAX_VALUE。keepAliveTime=0说明只要线程个数比核心线程个数多并且当前空闲则回收。 85 | 86 | - newSingleThreadExecutor 87 | 88 | ```java 89 | public static ExecutorService newSingleThreadExecutor() { 90 | return new FinalizableDelegatedExecutorService 91 | (new ThreadPoolExecutor(1, 1, 92 | 0L, TimeUnit.MILLISECONDS, 93 | new LinkedBlockingQueue())); 94 | } 95 | ``` 96 | 97 | 创建一个核心线程个数和最大线程个数都是1的线程池。 98 | 99 | - newCachedThreadPool 100 | 101 | ```java 102 | public static ExecutorService newCachedThreadPool() { 103 | return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 104 | 60L, TimeUnit.SECONDS, 105 | new SynchronousQueue()); 106 | } 107 | ``` 108 | 创建一个按需创建线程的线程池,初始线程个数为0,最多线程个数为Integer.MAX_VALUE。KeepAliveTime=60说明只要当前线程在60s内空闲就会被回收。这个类型的特殊之处在于,加入同步队列的任务会被马上执行,同步队列里面最多只有一个任务。 109 | 110 | ## 源码分析 111 | 112 | ThreadPoolExecutor的实现实际是一个生产-消费模型,当用户添加任务到线程池时相当于生产者生产元素,workers中的线程直接执行任务或者从任务队列里面获取任务(当没有空闲的Worker时,任务会被暂存于任务队列中)时相当于消费者消费元素。 113 | 114 | ### void execute(Runnable command) 115 | 116 | ```java 117 | // 执行任务 118 | public void execute(Runnable command) { 119 | if (command == null) 120 | throw new NullPointerException(); 121 | // 获取线程池状态 122 | int c = ctl.get(); 123 | // 如果Worker个数小于核心线程数则新增一个Worker 124 | if (workerCountOf(c) < corePoolSize) { 125 | // 添加Worker,第二个参数为true表示新增Worker为核心线程 126 | if (addWorker(command, true)) 127 | return; 128 | // 重新获取ctl,多线程下ctl变化比较频繁,要确保所获取的状态是最新的 129 | c = ctl.get(); 130 | } 131 | // 线程池关闭后没有接受任务的必要 132 | // 如果线程池还在运行,尝试将任务加入工作队列 133 | if (isRunning(c) && workQueue.offer(command)) { 134 | int recheck = ctl.get(); 135 | // 可能任务入队后线程池又关闭了,则直接移除该任务 136 | if (! isRunning(recheck) && remove(command)) 137 | reject(command); 138 | // 在该任务成功入队前,可能所有Worker都因为keepAliveTime到达而被回收, 139 | // 这时需要重新创建一个Worker来处理任务队列里面的任务 140 | else if (workerCountOf(recheck) == 0) 141 | addWorker(null, false); 142 | } 143 | // 如果任务队列满了,则尝试增加一个非核心线程来处理任务, 144 | // 失败则执行拒绝策略 145 | else if (!addWorker(command, false)) 146 | reject(command); 147 | } 148 | 149 | // 添加一个Worker 150 | private boolean addWorker(Runnable firstTask, boolean core) { 151 | 152 | // 此循环用于增加Worker个数 153 | retry: 154 | for (;;) { 155 | int c = ctl.get(); 156 | int rs = runStateOf(c); 157 | 158 | // 当线程池状态为SHUTDOWN、STOP、TIDYING或TERMINATED时将不再增加Worker来处理任务, 159 | // 但要排除线程池状态刚转为SHUTDOWN且 160 | // ((设置了Worker过期时间且所有Worker均被回收)或(未设置Worker过期时间且Worker个数小于corePoolSize)) 161 | // 但任务队列还有任务的情况。 162 | // 因为由SHUTDOWN状态的定义可知线程池会拒绝新任务但会处理任务队列里面剩余任务。 163 | // firstTask==null表示此次调用addWorker方法并不是要直接给新创建的Worker分配一个任务, 164 | // 而是要让它从任务队列中取尝试获取一个任务。 165 | // 在所有Worker都被回收且任务队列非空的情况下, 166 | // 自然要新增Worker来处理任务队列中剩余的任务; 167 | // 在未设置Worker过期时间且Worker数小于corePoolSize的情况下, 168 | // 仍需要添加一个Worker来提高处理剩余任务的效率。 169 | if (rs >= SHUTDOWN && 170 | ! (rs == SHUTDOWN && 171 | firstTask == null && 172 | ! workQueue.isEmpty())) 173 | return false; 174 | 175 | for (;;) { 176 | int wc = workerCountOf(c); 177 | // Worker数量检测 178 | if (wc >= CAPACITY || 179 | wc >= (core ? corePoolSize : maximumPoolSize)) 180 | return false; 181 | // 成功增加了Worker个数,直接跳出外层for循环执行实际添加Worker的代码 182 | if (compareAndIncrementWorkerCount(c)) 183 | break retry; 184 | c = ctl.get(); 185 | // 状态改变则跳出内层循环,再次执行外循环进行新的状态判断 186 | // 否则继续在内层循环自旋直到CAS操作成功 187 | if (runStateOf(c) != rs) 188 | continue retry; 189 | } 190 | } 191 | 192 | // 执行到此处说明已通过CAS操作成功增减了Worker个数 193 | // 以下代码用于实际增加Worker 194 | boolean workerStarted = false; 195 | boolean workerAdded = false; 196 | Worker w = null; 197 | try { 198 | w = new Worker(firstTask); 199 | final Thread t = w.thread; 200 | if (t != null) { 201 | final ReentrantLock mainLock = this.mainLock; 202 | // 加独占锁是为了实现workers同步,因为可能多个线程调用了线程池的execute方法 203 | mainLock.lock(); 204 | try { 205 | // 重新获取线程池状态,因为有可能在获取锁之前执行了shutdown操作 206 | int rs = runStateOf(ctl.get()); 207 | // 如果线程池还在运行或(线程池处于SHUTDOWN状态并且firstTast为null),执行添加Worker操作 208 | if (rs < SHUTDOWN || 209 | (rs == SHUTDOWN && firstTask == null)) { 210 | if (t.isAlive()) 211 | throw new IllegalThreadStateException(); 212 | // 将新创建的Worker添加到workers队列 213 | workers.add(w); 214 | int s = workers.size(); 215 | // 更新线程池工作线程最大数量 216 | if (s > largestPoolSize) 217 | largestPoolSize = s; 218 | workerAdded = true; 219 | } 220 | } finally { 221 | mainLock.unlock(); 222 | } 223 | if (workerAdded) { 224 | // 添加成功则启动工作线程 225 | t.start(); 226 | workerStarted = true; 227 | } 228 | } 229 | } finally { 230 | if (! workerStarted) 231 | addWorkerFailed(w); 232 | } 233 | return workerStarted; 234 | } 235 | ``` 236 | 237 | ### Worker的执行 238 | 239 | 任务提交到线程池后由Worker来执行。 240 | 241 | ```java 242 | Worker(Runnable firstTask) { 243 | // 调用runWorker前禁止中断 244 | setState(-1); 245 | this.firstTask = firstTask; 246 | this.thread = getThreadFactory().newThread(this); 247 | } 248 | 249 | final void runWorker(Worker w) { 250 | Thread wt = Thread.currentThread(); 251 | Runnable task = w.firstTask; 252 | w.firstTask = null; 253 | w.unlock(); // 将state置为0,允许中断 254 | boolean completedAbruptly = true; 255 | try { 256 | // 执行传入的任务或任务队列中的任务 257 | // getTask用于从任务队列中获取任务,可能会被阻塞 258 | while (task != null || (task = getTask()) != null) { 259 | w.lock(); 260 | ... 261 | try { 262 | // 空方法,用于子类继承重写 263 | beforeExecute(wt, task); 264 | Throwable thrown = null; 265 | try { 266 | // 执行任务 267 | task.run(); 268 | } catch (RuntimeException x) { 269 | thrown = x; throw x; 270 | } catch (Error x) { 271 | thrown = x; throw x; 272 | } catch (Throwable x) { 273 | thrown = x; throw new Error(x); 274 | } finally { 275 | // 空方法,用于子类继承重写 276 | afterExecute(task, thrown); 277 | } 278 | } finally { 279 | task = null; 280 | // 添加任务完成数量 281 | w.completedTasks++; 282 | w.unlock(); 283 | } 284 | } 285 | completedAbruptly = false; 286 | } finally { 287 | // Worker被回收前执行清理工作 288 | processWorkerExit(w, completedAbruptly); 289 | } 290 | } 291 | ``` 292 | 293 | 在构造函数中设置Worker的状态为-1是为了避免当前Worker在调用runWorker方法前被中断(当其他线程调用了shutdownNow方法,如果Worker状态>=0则会中断该线程)。 294 | 295 | runWorker中调用unlock方法时将state置为0,使Worker线程可被中断。 296 | 297 | processWorkerExit方法如下。 298 | 299 | ```java 300 | private void processWorkerExit(Worker w, boolean completedAbruptly) { 301 | // 如果runWorker方法非正常退出,则将workerCount递减 302 | if (completedAbruptly) 303 | decrementWorkerCount(); 304 | 305 | final ReentrantLock mainLock = this.mainLock; 306 | mainLock.lock(); 307 | try { 308 | // 记录任务完成个数 309 | completedTaskCount += w.completedTasks; 310 | workers.remove(w); 311 | } finally { 312 | mainLock.unlock(); 313 | } 314 | 315 | // 尝试设置线程池状态为TERMINATED,如果当前是SHUTDOWN状态并且任务队列为空 316 | // 或当前是STOP状态,当前线程池里没有活动线程 317 | tryTerminate(); 318 | 319 | int c = ctl.get(); 320 | if (runStateLessThan(c, STOP)) { 321 | if (!completedAbruptly) { 322 | 323 | // 在设置了Worker过期时间的情况下,如果任务队列为空,不必新增Worker, 324 | // 如果不为空,当存在Worker时不必新增Worker。 325 | // 在没有设置过期时间的情况下,仅当线程个数小于核心线程数时增加Worker。 326 | // 由此可知,在不主动关闭线程池的情况下, 327 | // 将会一直有Worker存在来接受任务。 328 | int min = allowCoreThreadTimeOut ? 0 : corePoolSize; 329 | if (min == 0 && ! workQueue.isEmpty()) 330 | min = 1; 331 | if (workerCountOf(c) >= min) 332 | return; // 将不执行addWorker操作 333 | } 334 | addWorker(null, false); 335 | } 336 | } 337 | ``` 338 | 339 | ### void shutdown() 340 | 341 | 调用shutdown后,线程池将不再接受新任务,但任务队列中的任务还是要执行的。 342 | 343 | ```java 344 | public void shutdown() { 345 | final ReentrantLock mainLock = this.mainLock; 346 | mainLock.lock(); 347 | try { 348 | // 检查是否有关闭线程池的权限 349 | checkShutdownAccess(); 350 | // 设置当前线程池状态为SHUTDOWN,如果已经是SHUTDOWN则直接返回 351 | advanceRunState(SHUTDOWN); 352 | // 中断空闲的Worker 353 | interruptIdleWorkers(); 354 | onShutdown(); // hook for ScheduledThreadPoolExecutor 355 | } finally { 356 | mainLock.unlock(); 357 | } 358 | // 尝试将状态转为TERMINATED 359 | tryTerminate(); 360 | } 361 | 362 | private static final RuntimePermission shutdownPerm = new RuntimePermission("modifyThread"); 363 | 364 | /** 365 | * 检查是否设置了安全管理器,是则看当前调用shutdown命令的线程是否具有关闭线程的权限, 366 | * 如果有还要看调用线程是否有中断工作线程的权限, 367 | * 如果没有权限则抛出异常 368 | */ 369 | private void checkShutdownAccess() { 370 | SecurityManager security = System.getSecurityManager(); 371 | if (security != null) { 372 | security.checkPermission(shutdownPerm); 373 | final ReentrantLock mainLock = this.mainLock; 374 | mainLock.lock(); 375 | try { 376 | for (Worker w : workers) 377 | security.checkAccess(w.thread); 378 | } finally { 379 | mainLock.unlock(); 380 | } 381 | } 382 | } 383 | 384 | // ez 385 | private void advanceRunState(int targetState) { 386 | for (;;) { 387 | int c = ctl.get(); 388 | if (runStateAtLeast(c, targetState) || 389 | ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c)))) 390 | break; 391 | } 392 | } 393 | 394 | // 设置所有空闲线程的中断标志 395 | private void interruptIdleWorkers() { 396 | interruptIdleWorkers(false); 397 | } 398 | 399 | private void interruptIdleWorkers(boolean onlyOne) { 400 | final ReentrantLock mainLock = this.mainLock; 401 | mainLock.lock(); 402 | try { 403 | for (Worker w : workers) { 404 | Thread t = w.thread; 405 | // 只中断那些还没被中断的 406 | // 获取w的锁成功说明w在执行runWorker方法调用getTask时被阻塞, 407 | // 也就是说w是空闲的,那就中断它 408 | if (!t.isInterrupted() && w.tryLock()) { 409 | try { 410 | t.interrupt(); 411 | } catch (SecurityException ignore) { 412 | } finally { 413 | w.unlock(); 414 | } 415 | } 416 | // 如果只中断一个则退出循环 417 | if (onlyOne) 418 | break; 419 | } 420 | } finally { 421 | mainLock.unlock(); 422 | } 423 | } 424 | 425 | final void tryTerminate() { 426 | for (;;) { 427 | int c = ctl.get(); 428 | // 判断是否满足可终止条件 429 | // 线程池处于RUNNING状态 430 | // 或处于TIDYING状态(说明有其他线程调用了tryTerminate方法且即将成功终止线程池) 431 | // 或线程池正处于SHUTDOWN状态且任务队列不为空时不可终止 432 | if (isRunning(c) || 433 | runStateAtLeast(c, TIDYING) || 434 | (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty())) 435 | return; 436 | // 还有Worker的话,中断一个空闲Worker后返回 437 | // 正在执行任务的Worker会在执行完任务后调用tryTerminate方法 438 | if (workerCountOf(c) != 0) { 439 | interruptIdleWorkers(ONLY_ONE); 440 | return; 441 | } 442 | 443 | final ReentrantLock mainLock = this.mainLock; 444 | mainLock.lock(); 445 | try { 446 | // 设置线程池状态为TIDYING 447 | if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) { 448 | try { 449 | // 空方法,由子类继承重写,进行线程池关闭时的清理工作 450 | terminated(); 451 | } finally { 452 | // 此处无需使用CAS,因为即使CAS失败也说明线程池终止了 453 | ctl.set(ctlOf(TERMINATED, 0)); 454 | // 激活因调用条件变量termination的await系列方法而被阻塞的所有线程 455 | termination.signalAll(); 456 | } 457 | return; 458 | } 459 | } finally { 460 | mainLock.unlock(); 461 | } 462 | // else retry on failed CAS 463 | } 464 | } 465 | ``` 466 | 467 | ### List<Runnable> shutdownNow() 468 | 469 | 调用shutdownNow后,线程池将不会再接受新任务,并且会丢弃任务队列里面的任务且中断正在执行的任务,然后立刻返回任务队列里面的任务列表。 470 | 471 | ```java 472 | public List shutdownNow() { 473 | List tasks; 474 | final ReentrantLock mainLock = this.mainLock; 475 | mainLock.lock(); 476 | try { 477 | checkShutdownAccess(); 478 | advanceRunState(STOP); 479 | // 不是interruptIdleWorkers() 480 | // 中断所有在运行的Worker 481 | interruptWorkers(); 482 | // 将任务队列中的任务移动到tasks中 483 | tasks = drainQueue(); 484 | } finally { 485 | mainLock.unlock(); 486 | } 487 | tryTerminate(); 488 | return tasks; 489 | } 490 | 491 | // 中断所有在运行的Worker 492 | private void interruptWorkers() { 493 | final ReentrantLock mainLock = this.mainLock; 494 | mainLock.lock(); 495 | try { 496 | for (Worker w : workers) 497 | w.interruptIfStarted(); 498 | } finally { 499 | mainLock.unlock(); 500 | } 501 | } 502 | ``` 503 | 504 | ### boolean awaitTermination(long timeout, TimeUnit unit) 505 | 506 | 当线程调用awaitTermination后,当前线程会被阻塞,直到线程池状态变成TERMINATIED或等待超时才返回。 507 | 508 | ```java 509 | public boolean awaitTermination(long timeout, TimeUnit unit) 510 | throws InterruptedException { 511 | long nanos = unit.toNanos(timeout); 512 | final ReentrantLock mainLock = this.mainLock; 513 | mainLock.lock(); 514 | try { 515 | for (;;) { 516 | // 如果线程池已经终止,则直接返回 517 | if (runStateAtLeast(ctl.get(), TERMINATED)) 518 | return true; 519 | if (nanos <= 0) 520 | return false; 521 | // 等待相应时间,线程池成功关闭后会调用termination.signalAll()将当前线程激活 522 | nanos = termination.awaitNanos(nanos); 523 | } 524 | } finally { 525 | mainLock.unlock(); 526 | } 527 | } 528 | ``` 529 | 530 | ## 更多 531 | 532 | 相关笔记:[《Java并发编程之美》阅读笔记](/README.md) 533 | -------------------------------------------------------------------------------- /09Java并发包中ScheduledThreadPoolExecutor原理探究.md: -------------------------------------------------------------------------------- 1 | # 第9章 Java并发包中ScheduledThreadPoolExecutor原理探究 2 | 3 | ## 目录 4 | 5 | - [类图结构](#类图结构) 6 | - [源码分析](#源码分析) 7 | - [schedule(Runnable command, long delay, TimeUnit unit)](#schedulerunnable-command-long-delay-timeunit-unit) 8 | - [scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)](#schedulewithfixeddelayrunnable-command long-initialdelay long-delay timeunit-unit) 9 | - [scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)](#scheduleatfixedraterunnable-command-long-initialdelay-long-period-timeunit-unit) 10 | - [更多](#更多) 11 | 12 | ## 类图结构 13 | 14 | ScheduledThreadPoolExecutor时一个可以在指定一定延迟时间后或者定时进行任务调度执行的线程池。 15 | 16 | ![](images/13.png) 17 | 18 | ScheduledThreadPoolExecutor继承了ThreadPoolExecutor并实现了ScheduledExecutorService接口。 19 | 20 | 线程池队列是DelayedWorkQueue,与DelayedQueue一样属于延迟队列。 21 | 22 | ScheduledFuturetask是具有返回值的任务,继承自FutureTask。FutureTask内部用一个变量state来表示任务的状态,一开始为NEW。 23 | 24 | 各状态意义如下: 25 | 26 | private static final int NEW = 0; // 初始状态 27 | private static final int COMPLETING = 1; // 执行中 28 | private static final int NORMAL = 2; // 正常运行结束 29 | private static final int EXCEPTIONAL = 3; // 运行中异常 30 | private static final int CANCELLED = 4; // 任务被取消 31 | private static final int INTERRUPTING = 5; // 任务正在被中断 32 | private static final int INTERRUPTED = 6; // 任务已经被中断 33 | 34 | ScheduledFutureTask内部用一个变量period来表示任务的类型: 35 | 36 | - period=0,说明当前任务是一次性的,执行完毕后就推出了。 37 | - period为负数,说明当前任务为固定延迟的定时可重复执行任务(执行完一次后会停止指定时间后再次运行,若每次执行任务耗时不同,则显然相邻两次任务执行间隔不同)。 38 | - period为正数,说明当前任务为固定频率的定尺可重复执行任务(也即固定周期)。 39 | 40 | 以下为ScheduledThreadPoolExecutor的构造函数: 41 | 42 | ```java 43 | public ScheduledThreadPoolExecutor(int corePoolSize) { 44 | super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, 45 | new DelayedWorkQueue()); 46 | } 47 | 48 | // 指定了线程工厂 49 | public ScheduledThreadPoolExecutor(int corePoolSize, 50 | ThreadFactory threadFactory) { 51 | super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, 52 | new DelayedWorkQueue(), threadFactory); 53 | } 54 | 55 | // 指定了拒绝策略 56 | public ScheduledThreadPoolExecutor(int corePoolSize, 57 | RejectedExecutionHandler handler) { 58 | super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, 59 | new DelayedWorkQueue(), handler); 60 | } 61 | 62 | // 指定了线程工厂和拒绝策略 63 | public ScheduledThreadPoolExecutor(int corePoolSize, 64 | ThreadFactory threadFactory, 65 | RejectedExecutionHandler handler) { 66 | super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, 67 | new DelayedWorkQueue(), threadFactory, handler); 68 | } 69 | ``` 70 | 71 | 从上面的代码中可以看到,ScheduledThreadPoolExecutor的线程池队列为DelayedWorkQueue。 72 | 73 | ## 源码分析 74 | 75 | ### schedule(Runnable command, long delay, TimeUnit unit) 76 | 77 | 提交一个延迟执行的任务,任务从提交时间算起延迟单位为unit的delay后开始执行。提交的任务不是周期性任务,任务只会执行一次。 78 | 79 | ```java 80 | public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { 81 | // 参数校验 82 | if (command == null || unit == null) 83 | throw new NullPointerException(); 84 | // 将任务包装成ScheduledFutureTask 85 | // triggerTime方法用来计算触发时间(即任务开始执行的绝对时间) 86 | RunnableScheduledFuture t = decorateTask(command, 87 | new ScheduledFutureTask(command, null, triggerTime(delay, unit))); 88 | // 添加任务到延迟队列 89 | delayedExecute(t); 90 | return t; 91 | } 92 | ``` 93 | 94 | 以下是ScheduledFutureTask的相关代码: 95 | 96 | ```java 97 | ScheduledFutureTask(Runnable r, V result, long ns) { 98 | // 调用父类构造函数 99 | super(r, result); 100 | this.time = ns; // 等待ns纳秒后开始执行 101 | this.period = 0; // period=0说明为一次性任务 102 | // 记录任务编号 103 | this.sequenceNumber = sequencer.getAndIncrement(); 104 | } 105 | 106 | public FutureTask(Runnable runnable, V result) { 107 | // 将Runnable任务转化成Callable任务 108 | this.callable = Executors.callable(runnable, result); 109 | this.state = NEW; // ensure visibility of callable 110 | } 111 | ``` 112 | 113 | delayedExecute的代码如下: 114 | 115 | ```java 116 | private void delayedExecute(RunnableScheduledFuture task) { 117 | // 线程池关闭则执行拒绝策略 118 | if (isShutdown()) 119 | reject(task); 120 | else { 121 | // 将任务添加到任务队列中 122 | // 任务队列为DelayedWorkQueue 123 | // 所加的task实现了comparable接口 124 | // 添加到任务队列中能保证队首元素为最早需要执行的 125 | super.getQueue().add(task); 126 | // 再次检查线程池是否关闭 127 | // 因为执行上面add代码过程中线程池完全有可能被关闭 128 | // 如果线程池被关闭则判断当前任务是否可以在当前状态下继续执行 129 | // 不能继续执行则移除当前任务 130 | if (isShutdown() && 131 | !canRunInCurrentRunState(task.isPeriodic()) && 132 | remove(task)) 133 | task.cancel(false); 134 | else 135 | // 保证至少有一个线程存活可以从任务队列中获取任务处理任务 136 | ensurePrestart(); 137 | } 138 | } 139 | 140 | // 判断任务是否是周期执行的 141 | public boolean isPeriodic() { 142 | return period != 0; 143 | } 144 | 145 | // 根据periodic来决定isRunningOrShutdown的参数 146 | // continueExistingPeriodicTasksAfterShutdown和 147 | // executeExistingDelayedTasksAfterShutdown的值可通过相应的setter方法来设置 148 | // 为true表示线程池关闭后当前任务会继续执行完毕 149 | // 为false则取消当前任务 150 | boolean canRunInCurrentRunState(boolean periodic) { 151 | return isRunningOrShutdown(periodic ? 152 | continueExistingPeriodicTasksAfterShutdown : 153 | executeExistingDelayedTasksAfterShutdown); 154 | } 155 | 156 | // 确保至少有一个线程存活来执行任务 157 | void ensurePrestart() { 158 | int wc = workerCountOf(ctl.get()); 159 | if (wc < corePoolSize) 160 | addWorker(null, true); 161 | else if (wc == 0) 162 | addWorker(null, false); 163 | } 164 | ``` 165 | 166 | 线程池中具体执行任务的是Worker,Worker通过调用run方法来执行。这里的任务是ScheduledFutureTask,下面来看其run方法。 167 | 168 | ```java 169 | // ScheduledFutureTask.run 170 | public void run() { 171 | boolean periodic = isPeriodic(); 172 | // 判断是否需要取消任务 173 | if (!canRunInCurrentRunState(periodic)) 174 | cancel(false); 175 | // 一次性任务 176 | else if (!periodic) 177 | // 调用FutureTask的run方法 178 | ScheduledFutureTask.super.run(); 179 | // 周期性任务 180 | // runAndReset为FutureTask中的方法 181 | // 用于执行当前任务但不改变future的状态 182 | else if (ScheduledFutureTask.super.runAndReset()) { 183 | // 设置下次执行的时间 184 | setNextRunTime(); 185 | // 默认情况下,outerTask = this就是当前对象 186 | reExecutePeriodic(outerTask); 187 | } 188 | } 189 | 190 | // 设置周期性任务下次执行时间 191 | private void setNextRunTime() { 192 | long p = period; 193 | // p > 0表示任务执行频率一定 194 | if (p > 0) 195 | // time为此次任务(已执行完毕)刚开始执行时的时间 196 | time += p; 197 | // p < 0表示任务固定延迟时间 198 | // 即此次任务完成后会等待-p时间再执行下次任务 199 | else 200 | // 获取-p时间后的绝对时间 201 | time = triggerTime(-p); 202 | } 203 | 204 | // 周期性执行 205 | void reExecutePeriodic(RunnableScheduledFuture task) { 206 | if (canRunInCurrentRunState(true)) { 207 | // 再次将task添加至任务队列中等待执行 208 | // 当轮到task执行时,又会在run中调用此方法 209 | // 再次将自身添加到任务队列中,从而达到周期性执行效果 210 | super.getQueue().add(task); 211 | if (!canRunInCurrentRunState(true) && remove(task)) 212 | task.cancel(false); 213 | else 214 | ensurePrestart(); 215 | } 216 | } 217 | ``` 218 | 219 | ### scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) 220 | 221 | 当任务执行完毕后,让其延迟固定时间后再次运行。 222 | 223 | ```java 224 | // command:所要执行的任务 225 | // initialDelay: 提交任务后等待多少时间再开始执行任务 226 | // delay: 一次任务执行完后等待多少时间才执行下次任务 227 | public ScheduledFuture scheduleWithFixedDelay(Runnable command, 228 | long initialDelay, 229 | long delay, 230 | TimeUnit unit) { 231 | // 参数校验 232 | if (command == null || unit == null) 233 | throw new NullPointerException(); 234 | if (delay <= 0) 235 | throw new IllegalArgumentException(); 236 | // 将Runnable任务转换成ScheduledFutureTask 237 | // 第四个参数period为-delay < 0表示当前任务是固定延迟的 238 | ScheduledFutureTask sft = 239 | new ScheduledFutureTask(command, 240 | null, 241 | triggerTime(initialDelay, unit), 242 | unit.toNanos(-delay)); 243 | // decorateTask默认直接返回第二个参数,即sft 244 | RunnableScheduledFuture t = decorateTask(command, sft); 245 | // 默认情况下t = sft 246 | // outerTask即为sft自身,用于实现周期性执行 247 | sft.outerTask = t; 248 | delayedExecute(t); 249 | return t; 250 | } 251 | 252 | // 可被子类重写拓展 253 | // 默认实现只是简单的返回task 254 | protected RunnableScheduledFuture decorateTask( 255 | Runnable runnable, RunnableScheduledFuture task) { 256 | return task; 257 | } 258 | ``` 259 | 260 | 主要不同在于设置了period=-delay,其他代码与schedule相同,相应的代码会判断period的取值从而决定程序不同的行为。 261 | 262 | ### scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) 263 | 264 | 按固定频率周期性地执行任务。 265 | 266 | ```java 267 | public ScheduledFuture scheduleAtFixedRate(Runnable command, 268 | long initialDelay, 269 | long period, 270 | TimeUnit unit) { 271 | if (command == null || unit == null) 272 | throw new NullPointerException(); 273 | if (period <= 0) 274 | throw new IllegalArgumentException(); 275 | // 关键在于此处第四个参数period > 0 276 | ScheduledFutureTask sft = 277 | new ScheduledFutureTask(command, 278 | null, 279 | triggerTime(initialDelay, unit), 280 | unit.toNanos(period)); 281 | RunnableScheduledFuture t = decorateTask(command, sft); 282 | sft.outerTask = t; 283 | delayedExecute(t); 284 | return t; 285 | } 286 | ``` 287 | 288 | 除了设置period的值大于0外,总体与scheduleWithFixedDelay类似,不再赘述。 289 | 290 | ## 更多 291 | 292 | 相关笔记:[《Java并发编程之美》阅读笔记](/README.md) -------------------------------------------------------------------------------- /10Java并发包中线程同步器原理剖析.md: -------------------------------------------------------------------------------- 1 | # 第10章 Java并发包中线程同步器原理剖析 2 | 3 | ## 目录 4 | 5 | - [CountDownLatch原理剖析](#countdownlatch原理剖析) 6 | - [示例](#示例) 7 | - [类图结构](#类图结构) 8 | - [源码解析](#源码解析) 9 | - [void await()](#void-await) 10 | - [boolean await(long timeout, TimeUnit unit)](#boolean-awaitlong-timeout-timeunit-unit) 11 | - [void countDown()](#void-countdown) 12 | - [CyclicBarrier原理探究](#cyclicbarrier原理探究) 13 | - [示例](#示例-1) 14 | - [类图结构](#类图结构-1) 15 | - [源码分析](#源码分析) 16 | - [int await()](#int-await) 17 | - [boolean await(long timeout, TimeUnit unit)](#boolean-awaitlong-timeout-timeunit-unit-1) 18 | - [int dowait(boolean timed, long nanos)](#int-dowaitboolean-timed-long-nanos) 19 | - [Semaphore原理探究](#semaphore原理探究) 20 | - [示例](#示例-2) 21 | - [类图结构](#类图结构-2) 22 | - [源码解析](#源码解析-1) 23 | - [void acquire()](#void-acquire) 24 | - [void acquire(int permits)](#void-acquireint-permits) 25 | - [void acquireUninterruptibly()](#void-acquireuninterruptibly) 26 | - [void acquireUninterruptibly(int permits)](#void-acquireuninterruptiblyint-permits) 27 | - [void release()](#void-release) 28 | - [void release(int permits)](#void-releaseint-permits) 29 | - [更多](#更多) 30 | 31 | ## CountDownLatch原理剖析 32 | 33 | 日常开发中经常遇到一个线程需要等待一些线程都结束后才能继续向下运行的场景,在CountDownLatch出现之前通常使用join方法来实现,但join方法不够灵活,所以开发了CountDownLatch。 34 | 35 | ### 示例 36 | 37 | ```java 38 | public static void main(String[] args) throws InterruptedException { 39 | CountDownLatch countDownLatch = new CountDownLatch(2); 40 | 41 | ExecutorService executorService = Executors.newFixedThreadPool(2); 42 | // 添加任务 43 | executorService.execute(new Runnable() { 44 | @Override 45 | public void run() { 46 | try { 47 | // 模拟运行时间 48 | Thread.sleep(1000); 49 | System.out.println("thread one over..."); 50 | } catch (InterruptedException e) { 51 | e.printStackTrace(); 52 | }finally { 53 | // 递减计数器 54 | countDownLatch.countDown(); 55 | } 56 | } 57 | }); 58 | 59 | // 同上 60 | executorService.execute(new Runnable() { 61 | @Override 62 | public void run() { 63 | try { 64 | Thread.sleep(1000); 65 | System.out.println("thread two over..."); 66 | } catch (InterruptedException e) { 67 | e.printStackTrace(); 68 | }finally { 69 | countDownLatch.countDown(); 70 | } 71 | } 72 | }); 73 | 74 | System.out.println("wait all child thread over!"); 75 | // 阻塞直到被interrupt或计数器递减至0 76 | countDownLatch.await(); 77 | System.out.println("all child thread over!"); 78 | executorService.shutdown(); 79 | } 80 | ``` 81 | 82 | 输出为: 83 | 84 | wait all child thread over! 85 | thread one over... 86 | thread two over... 87 | all child thread over! 88 | 89 | CountDownLatch相对于join方法的优点大致有两点: 90 | 91 | - 调用一个子线程的join方法后,该线程会一直阻塞直到子线程运行完毕,而CountDownLatch允许子线程运行完毕或在运行过程中递减计数器,也就是说await方法不一定要等到子线程运行结束才返回。 92 | - 使用线程池来管理线程一般都是直接添加Runnable到线程池,这时就没有办法再调用线程的join方法了,而仍可在子线程中递减计数器,也就是说CountDownLatch相比join方法可以更灵活地控制线程的同步。 93 | 94 | ### 类图结构 95 | 96 | ![](images/14.png) 97 | 98 | 由图可知,CountDownLatch是基于AQS实现的。 99 | 100 | 由下面的代码可知,**CountDownLatch的计数器值就是AQS的state值**。 101 | 102 | ```java 103 | public CountDownLatch(int count) { 104 | if (count < 0) throw new IllegalArgumentException("count < 0"); 105 | this.sync = new Sync(count); 106 | } 107 | Sync(int count) { 108 | setState(count); 109 | } 110 | ``` 111 | 112 | ### 源码解析 113 | 114 | #### void await() 115 | 116 | 当线程调用CountDownLatch的await方法后,当前线程会被阻塞,直到CountDownLatch的计数器值递减至0或者其他线程调用了当前线程的interrupt方法。 117 | 118 | ```java 119 | public void await() throws InterruptedException { 120 | // 允许中断(中断时抛出异常) 121 | sync.acquireSharedInterruptibly(1); 122 | } 123 | 124 | // AQS的方法 125 | public final void acquireSharedInterruptibly(int arg) 126 | throws InterruptedException { 127 | if (Thread.interrupted()) 128 | throw new InterruptedException(); 129 | // state=0时tryAcquireShared方法返回1,直接返回 130 | // 否则执行doAcquireSharedInterruptibly方法 131 | if (tryAcquireShared(arg) < 0) 132 | // state不为0,调用该方法使await方法阻塞 133 | doAcquireSharedInterruptibly(arg); 134 | } 135 | 136 | // Sync的方法(重写了AQS中的该方法) 137 | protected int tryAcquireShared(int acquires) { 138 | return (getState() == 0) ? 1 : -1; 139 | } 140 | 141 | // AQS的方法 142 | private void doAcquireSharedInterruptibly(int arg) 143 | throws InterruptedException { 144 | final Node node = addWaiter(Node.SHARED); 145 | boolean failed = true; 146 | try { 147 | for (;;) { 148 | final Node p = node.predecessor(); 149 | if (p == head) { 150 | // 获取state值,state=0时r=1,直接返回,不再阻塞 151 | int r = tryAcquireShared(arg); 152 | if (r >= 0) { 153 | setHeadAndPropagate(node, r); 154 | p.next = null; // help GC 155 | failed = false; 156 | return; 157 | } 158 | } 159 | // 若state不为0则阻塞调用await方法的线程 160 | // 等到其他线程执行countDown方法使计数器递减至0 161 | // (state变为0)或该线程被interrupt时 162 | // 该线程才能继续向下运行 163 | if (shouldParkAfterFailedAcquire(p, node) && 164 | parkAndCheckInterrupt()) 165 | throw new InterruptedException(); 166 | } 167 | } finally { 168 | if (failed) 169 | cancelAcquire(node); 170 | } 171 | } 172 | ``` 173 | 174 | #### boolean await(long timeout, TimeUnit unit) 175 | 176 | 相较于上面的await方法,调用此方法后调用线程最多被阻塞timeout时间(单位由unit指定),即使计数器没有递减至0或调用线程没有被interrupt,调用线程也会继续向下运行。 177 | 178 | ```java 179 | public boolean await(long timeout, TimeUnit unit) 180 | throws InterruptedException { 181 | return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout)); 182 | } 183 | ``` 184 | 185 | #### void countDown() 186 | 187 | 递减计数器,当计数器的值为0(即state=0)时会唤醒所有因调用await方法而被阻塞的线程。 188 | 189 | ```java 190 | public void countDown() { 191 | // 将计数器减1 192 | sync.releaseShared(1); 193 | } 194 | 195 | // AQS的方法 196 | public final boolean releaseShared(int arg) { 197 | // 当state被递减至0时tryReleaseShared返回true 198 | // 会执行doReleaseShared方法唤醒因调用await方法阻塞的线程 199 | // 否则如果state不是0的话什么也不做 200 | if (tryReleaseShared(arg)) { 201 | doReleaseShared(); 202 | return true; 203 | } 204 | return false; 205 | } 206 | 207 | // Sync重写的AQS中的方法 208 | protected boolean tryReleaseShared(int releases) { 209 | for (;;) { 210 | int c = getState(); 211 | // 如果state已经为0,没有递减必要,直接返回 212 | // 否则会使state变成负数 213 | if (c == 0) 214 | return false; 215 | int nextc = c-1; 216 | // 通过CAS递减state的值 217 | if (compareAndSetState(c, nextc)) 218 | // 如果state被递减至0,返回true以进行后续唤醒工作 219 | return nextc == 0; 220 | } 221 | } 222 | ``` 223 | 224 | ## CyclicBarrier原理探究 225 | 226 | CountDownLatch的计数器时一次性的,也就是说当计数器至变为0后,再调用await和countDown方法会直接返回。而CyclicBarrier则解决了此问题。CyclicBarrier是回环屏障的意思,它可以使一组线程全部达到一个状态后再全部同时执行,然后重置自身状态又可用于下一次的状态同步。 227 | 228 | ### 示例 229 | 230 | 假设一个任务由阶段1、阶段2、阶段3组成,每个线程要串行地执行阶段1、阶段2、阶段3,当多个线程执行该任务时,必须要保证所有线程的阶段1都执行完毕后才能进入阶段2,当所有线程的阶段2都执行完毕后才能进入阶段3,可用下面的代码实现: 231 | 232 | ```java 233 | public static void main(String[] args) { 234 | // 等待两个线程同步 235 | CyclicBarrier cyclicBarrier = new CyclicBarrier(2); 236 | 237 | ExecutorService executorService = Executors.newFixedThreadPool(2); 238 | // 运行两个子线程,当两个子线程的step1都执行完毕后才会执行step2 239 | // 当两个子线程的step2都执行完毕后才会执行step3 240 | for(int i = 0; i < 2; i++) { 241 | executorService.execute(new Runnable() { 242 | @Override 243 | public void run() { 244 | try{ 245 | System.out.println(Thread.currentThread() + " step1"); 246 | cyclicBarrier.await(); 247 | System.out.println(Thread.currentThread() + " step2"); 248 | cyclicBarrier.await(); 249 | System.out.println(Thread.currentThread() + " step3"); 250 | }catch (Exception e){ 251 | e.printStackTrace(); 252 | } 253 | } 254 | }); 255 | } 256 | executorService.shutdown(); 257 | } 258 | ``` 259 | 260 | 输出如下: 261 | 262 | Thread[pool-1-thread-1,5,main] step1 263 | Thread[pool-1-thread-2,5,main] step1 264 | Thread[pool-1-thread-1,5,main] step2 265 | Thread[pool-1-thread-2,5,main] step2 266 | Thread[pool-1-thread-2,5,main] step3 267 | Thread[pool-1-thread-1,5,main] step3 268 | 269 | ### 类图结构 270 | 271 | ![](images/15.png) 272 | 273 | CyclicBarrier基于ReentrantLock实现,本质上还是基于AQS的。parties用于记录线程个数,表示多少个线程调用await方法后,所有线程才会冲破屏障往下运行。count一开始等于parties,当由线程调用await方法时会递减1,当count变成0时到达屏障点,所有调用await的线程会一起往下执行,此时要重置CyclicBarrier,再次令count=parties。 274 | 275 | lock用于保证更新计数器count的原子性。lock的条件变量trip用于支持线程间使用await和signalAll进行通信。 276 | 277 | 以下是CyclicBarrier的构造函数: 278 | 279 | ```java 280 | public CyclicBarrier(int parties) { 281 | this(parties, null); 282 | } 283 | 284 | // barrierAction为达到屏障点(parties个线程调用了await方法)时执行的任务 285 | public CyclicBarrier(int parties, Runnable barrierAction) { 286 | if (parties <= 0) throw new IllegalArgumentException(); 287 | this.parties = parties; 288 | this.count = parties; 289 | this.barrierCommand = barrierAction; 290 | } 291 | ``` 292 | 293 | Generation的定义如下: 294 | 295 | ```java 296 | private static class Generation { 297 | // 记录当前屏障是否可以被打破 298 | boolean broken = false; 299 | } 300 | ``` 301 | 302 | ### 源码分析 303 | 304 | #### int await() 305 | 306 | 当前线程调用该方法时会阻塞,直到满足以下条件之一才会返回: 307 | 308 | - parties个线程调用了await方法,也就是到达屏障点 309 | - 其他线程调用了当前线程的interrupt方法 310 | - Generation对象的broken标志被设置为true,抛出BrokenBarrierExecption 311 | 312 | ```java 313 | public int await() throws InterruptedException, BrokenBarrierException { 314 | try { 315 | // false表示不设置超时时间,此时后面参数无意义 316 | // dowait稍后具体分析 317 | return dowait(false, 0L); 318 | } catch (TimeoutException toe) { 319 | throw new Error(toe); // cannot happen 320 | } 321 | } 322 | ``` 323 | 324 | #### boolean await(long timeout, TimeUnit unit) 325 | 326 | 相比于await(),等待超时会返回false。 327 | 328 | ```java 329 | public int await(long timeout, TimeUnit unit) 330 | throws InterruptedException, 331 | BrokenBarrierException, 332 | TimeoutException { 333 | // 设置了超时时间 334 | // dowait稍后分析 335 | return dowait(true, unit.toNanos(timeout)); 336 | } 337 | ``` 338 | 339 | #### int dowait(boolean timed, long nanos) 340 | 341 | ```java 342 | private int dowait(boolean timed, long nanos) 343 | throws InterruptedException, BrokenBarrierException, 344 | TimeoutException { 345 | final ReentrantLock lock = this.lock; 346 | lock.lock(); 347 | try { 348 | final Generation g = generation; 349 | // 屏障已被打破则抛出异常 350 | if (g.broken) 351 | throw new BrokenBarrierException(); 352 | 353 | // 线程中断则抛出异常 354 | if (Thread.interrupted()) { 355 | // 打破屏障 356 | // 会做三件事 357 | // 1. 设置generation的broken为true 358 | // 2. 重置count为parites 359 | // 3. 调用signalAll激活所有等待线程 360 | breakBarrier(); 361 | throw new InterruptedException(); 362 | } 363 | 364 | int index = --count; 365 | // 到达了屏障点 366 | if (index == 0) { 367 | boolean ranAction = false; 368 | try { 369 | final Runnable command = barrierCommand; 370 | if (command != null) 371 | // 执行每一次到达屏障点所需要执行的任务 372 | command.run(); 373 | ranAction = true; 374 | // 重置状态,进入下一次屏障 375 | nextGeneration(); 376 | return 0; 377 | } finally { 378 | if (!ranAction) 379 | breakBarrier(); 380 | } 381 | } 382 | 383 | // 如果index不为0 384 | // loop until tripped, broken, interrupted, or timed out 385 | for (;;) { 386 | try { 387 | if (!timed) 388 | trip.await(); 389 | else if (nanos > 0L) 390 | nanos = trip.awaitNanos(nanos); 391 | } catch (InterruptedException ie) { 392 | // 执行此处时,有可能其他线程已经调用了nextGeneration方法 393 | // 此时应该使当前线程正常执行下去 394 | // 否则打破屏障 395 | if (g == generation && ! g.broken) { 396 | breakBarrier(); 397 | throw ie; 398 | } else { 399 | // We're about to finish waiting even if we had not 400 | // been interrupted, so this interrupt is deemed to 401 | // "belong" to subsequent execution. 402 | Thread.currentThread().interrupt(); 403 | } 404 | } 405 | 406 | if (g.broken) 407 | throw new BrokenBarrierException(); 408 | // 如果此次屏障已经结束,则正常返回 409 | if (g != generation) 410 | return index; 411 | // 如果是因为超时,则打破屏障并抛出异常 412 | if (timed && nanos <= 0L) { 413 | breakBarrier(); 414 | throw new TimeoutException(); 415 | } 416 | } 417 | } finally { 418 | lock.unlock(); 419 | } 420 | } 421 | 422 | // 打破屏障 423 | private void breakBarrier() { 424 | // 设置打破标志 425 | generation.broken = true; 426 | // 重置count 427 | count = parties; 428 | // 唤醒所有等待的线程 429 | trip.signalAll(); 430 | } 431 | 432 | private void nextGeneration() { 433 | // 唤醒当前屏障下所有被阻塞的线程 434 | trip.signalAll(); 435 | // 重置状态,进入下一次屏障 436 | count = parties; 437 | generation = new Generation(); 438 | } 439 | ``` 440 | 441 | ## Semaphore原理探究 442 | 443 | Semaphore信号量也是一个同步器,与CountDownLatch和CyclicBarrier不同的是,它内部的计数器是递增的,并且在初始化时可以指定计数器的初始值(通常为0),但不必知道需要同步的线程个数,而是在需要同步的地方调用acquire方法时指定需要同步的线程个数。 444 | 445 | ### 示例 446 | 447 | ```java 448 | public static void main(String[] args) throws InterruptedException { 449 | final int THREAD_COUNT = 2; 450 | // 初始信号量为0 451 | Semaphore semaphore = new Semaphore(0); 452 | ExecutorService executorService = Executors.newFixedThreadPool(THREAD_COUNT); 453 | 454 | for (int i = 0; i < THREAD_COUNT; i++){ 455 | executorService.execute(new Runnable() { 456 | @Override 457 | public void run() { 458 | System.out.println(Thread.currentThread() + " over"); 459 | // 信号量+1 460 | semaphore.release(); 461 | } 462 | }); 463 | } 464 | 465 | // 当信号量达到2时才停止阻塞 466 | semaphore.acquire(2); 467 | System.out.println("all child thread over!"); 468 | 469 | executorService.shutdown(); 470 | } 471 | ``` 472 | 473 | ### 类图结构 474 | 475 | ![](images/16.png) 476 | 477 | 由图可知,Semaphore还是使用AQS实现的,并且可以选取公平性策略(默认为非公平性的)。 478 | 479 | ### 源码解析 480 | 481 | #### void acquire() 482 | 483 | 表示当前线程希望获取一个信号量资源,如果当前信号量大于0,则当前信号量的计数减1,然后该方法直接返回。否则如果当前信号量等于0,则被阻塞。 484 | 485 | ```java 486 | public void acquire() throws InterruptedException { 487 | sync.acquireSharedInterruptibly(1); 488 | } 489 | 490 | public final void acquireSharedInterruptibly(int arg) 491 | throws InterruptedException { 492 | // 可以被中断 493 | if (Thread.interrupted()) 494 | throw new InterruptedException(); 495 | // 调用Sync子类方法尝试获取,这里根据构造函数决定公平策略 496 | if (tryAcquireShared(arg) < 0) 497 | // 将当前线程放入阻塞队列,然后再次尝试 498 | // 如果失败则挂起当前线程 499 | doAcquireSharedInterruptibly(arg); 500 | } 501 | ``` 502 | 503 | tryAcquireShared由Sync的子类实现以根据公平性采取相应的行为。 504 | 505 | 以下是非公平策略NofairSync的实现: 506 | 507 | ```java 508 | protected int tryAcquireShared(int acquires) { 509 | return nonfairTryAcquireShared(acquires); 510 | } 511 | 512 | final int nonfairTryAcquireShared(int acquires) { 513 | for (;;) { 514 | int available = getState(); 515 | int remaining = available - acquires; 516 | // 如果剩余信号量小于0直接返回 517 | // 否则如果更新信号量成功则返回 518 | if (remaining < 0 || 519 | compareAndSetState(available, remaining)) 520 | return remaining; 521 | } 522 | } 523 | ``` 524 | 525 | 假设线程A调用了acquire方法尝试获取信号量但因信号量不足被阻塞,这时线程B通过release增加了信号量,此时线程C完全可以调用acquire方法成功获取到信号量(如果信号量足够的话),这就是非公平性的体现。 526 | 527 | 下面是公平性的实现: 528 | 529 | ```java 530 | protected int tryAcquireShared(int acquires) { 531 | for (;;) { 532 | // 关键在于先判断AQS队列中是否已经有元素要获取信号量 533 | if (hasQueuedPredecessors()) 534 | return -1; 535 | int available = getState(); 536 | int remaining = available - acquires; 537 | if (remaining < 0 || 538 | compareAndSetState(available, remaining)) 539 | return remaining; 540 | } 541 | } 542 | ``` 543 | 544 | hasQueuedPredecessors方法(可参看[第6章 Java并发包中锁原理剖析](/06Java并发包中锁原理剖析.md))用于判断当前线程的前驱节点是否也在等待获取该资源,如果是则自己放弃获取的权限,然后当前线程会被放入AQS中,否则尝试去获取。 545 | 546 | #### void acquire(int permits) 547 | 548 | 可获取多个信号量。 549 | 550 | ```java 551 | public void acquire(int permits) throws InterruptedException { 552 | if (permits < 0) throw new IllegalArgumentException(); 553 | sync.acquireSharedInterruptibly(permits); 554 | } 555 | ``` 556 | 557 | #### void acquireUninterruptibly() 558 | 559 | 不对中断进行响应。 560 | 561 | ```java 562 | public void acquireUninterruptibly() { 563 | sync.acquireShared(1); 564 | } 565 | ``` 566 | 567 | #### void acquireUninterruptibly(int permits) 568 | 569 | 不对中断进行相应并且可获取多个信号量。 570 | 571 | ```java 572 | public void acquireUninterruptibly(int permits) { 573 | if (permits < 0) throw new IllegalArgumentException(); 574 | sync.acquireShared(permits); 575 | } 576 | ``` 577 | 578 | #### void release() 579 | 580 | 使信号量加1,如果当前有线程因为调用acquire方法被阻塞而被放入AQS中的话,会根据公平性策略选择一个信号量个数能被满足的线程进行激活。 581 | 582 | ```java 583 | public void release() { 584 | sync.releaseShared(1); 585 | } 586 | 587 | public final boolean releaseShared(int arg) { 588 | // 尝试释放资源(增加信号量) 589 | if (tryReleaseShared(arg)) { 590 | // 释放资源成功则根据公平性策略唤醒AQS中阻塞的线程 591 | doReleaseShared(); 592 | return true; 593 | } 594 | return false; 595 | } 596 | 597 | protected final boolean tryReleaseShared(int releases) { 598 | for (;;) { 599 | int current = getState(); 600 | int next = current + releases; 601 | if (next < current) // overflow 602 | throw new Error("Maximum permit count exceeded"); 603 | if (compareAndSetState(current, next)) 604 | return true; 605 | } 606 | } 607 | ``` 608 | 609 | #### void release(int permits) 610 | 611 | 可增加多个信号量。 612 | 613 | ```java 614 | public void release(int permits) { 615 | if (permits < 0) throw new IllegalArgumentException(); 616 | sync.releaseShared(permits); 617 | } 618 | ``` 619 | 620 | ## 更多 621 | 622 | 相关笔记:[《Java并发编程之美》阅读笔记](/README.md) 623 | -------------------------------------------------------------------------------- /11并发编程实践.md: -------------------------------------------------------------------------------- 1 | # 第11章 并发编程实践 2 | 🤪🤪🤪🤪🤪🤪🤪🤪🤪🤪🤪🤪🤪🤪🤪🤪🤪🤪🤪🤪🤪
3 | 🤪                                           本章没做任何笔记                                           🤪
4 | 🤪                     因为每个人在并发实践中碰到的问题各不相同                     🤪
5 | 🤪                                 需要大家去查找具体资料来解决                               🤪
6 | 🤪                                             故索性在此水一波                                         🤪
7 | 🤪                                                        逃了                                                    🤪
8 | 🤪🤪🤪🤪🤪🤪🤪🤪🤪🤪🤪🤪🤪🤪🤪🤪🤪🤪🤪🤪🤪
9 | ## 更多 10 | 11 | 相关笔记:[《Java并发编程之美》阅读笔记](/README.md) 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 📝《Java并发编程之美》阅读笔记 2 | 3 | ### 👀简介 4 | 5 | 最近在阅读《Java并发编程之美》这本书,为了督促自己啃完这本书,计划每读完一章写一篇阅读笔记,供以后参考。 6 | 7 | ### 📋笔记列表 8 | 9 | #### 🚩第一部分 Java并发编程基础篇 10 | 11 | - [第1章 并发编程线程基础](/01并发编程线程基础.md) 12 | 13 | - [第2章 并发编程的其他基础知识](/02并发编程的其他基础知识.md) 14 | 15 | #### 🚩第二部分 Java并发编程高级篇 16 | 17 | - [第3章 Java并发包中的ThreadLocalRandom类原理剖析](/03Java并发包中的ThreadLocalRandom类原理剖析.md) 18 | 19 | - [第4章 Java并发包中原子操作类原理剖析](/04Java并发包中原子操作类原理剖析.md) 20 | 21 | - [第5章 Java并发包中并发List源码剖析](/05Java并发包中并发List源码剖析.md) 22 | 23 | - [第6章 Java并发包中锁原理剖析](/06Java并发包中锁原理剖析.md) 24 | 25 | - [第7章 Java并发包中并发队列原理剖析](/07Java并发包中并发队列原理剖析.md) 26 | 27 | - [第8章 Java并发包中线程池ThreadPoolExecutor原理探究](/08Java并发包中线程池ThreadPoolExecutor原理探究.md) 28 | 29 | - [第9章 Java并发包中ScheduledThreadPoolExecutor原理探究](/09Java并发包中ScheduledThreadPoolExecutor原理探究.md) 30 | 31 | - [第10章 Java并发包中线程同步器原理剖析](/10Java并发包中线程同步器原理剖析.md) 32 | 33 | #### 🚩第三部分 Java并发编程实践篇 34 | 35 | - [第11章 并发编程实践](/11并发编程实践.md) 36 | -------------------------------------------------------------------------------- /images/01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afkbrb/java-concurrency-note/85bc51c3b64691c557b1bc51510a7877df0d45cb/images/01.png -------------------------------------------------------------------------------- /images/02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afkbrb/java-concurrency-note/85bc51c3b64691c557b1bc51510a7877df0d45cb/images/02.png -------------------------------------------------------------------------------- /images/03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afkbrb/java-concurrency-note/85bc51c3b64691c557b1bc51510a7877df0d45cb/images/03.png -------------------------------------------------------------------------------- /images/04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afkbrb/java-concurrency-note/85bc51c3b64691c557b1bc51510a7877df0d45cb/images/04.png -------------------------------------------------------------------------------- /images/05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afkbrb/java-concurrency-note/85bc51c3b64691c557b1bc51510a7877df0d45cb/images/05.png -------------------------------------------------------------------------------- /images/06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afkbrb/java-concurrency-note/85bc51c3b64691c557b1bc51510a7877df0d45cb/images/06.png -------------------------------------------------------------------------------- /images/07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afkbrb/java-concurrency-note/85bc51c3b64691c557b1bc51510a7877df0d45cb/images/07.png -------------------------------------------------------------------------------- /images/08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afkbrb/java-concurrency-note/85bc51c3b64691c557b1bc51510a7877df0d45cb/images/08.png -------------------------------------------------------------------------------- /images/09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afkbrb/java-concurrency-note/85bc51c3b64691c557b1bc51510a7877df0d45cb/images/09.png -------------------------------------------------------------------------------- /images/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afkbrb/java-concurrency-note/85bc51c3b64691c557b1bc51510a7877df0d45cb/images/10.png -------------------------------------------------------------------------------- /images/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afkbrb/java-concurrency-note/85bc51c3b64691c557b1bc51510a7877df0d45cb/images/11.png -------------------------------------------------------------------------------- /images/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afkbrb/java-concurrency-note/85bc51c3b64691c557b1bc51510a7877df0d45cb/images/12.png -------------------------------------------------------------------------------- /images/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afkbrb/java-concurrency-note/85bc51c3b64691c557b1bc51510a7877df0d45cb/images/13.png -------------------------------------------------------------------------------- /images/14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afkbrb/java-concurrency-note/85bc51c3b64691c557b1bc51510a7877df0d45cb/images/14.png -------------------------------------------------------------------------------- /images/15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afkbrb/java-concurrency-note/85bc51c3b64691c557b1bc51510a7877df0d45cb/images/15.png -------------------------------------------------------------------------------- /images/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afkbrb/java-concurrency-note/85bc51c3b64691c557b1bc51510a7877df0d45cb/images/16.png --------------------------------------------------------------------------------