├── README.md ├── doc ├── chapter1.md ├── chapter10.md ├── chapter2.md ├── chapter3-1.md ├── chapter3-2.md ├── chapter3.md ├── chapter4.md ├── chapter5.md ├── chapter6.md ├── chapter7.md └── chapter8.md ├── image ├── 0.jpg ├── 10-1.png ├── 10-2.png ├── 10-3.png ├── 10-4.png ├── 10-5.png ├── 2-1.png ├── 5-1.jpeg ├── 5-1.png ├── 5-2.jpeg ├── 5-3.jpeg ├── 5-4.jpeg ├── 5-5.jpeg ├── 6-1.png └── 7-1.png ├── pom.xml └── src └── main ├── java └── com │ └── concurrent │ ├── JavaApplication.java │ ├── chapter1 │ ├── ConcurrentTest.java │ └── DeadLockDemo.java │ ├── chapter2 │ └── Counter.java │ ├── chapter3 │ ├── DoubleCheckedLocking.java │ ├── FinalReferenceEscapeExample.java │ └── ReentrantLockExample.java │ ├── chapter4 │ ├── ConnectionDriver.java │ ├── ConnectionPool.java │ ├── MultiThread.java │ ├── Priority.java │ └── ThreadState.java │ ├── chapter5 │ ├── BoundedQueue.java │ ├── Cache.java │ ├── ConditionUseCase.java │ ├── Mutex.java │ └── TwinsLock.java │ ├── chapter7 │ └── AtomicReferenceTest.java │ └── chapter8 │ ├── BankWaterService.java │ ├── CountDownLatchTest.java │ ├── CyclicBarrierTest.java │ ├── JoinCountDownLatchTest.java │ └── SemaphoreTest.java └── resources └── application.properties /README.md: -------------------------------------------------------------------------------- 1 | ## 《Java并发编程的艺术》学习笔记 2 | 3 | 4 | - [第1章 并发编程的挑战](https://github.com/zaiyunduan123/java-concurrent-art/blob/master/doc/chapter1.md) 5 | - [第2章 Java并发机制的底层实现原理](https://github.com/zaiyunduan123/java-concurrent-art/blob/master/doc/chapter2.md) 6 | - [第3章 Java内存模型](https://github.com/zaiyunduan123/java-concurrent-art/blob/master/doc/chapter3.md) 7 | - [第4章 Java并发编程基础](https://github.com/zaiyunduan123/java-concurrent-art/blob/master/doc/chapter4.md) 8 | - [第5章 Java中的锁](https://github.com/zaiyunduan123/java-concurrent-art/blob/master/doc/chapter5.md) 9 | - [第6章 Java并发容器和框架](https://github.com/zaiyunduan123/java-concurrent-art/blob/master/doc/chapter6.md) 10 | - [第7章 Java中的13个原子操作类](https://github.com/zaiyunduan123/java-concurrent-art/blob/master/doc/chapter7.md) 11 | - [第8章 Java中的并发工具类](https://github.com/zaiyunduan123/java-concurrent-art/blob/master/doc/chapter8.md) 12 | - [第10章 Executor框架](https://github.com/zaiyunduan123/java-concurrent-art/blob/master/doc/chapter10.md) 13 | 14 | 15 | ### 思维导图 16 | ![](https://github.com/zaiyunduan123/java-concurrent-art/blob/master/image/0.jpg) 17 | 18 | -------------------------------------------------------------------------------- /doc/chapter1.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [第1章 并发编程的挑战](#%E7%AC%AC1%E7%AB%A0-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E7%9A%84%E6%8C%91%E6%88%98) 6 | - [上下文切换](#%E4%B8%8A%E4%B8%8B%E6%96%87%E5%88%87%E6%8D%A2) 7 | - [如何减少上下文切换](#%E5%A6%82%E4%BD%95%E5%87%8F%E5%B0%91%E4%B8%8A%E4%B8%8B%E6%96%87%E5%88%87%E6%8D%A2) 8 | - [死锁](#%E6%AD%BB%E9%94%81) 9 | - [避免死锁的几种常见方法](#%E9%81%BF%E5%85%8D%E6%AD%BB%E9%94%81%E7%9A%84%E5%87%A0%E7%A7%8D%E5%B8%B8%E8%A7%81%E6%96%B9%E6%B3%95) 10 | 11 | 12 | 13 | # 第1章 并发编程的挑战 14 | 15 | ## 上下文切换 16 | ### 如何减少上下文切换 17 | 18 | 1. 减少上下文切换的方法有无锁并发编程、CAS算法、使用最少的线程和使用协程 19 | 2. 无锁并发编程。多线程竞争时,会引起上下文切换,所以多线程处理数据时,可以用一些办法来避免使用锁,将数据Id按hash算法取模来分段,不同的线程处理不同时端的数据 20 | 3. CAS算法。Java的atomic包使用CAS算法来更新数据,面不需要加锁。Atomic变量的更新可以实现数据操作的原子性及可见性。这个是由volatile 原语及CPU的CAS指令来实现的。 21 | 4. 使用最少的线程。若任务少,但创建了很多线程来处理,这样会造成大量的线程处于等等状态。 22 | 23 | ## 死锁 24 | ### 避免死锁的几种常见方法 25 | 26 | 1. 避免一个线程同时获取多个锁 27 | 2. 避免一个线程同时占用多个资源,尽量保证每个锁只占用一个资源 28 | 3. 尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制 29 | 4. 对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况 -------------------------------------------------------------------------------- /doc/chapter10.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [第10章 Executor框架](#%E7%AC%AC10%E7%AB%A0-executor%E6%A1%86%E6%9E%B6) 6 | - [介绍](#%E4%BB%8B%E7%BB%8D) 7 | - [Executor框架结构](#executor%E6%A1%86%E6%9E%B6%E7%BB%93%E6%9E%84) 8 | - [ThreadPoolExecutor](#threadpoolexecutor) 9 | - [FixedThreadPool](#fixedthreadpool) 10 | - [SingleThreadExecutor](#singlethreadexecutor) 11 | - [CachedThreadPool](#cachedthreadpool) 12 | 13 | 14 | 15 | 16 | 17 | # 第10章 Executor框架 18 | 19 | ## 介绍 20 | 21 | 从代码上看,Executor 是一个简单的接口,但它却是整个异步任务执行框架的基础。他将任务的提交和执行解耦开来,任务用 Runnable 来表示。Executor 基于生产者-消费者模式,提交任务的线程相当于生产者,执行任务的线程相当于消费者。同时,Executor 的实现还提供了对任务执行的生命周期管理的支持。 22 | 23 | ![](https://github.com/zaiyunduan123/java-concurrent-art/blob/master/image/10-1.png) 24 | 25 | 26 | ### Executor框架结构 27 | Executor框架有3部分组成 28 | 1. 任务:包括被执行任务需要实现的接口:Runnable接口、Callable接口 29 | 2. 任务的执行:包括任务执行机制的核心接口Executor,以及继承自己Executor的ExecutorService接口。ThreadPoolExecutor和ScheduledThreadPoolExecutor是实现ExecutorService接口两个关键类 30 | 3. 异步计算的结果:包括接口Future和实现Future接口的FutureTask类 31 | 32 | ![](https://github.com/zaiyunduan123/java-concurrent-art/blob/master/image/10-2.png) 33 | 34 | ## ThreadPoolExecutor 35 | 36 | Executor框架最核心的类是ThreadPoolExecutor,它是线程池的实现类,主要由下列4个组件构成 37 | 1. corePool:核心线程池的大小。 38 | 2. maximumPool:最大线程池的大小。 39 | 3. BlockingQueue:用来暂时保存任务的工作队列。 40 | 4. RejectedExecutionHandler:当ThreadPoolExecutor已经关闭或ThreadPoolExecutor已经饱和时(达到了最大线程池大小且工作队列已满),execute()方法将要调用的Handler。 41 | 42 | 通过Executor框架的工具类Executors,可以创建3种类型的ThreadPoolExecutor: 43 | 1. FixedThreadPool 44 | 2. SingleThreadExecutor 45 | 3. CachedThreadPool 46 | 47 | ### FixedThreadPool 48 | FixedThreadPool被称为可重用固定线程数的线程池 49 | - 可控制线程最大并发数(同时执行的线程数) 50 | - 超出的线程会在队列中等待 51 | 52 | ![](https://github.com/zaiyunduan123/java-concurrent-art/blob/master/image/10-3.png) 53 | 54 | ### SingleThreadExecutor 55 | - 有且仅有一个工作线程执行任务 56 | - 所有任务按照指定顺序执行,即遵循队列的入队出队规则 57 | 58 | ![](https://github.com/zaiyunduan123/java-concurrent-art/blob/master/image/10-4.png) 59 | 60 | ### CachedThreadPool 61 | 62 | CachedThreadPool是一个会根据需要创建新线程的线程池 63 | - 线程数无限制 64 | - 有空闲线程则复用空闲线程,若无空闲线程则新建线程 一定程序减少频繁创建/销毁线程,减少系统开销 65 | ![](https://github.com/zaiyunduan123/java-concurrent-art/blob/master/image/10-5.png) -------------------------------------------------------------------------------- /doc/chapter2.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [第2章 Java并发机制的底层实现原理](#%E7%AC%AC2%E7%AB%A0-java%E5%B9%B6%E5%8F%91%E6%9C%BA%E5%88%B6%E7%9A%84%E5%BA%95%E5%B1%82%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86) 6 | - [volatile](#volatile) 7 | - [实现原理](#%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86) 8 | - [synchronized](#synchronized) 9 | - [实现原理](#%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86-1) 10 | - [Java对象头](#java%E5%AF%B9%E8%B1%A1%E5%A4%B4) 11 | - [锁的升级](#%E9%94%81%E7%9A%84%E5%8D%87%E7%BA%A7) 12 | - [原子操作](#%E5%8E%9F%E5%AD%90%E6%93%8D%E4%BD%9C) 13 | - [处理器如何实现原子操作](#%E5%A4%84%E7%90%86%E5%99%A8%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%E5%8E%9F%E5%AD%90%E6%93%8D%E4%BD%9C) 14 | - [JAVA如何实现原子操作](#java%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%E5%8E%9F%E5%AD%90%E6%93%8D%E4%BD%9C) 15 | - [循环CAS](#%E5%BE%AA%E7%8E%AFcas) 16 | - [锁](#%E9%94%81) 17 | 18 | 19 | 20 | 21 | # 第2章 Java并发机制的底层实现原理 22 | 23 | ## volatile 24 | 一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义: 25 | 1. 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。 26 | 2. 禁止进行指令重排序。 27 | 28 | ### 实现原理 29 | 加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令 30 | 31 | lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能: 32 | 1. 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成; 33 | 2. 它会强制将对缓存的修改操作立即写入主存; 34 | 3. 如果是写操作,它会导致其他CPU中对应的缓存行无效。 35 | 36 | ## synchronized 37 | synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性 38 | 39 | 40 | 利用synchronized实现同步的基础,Java中的每一个对象都可以作为锁,有以下3种形式 41 | - 对于普通同步方法,锁是当前实例对象 42 | - 对于静态同步方法,锁是当前类的Class对象 43 | - 对于同步方法块,锁锁Synchonized括号里配置的对象 44 | 45 | ### 实现原理 46 | Java 虚拟机中的同步(Synchronization)是基于进入和退出Monitor对象实现, 无论是显式同步(有明确的 monitorenter 和 monitorexit 指令,即同步代码块)还是隐式同步都是如此。在 Java 语言中,同步用的最多的地方可能是被 synchronized 修饰的同步方法。同步方法并不是由monitorenter 和 monitorexit 指令来实现同步的,而是由方法调用指令读取运行时常量池中方法的 ACC_SYNCHRONIZED 标志来隐式实现的 47 | 48 | 我们对如下同步代码块进行javap反编译: 49 | ```java 50 | 1 public void add(Object obj){ 51 | 2 synchronized (obj){ 52 | 3 //do something 53 | 4 } 54 | 5 } 55 | ``` 56 | 反编译后的代码如下: 57 | ```java 58 | 1public class com.wuzy.thread.SynchronizedDemo { 59 | 2 public com.wuzy.thread.SynchronizedDemo(); 60 | 3 Code: 61 | 4 0: aload_0 62 | 5 1: invokespecial #1 // Method java/lang/Object."":()V 63 | 6 4: return 64 | 7 65 | 8 public void add(java.lang.Object); 66 | 9 Code: 67 | 10 0: aload_1 68 | 11 1: dup 69 | 12 2: astore_2 70 | 13 3: monitorenter //注意此处,进入同步方法 71 | 14 4: aload_2 72 | 15 5: monitorexit //注意此处,退出同步方法 73 | 16 6: goto 14 74 | 17 9: astore_3 75 | 18 10: aload_2 76 | 19 11: monitorexit //注意此处,退出同步方法 77 | 20 12: aload_3 78 | 21 13: athrow 79 | 22 14: return 80 | 23 Exception table: 81 | 24 from to target type 82 | 25 4 6 9 any 83 | 26 9 12 9 any 84 | 27} 85 | ``` 86 | 我们看下第13行~15行代码,发现同步代码块是使用monitorenter和monitorexit指令来进行代码同步的,注意看第19行代码,为什么会多出一个monitorexit指令,主要是JVM为了防止代码出现异常,也能正确退出同步方法。 87 | 88 | 接下来我们将同步整个方法进行反编译一下: 89 | ```java 90 | 1 public synchronized void update(){ 91 | 2 92 | 3 } 93 | ``` 94 | 反编译后的代码如下: 95 | ```java 96 | 1public class com.wuzy.thread.SynchronizedDemo { 97 | 2 public com.wuzy.thread.SynchronizedDemo(); 98 | 3 Code: 99 | 4 0: aload_0 100 | 5 1: invokespecial #1 // Method java/lang/Object."":()V 101 | 6 4: return 102 | 7 103 | 8 public synchronized void update(); 104 | 9 Code: 105 | 10 0: return 106 | 11} 107 | ``` 108 | 109 | 从反编译的代码看,同步方法并不是用monitorenter和monitorexit指令来进行同步的,实际上同步方法会被翻译成普通的方法调用和返回指令如:invokevirtual、areturn指令,在VM字节码层面并没有任何特别的指令来实现被synchronized修饰的方法,而是在Class文件的方法表中将该方法的access_flags字段中的synchronized标志位置设为1,表示该方法是同步方法并使用调用该方法的对象或该方法所属的Class在JVM的内部对象表示做为锁对象。 110 | 111 | ### Java对象头 112 | 113 | 在JVM中,对象在内存中的布局分为3块:对象头、实例数据和对齐填充。 114 | 115 | synchronized使用的锁信息都放在对象头里,JVM中用2个字节来储存对象头(如果对象是数组则分配3个字节,多的一个字节用于存储数组的长度)。而对象头包含两部分信息,分别为Mark Word和类型指针。Mark Word主要用于储存对象自身的运行时数据,例如对象的hashCode、GC分代年龄、锁状态标志、线程持有的锁、偏向线程的ID、偏向时间戳等。而类型指针用于标识JVM通过这个指针来确定这个对象是哪个类的实例。 116 | 117 | 由于对象需要储存的运行时数据过多,Mark Word被设计成一个非固定的数据结构以便在极小的空间内存储更多的信息。对象在不同的状态下,Mark Word会存储不同的内容(只放32位虚拟机的图表)。 118 | 119 | ![](https://github.com/zaiyunduan123/java-concurrent-art/blob/master/image/2-1.png) 120 | 121 | 122 | 锁标志位的表示意义 123 | - 锁标识 lock=00 表示轻量级锁 124 | - 锁标识 lock=10 表示重量级锁 125 | - 偏向锁标识 biased_lock=1表示偏向锁 126 | - 偏向锁标识 biased_lock=0且锁标识=01表示无锁状态 127 | ### 锁的升级 128 | 锁的状态:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态 129 | 130 | 所谓锁的升级、降级,就是 JVM 优化 synchronized 运行的机制,当 JVM 检测到不同的竞争状况时,会自动切换到适合的锁实现,这种切换就是锁的升级、降级。 131 | 132 | 当没有竞争出现时,默认会使用偏斜锁。JVM 会利用 CAS 操作(compare and swap),在对象头上的 Mark Word 部分设置线程 ID,以表示这个对象偏向于当前线程,所以并不涉及真正的互斥锁。这样做的假设是基于在很多应用场景中,大部分对象生命周期中最多会被一个线程锁定,使用偏斜锁可以降低无竞争开销。 133 | 134 | 如果有另外的线程试图锁定某个已经被偏斜过的对象,JVM 就需要撤销(revoke)偏斜锁,并切换到轻量级锁实现。轻量级锁依赖 CAS 操作 Mark Word 来试图获取锁,如果重试成功,就使用普通的轻量级锁;否则,进一步升级为重量级锁。 135 | 136 | 通俗来讲就是: 137 | - 偏向锁:仅有一个线程进入临界区 138 | - 轻量级锁:多个线程交替进入临界区 139 | - 重量级锁:多个线程同时进入临界区 140 | 141 | 142 | 我注意到有的观点认为 Java 不会进行锁降级。实际上据我所知,锁降级确实是会发生的,当 JVM 进入安全点(SafePoint)的时候,会检查是否有闲置的 Monitor,然后试图进行降级。 143 | 144 | ## 原子操作 145 | 原子操作(atomic operation)意为"不可被中断的一个或一系列操作" 146 | 147 | ### 处理器如何实现原子操作 148 | 1、使用总线锁保证原子性 149 | 150 | 所谓总线锁就是使用处理器提供的一个LOCK#信号,当一个处理器在总线上输出此信号时,其他处理器的请求将被阻塞住,那么该处理器可以独占使用共享内存。 151 | 152 | 2、使用缓存锁保证原子性 153 | 154 | 所谓“缓存锁定”就是如果缓存在处理器缓存行中内存区域在LOCK操作期间被锁定,当它执行锁操作回写内存时,处理器不在总线上声言LOCK#信号,而是修改内部的内存地址,并允许它的缓存一致性机制来保证操作的原子性 155 | 156 | 157 | ### JAVA如何实现原子操作 158 | 159 | Java中主要通过下面两种方式来实现原子操作:锁和循环CAS 160 | 161 | #### 循环CAS 162 | CAS全称Compare-and-Swap(比较并交换),JVM中的CAS操作是依赖处理器提供的cmpxchg指令完成的,CAS指令中有3个操作数,分别是内存位置V、旧的预期值A和新值B 163 | 164 | 当CAS指令执行时,当且仅当内存位置V符合旧预期值时A时,处理器才会用新值B去更新V的值,否则就不执行更新,但是无论是否更新V,都会返回V的旧值,该操作过程就是一个原子操作 165 | 166 | JDK1.5之后才可以使用CAS,由sun.misc.Unsafe类里面的compareAndSwapInt()和compareAndSwapLong()等方法包装实现,虚拟机在即时编译时,对这些方法做了特殊处理,会编译出一条相关的处理器CAS指令 167 | 168 | **CAS实现原子操作的三大问题** 169 | 170 | **1、ABA问题**:初次读取内存旧值时是A,再次检查之前这段期间,如果内存位置的值发生过从A变成B再变回A的过程,我们就会错误的检查到旧值还是A,认为没有发生变化,其实已经发生过A-B-A得变化,这就是CAS操作的ABA问题 171 | 172 | 解决方法:使用版本号,即1A-2B-3A,这样就会发现1A到3A的变化,不存在ABA变化无感知问题,JDK的atomic包中提供一个带有标记的原子引用类AtomicStampedReference来解决ABA问题 173 | 174 | **2、循环时间长开销大**:自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销 175 | 176 | **3、只能保证一个共享变量的原子操作**:当对一个共享变量执行操作时,可以使用循环CAS来保证原子操作,但是多个共享变量操作时,就无法保证了 177 | 178 | 解决方法: 179 | - 将多个变量组合成一个共享变量,jdk提供了AtomicReference类来保证引用对象之间的原子性,那么就可以把多个变量放在一个对象里来进行CAS操作 180 | - 使用锁 181 | 182 | #### 锁 183 | 184 | 锁机制保证了只有获得锁的线程才能够操作锁定的内存区域。JVM内部实现了很多锁机制,有偏向锁、轻量级锁和互斥锁。除了偏向锁,JVM实现锁的方式都用了循环CAS,即当一个线程想进入同步块的时候使用循环CAS的方式来获取锁,当它退出同步块的时候使用循环CAS释放锁。 185 | 186 | 187 | -------------------------------------------------------------------------------- /doc/chapter3-1.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [锁的内存语义](#%E9%94%81%E7%9A%84%E5%86%85%E5%AD%98%E8%AF%AD%E4%B9%89) 6 | - [锁的释放-获取建立的happens before关系](#%E9%94%81%E7%9A%84%E9%87%8A%E6%94%BE-%E8%8E%B7%E5%8F%96%E5%BB%BA%E7%AB%8B%E7%9A%84happens-before%E5%85%B3%E7%B3%BB) 7 | - [锁释放和获取的内存语义](#%E9%94%81%E9%87%8A%E6%94%BE%E5%92%8C%E8%8E%B7%E5%8F%96%E7%9A%84%E5%86%85%E5%AD%98%E8%AF%AD%E4%B9%89) 8 | - [锁释放](#%E9%94%81%E9%87%8A%E6%94%BE) 9 | - [锁获取](#%E9%94%81%E8%8E%B7%E5%8F%96) 10 | 11 | 12 | 13 | # 锁的内存语义 14 | 15 | ## 锁的释放-获取建立的happens before关系 16 | 锁是Java并发编程中最重要的同步机制,锁除了了让临界区互斥执行外,还可以让释放锁的线程向获取同一个锁的线程发送消息 17 | 18 | ```java 19 | public class MonitorExample { 20 | int a = 0; 21 | 22 | public synchronized void writer() {// 1 23 | a++;// 2 24 | }// 3 25 | 26 | public synchronized void reader() {// 4 27 | int i = a;// 5 28 | }// 6 29 | 30 | } 31 | 32 | ``` 33 | 1. 根据程序次序规则,1 happens before 2,2 happens before 3,4 happens before 5,5 happens before 6 34 | 2. 根据监视器锁规则,3 happens before 4 35 | 3. 感觉传递性,2 happens before 5 36 | 37 | ![](http://img.blog.csdn.net/20170809091730250) 38 | 39 | 上图表示线程A释放锁之后,随后线程B获取同一个锁,也就2 happens before 5 。 40 | 41 | ## 锁释放和获取的内存语义 42 | 43 | 当线程释放锁时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中 44 | 45 | ### 锁释放 46 | ![](http://img.blog.csdn.net/20170809092235458) 47 | 48 | 而线程B获取锁,JMM会把其对应内存置为无效,从而使被监视器保护的临界区代码必须要从主内存去读取共享变量 49 | 50 | ### 锁获取 51 | ![](http://img.blog.csdn.net/20170809092536470) 52 | 53 | 锁的内存语义总结: 54 | 1. 线程A释放一个锁,实质上是线程A向接下来将要获取这个锁的某个线程发送了(线程A对共享变量做了修改的)消息 55 | 2. 线程B获取一个锁,实质上是线程B接收了之前线程A发的(在释放锁之前对共享变量做了修改的)消息 56 | 3. 线程A释放锁,随后线程B获取锁,这个过程实质上级是线程A通过主内存向线程B发送消息 -------------------------------------------------------------------------------- /doc/chapter3-2.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [volatile的内存语义](#volatile%E7%9A%84%E5%86%85%E5%AD%98%E8%AF%AD%E4%B9%89) 6 | - [volatile特性](#volatile%E7%89%B9%E6%80%A7) 7 | - [volatile写/读建立的happens before关系](#volatile%E5%86%99%E8%AF%BB%E5%BB%BA%E7%AB%8B%E7%9A%84happens-before%E5%85%B3%E7%B3%BB) 8 | - [volatile内存语义的实现](#volatile%E5%86%85%E5%AD%98%E8%AF%AD%E4%B9%89%E7%9A%84%E5%AE%9E%E7%8E%B0) 9 | - [volatile写操作](#volatile%E5%86%99%E6%93%8D%E4%BD%9C) 10 | - [volatile读操作](#volatile%E8%AF%BB%E6%93%8D%E4%BD%9C) 11 | 12 | 13 | 14 | 15 | # volatile的内存语义 16 | 17 | ## volatile特性 18 | 把对volatile变量的单个读、写,看出是使用同一个锁对这些单个读、写做了同步,比如: 19 | 20 | ```java 21 | public class VolatileFeaturesExample { 22 | 23 | volatile long vl = 0L; 24 | 25 | public void set(long l) { 26 | vl = l; 27 | } 28 | 29 | public void getAndIncrement() { 30 | vl++;// 复合(多个)volatile变量的读/写 31 | } 32 | 33 | public long get() { 34 | return vl;// 单个volatile变量的读 35 | } 36 | 37 | } 38 | ``` 39 | 等价于 40 | 41 | ```java 42 | class VolatileFeaturesExample1 { 43 | long vl = 0L; 44 | 45 | public synchronized void set(long l) { 46 | vl = l; 47 | } 48 | 49 | public synchronized long get() { 50 | return vl; 51 | } 52 | 53 | public void getAndIncrement() { 54 | long temp = get(); 55 | temp += 1L; 56 | set(temp); 57 | 58 | } 59 | } 60 | ``` 61 | 因为锁happens-before规则保证释放锁和获取锁两个线程之间的内存可见性,由可以推出,volatile变量的读总能看到对这个volatile变量最后的写入,而锁也决定了临界区代码的执行具有原子性,也就说,volatile变量同样对读写具有原子性 62 | 63 | 由上得出,volatile变量具有下列特性: 64 | 1. 可见性,volatile变量的读总能看到对这个volatile变量最后的写入 65 | 2. 原子性,对任意单个volatile变量的读写具有原子性,但volatile++复合操作是不具有原则性的 66 | ## volatile写/读建立的happens before关系 67 | 68 | 从内存的角度来说,volatile的写-读与锁的释放-获取有相同的内存效果 69 | 70 | ```java 71 | public class VolatileExample { 72 | int a=0; 73 | volatile boolean flag=false; 74 | 75 | public void writer(){ 76 | a=1; //1 77 | flag=true;//2 78 | } 79 | public void reader(){ 80 | if(flag){//3 81 | int i=a;//4 82 | 83 | } 84 | } 85 | } 86 | ``` 87 | 1、根据程序次序规则,1 happens before 2,3 happens before 4 88 | 2、根据volatile规则,2 happens before 3 89 | 3、根据happens before的传递规则,1 happens before 4 90 | ![](http://img.blog.csdn.net/20170809005605309) 91 | (1)当执行写入volatile时,也就是2,JMM会将该线程A对应本地内存更新过的共享变量刷新到主内存,那到共享变量a对其他线程是可见的,也就读到a就是想要的1,而不是0 92 | (2)当读一个volatile变量时,JMM会把该线程B对应的本地内存置为无效,线程直接从主内存读取共享变量,同时该读操作会把本地内存的值更为与主内存的值统一 93 | 94 | 95 | ## volatile内存语义的实现 96 | 下面看看JMM如何实现volatile写/读的内存语义 97 | 重排序分为编译器重排序和处理器重排序,JMM会限制这两种类型的重排序类型来保证volatile的内存语义 98 | ![](http://img.blog.csdn.net/20170809064111483) 99 | 1. 第二个操作是volatile写时,第一个操作不管是什么,都不能重排序 100 | 2. 第一个操作是volatile读时,第二个操作不管是什么,都不能重排序 101 | 3. 第一个操作是volatile是写,第二个操作是volatile是读,不能重排序 102 | 103 | 104 | JMM内存屏障插入策略:(Load:加载(读)、Store:保存(写),屏障名称就可以看出读写的先后顺序) 105 | 1. 在每个volatile写操作前插入StroreStore屏障 106 | 2. 在每个volatile写操作前插入StroreLoad屏障 107 | 3. 在每个volatile读操作前插入LoadLoad屏障 108 | 4. 在每个volatile读操作前插入LoadStore屏障 109 | 110 | ### volatile写操作 111 | ![](http://img.blog.csdn.net/20170809011911598) 112 | 上面的StroreStore屏障保证了在volatile写之前,其前面的所有普通写操作对任意处理器都是可见的,因为StroreStore屏障保障所有的普通写在本地内存的数据在voltile写之前刷新到主内存 113 | 114 | 而volatile写后面的StoreLoad屏障,作用是避免 115 | 116 | volatile写与后面可能有的volatile读/写操作重排序 117 | 118 | ### volatile读操作 119 | 120 | ![](http://img.blog.csdn.net/20170809071121175) 121 | 122 | 下面为代码示例 123 | 124 | ```java 125 | public class VolatileBarrierExample { 126 | int a; 127 | volatile int v1 = 1; 128 | volatile int v2 = 2; 129 | 130 | void readAndWrite() { 131 | int i = v1;// 第一个volatile读 132 | int j = v2;// 第二个volatile读 133 | a = i + j;// 普通写 134 | v1 = i + 1;// 第一个volatile写 135 | v2 = j * 2;// 第二个volatile写 136 | } 137 | } 138 | ``` 139 | 编译器生成字节码过程 140 | ![](http://img.blog.csdn.net/20170809072054181) 141 | 最后的StoreLoad屏障不能省略,因为编译器无法确定第二个volatile写后是否会有volatile读或写,保守起见,都会在该处加一个StoreLoad屏障 142 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /doc/chapter3.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [第3章 Java内存模型](#%E7%AC%AC3%E7%AB%A0-java%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B) 6 | 7 | 8 | 9 | 10 | # 第3章 Java内存模型 11 | 12 | - [volatile的内存语义](https://github.com/zaiyunduan123/java-concurrent-art/blob/master/doc/chapter3-2.md) 13 | - [锁的内存语义](https://github.com/zaiyunduan123/java-concurrent-art/blob/master/doc/chapter3-1.md) 14 | 15 | -------------------------------------------------------------------------------- /doc/chapter4.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [第4章 Java并发编程基础](#%E7%AC%AC4%E7%AB%A0-java%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E5%9F%BA%E7%A1%80) 6 | - [启动和终止线程](#%E5%90%AF%E5%8A%A8%E5%92%8C%E7%BB%88%E6%AD%A2%E7%BA%BF%E7%A8%8B) 7 | - [关于InterruptedException异常](#%E5%85%B3%E4%BA%8Einterruptedexception%E5%BC%82%E5%B8%B8) 8 | - [interrupt、isInterrupted和interrupted三种方法区别:](#interruptisinterrupted%E5%92%8Cinterrupted%E4%B8%89%E7%A7%8D%E6%96%B9%E6%B3%95%E5%8C%BA%E5%88%AB) 9 | 10 | 11 | 12 | # 第4章 Java并发编程基础 13 | 14 | ## 启动和终止线程 15 | ### 关于InterruptedException异常 16 | 17 | 抛InterruptedException的代表方法有: 18 | 1. java.lang.Object 类的 wait 方法 19 | 2. java.lang.Thread 类的 sleep 方法 20 | 3. java.lang.Thread 类的 join 方法 21 | 22 | 23 | 三个方法有个共同点就是需要花点时间: 24 | 1. 执行wait方法的线程,会进入等待区等待被notify/notify All。在等待期间,线程不会活动。 25 | 2. 执行sleep方法的线程,会暂停执行参数内所设置的时间。 26 | 3. 执行join方法的线程,会等待到指定的线程结束为止。 27 | 28 | 正是因为需要花时间的操作会降低程序的响应性,所以可能会取消/中途放弃执行这个方法。而取消主要是通过interrupt方法,所以可能抛出InterruptedException异常。 29 | 30 | 31 | 线程只是被interrupt是不会抛InterruptedException异常,而是线程处于阻塞状态的(sleep, wait, join)的同时被interrupt是才会抛该异常。 32 | 33 | 比如如下的代码: 34 | 35 | ```java 36 | while(true){ 37 | try { 38 | Thread.sleep(1000); 39 | }catch(InterruptedException ex) 40 | { 41 | throw new RuntimeException(ex); 42 | } 43 | } 44 | ``` 45 | 当线程执行sleep(1000)之后会被立即阻塞,如果在阻塞时外面调用interrupt来中断这个线程,那么就会throw new RuntimeException(ex);这个时候变量interrupt没有被置为true。 46 | 47 | 如果代码中不检测标识变量,也不调用Thread.currentThread().interrupt(),那么其实线程并未中断,这不能保证上层程序真正停止并退出。上层可能捕获了运行时异常,所以这个线程还是存活的。 48 | 49 | 所以,在任何时候碰到InterruptedException,都要自己手动把这个线程中断。由于这个时候已经处于非阻塞状态,所以可以正常中断,最正确的代码如下: 50 | 51 | ```java 52 | 53 | while(!Thread.currentThread().isInterrupted()){ 54 | try { 55 | Thread.sleep(1000); 56 | }catch(InterruptedException ex) 57 | { 58 | Thread.currentThread().interrupt(); 59 | throw new RuntimeException(ex); 60 | } 61 | 62 | ``` 63 | 通过Thread.currentThread().interrupt();将标识变量重新设置为 true,这样可以保证线程一定能够被及时中断。 64 | 65 | 66 | 67 | ### interrupt、isInterrupted和interrupted三种方法区别: 68 | 69 | ```java 70 | while (!Thread.currentThread().isInterrupted()) { 71 | try { 72 | Thread.sleep(5000); 73 | } catch (InterruptedException e) { 74 | Thread.currentThread().interrupt();//①:发出中断请求,设置中断状态 75 | System.out.println(Thread.currentThread().isInterrupted());//②:判断中断状态(不清除中断状态) 76 | System.out.println(Thread.interrupted());//③:判断中断状态(清除中断状态) 77 | } 78 | } 79 | 80 | } 81 | ``` 82 | 1. interrupt()会发出中断命令,设置中断状态。而isInterrupted()和interrupted()并不会发出中断线程的命令; 83 | 2. 调用Thread.currentThread().interrupt()方法并不会立刻中断当前线程,只有等当前线程阻塞在sleep、wait和join这些方法的内部会不断的检查中断状态的值上才会执行; 84 | 3. interrupted方法,用来检查中断状态并清除中断状态。 isInterrupted()和interrupted()的区别在于 interrupted会清除中断的状态;所以上面实例程序 会一直运行。如果注释掉第三点(catch代码库第三条),则程序会在下一次到达sleep的时候终止; 85 | 86 | -------------------------------------------------------------------------------- /doc/chapter5.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [第5章 Java中的锁](#%E7%AC%AC5%E7%AB%A0-java%E4%B8%AD%E7%9A%84%E9%94%81) 6 | - [Lock接口](#lock%E6%8E%A5%E5%8F%A3) 7 | - [Lock接口提供的synchronized所不具备的主要特性](#lock%E6%8E%A5%E5%8F%A3%E6%8F%90%E4%BE%9B%E7%9A%84synchronized%E6%89%80%E4%B8%8D%E5%85%B7%E5%A4%87%E7%9A%84%E4%B8%BB%E8%A6%81%E7%89%B9%E6%80%A7) 8 | - [队列同步器](#%E9%98%9F%E5%88%97%E5%90%8C%E6%AD%A5%E5%99%A8) 9 | - [同步队列的结构](#%E5%90%8C%E6%AD%A5%E9%98%9F%E5%88%97%E7%9A%84%E7%BB%93%E6%9E%84) 10 | - [同步状态的获取](#%E5%90%8C%E6%AD%A5%E7%8A%B6%E6%80%81%E7%9A%84%E8%8E%B7%E5%8F%96) 11 | - [重入锁](#%E9%87%8D%E5%85%A5%E9%94%81) 12 | - [ReentrantLock的非公平锁](#reentrantlock%E7%9A%84%E9%9D%9E%E5%85%AC%E5%B9%B3%E9%94%81) 13 | - [ReentrantLock的公平锁](#reentrantlock%E7%9A%84%E5%85%AC%E5%B9%B3%E9%94%81) 14 | - [读写锁 ReentrantReadWriteLock](#%E8%AF%BB%E5%86%99%E9%94%81-reentrantreadwritelock) 15 | - [LockSupport工具](#locksupport%E5%B7%A5%E5%85%B7) 16 | - [Condition接口](#condition%E6%8E%A5%E5%8F%A3) 17 | 18 | 19 | 20 | # 第5章 Java中的锁 21 | 22 | ## Lock接口 23 | ### Lock接口提供的synchronized所不具备的主要特性 24 | 25 | | 特性 | 描述 | 26 | | ------ | ------ | 27 | | 尝试非阻塞地获取锁 | 当前线程尝试获取锁,如果这一时刻锁没有被其他线程获取到,则成功获取并持有锁 | 28 | | 能被中断地获取锁 | 与synchronized不同,获取到的锁能够响应中断,当获取到锁的线程被中断时,中断异常将会被抛出,同时锁会被释放 | 29 | | 超时获取锁 | 在指定的时间截止之前获取锁,如果截止时间之前仍旧无法获取锁,则返回 | 30 | 31 | 32 | ## 队列同步器 33 | 34 | ### 同步队列的结构 35 | 队列同步器的实现依赖内部的同步队列来完成同步状态的管理。它是一个FIFO的双向队列,当线程获取同步状态失败时,同步器会将当前线程和等待状态等信息包装成一个节点并将其加入同步队列,同时会阻塞当前线程。当同步状态释放时,会把首节点中的线程唤醒,使其再次尝试获取同步状态。 36 | 37 | 节点是构成同步队列的基础,同步器拥有首节点和尾节点,没有成功获取同步状态的线程会成为节点加入该队列的尾部,其结构如下图所示 38 | 39 | ![](https://github.com/zaiyunduan123/java-concurrent-art/blob/master/image/5-1.jpeg) 40 | 41 | 同步器包含了两个节点类型的引用,一个指向头节点,而另一个指向尾节点。 42 | 43 | 如果一个线程没有获得同步队列,那么包装它的节点将被加入到队尾,显然这个过程应该是线程安全的。因此同步器提供了一个基于CAS的设置尾节点的方法:compareAndSetTail(Node expect,Node update),它需要传递一个它认为的尾节点和当前节点,只有设置成功,当前节点才被加入队尾。这个过程如下所示 44 | 45 | ![](https://github.com/zaiyunduan123/java-concurrent-art/blob/master/image/5-2.jpeg) 46 | 47 | 同步队列遵循FIFO,首节点是获取同步状态成功的节点,首节点线程在释放同步状态时,将会唤醒后继节点,而后继节点将会在获取同步状态成功时将自己设置为首节点,这一过程如下: 48 | 49 | ![](https://github.com/zaiyunduan123/java-concurrent-art/blob/master/image/5-3.jpeg) 50 | 51 | ### 同步状态的获取 52 | 下图描述了节点自旋获取同步状态的情况 53 | ![](https://github.com/zaiyunduan123/java-concurrent-art/blob/master/image/5-4.jpeg) 54 | ![](https://github.com/zaiyunduan123/java-concurrent-art/blob/master/image/5-5.jpeg) 55 | 56 | ## 重入锁 57 | 58 | 公平锁与非公平锁的区别:公平锁的获取顺序符合请求的绝对时间顺序,即FIFO,非公平锁不用。 59 | 60 | ### ReentrantLock的非公平锁 61 | ```java 62 | protected final boolean tryAcquire(int acquires) { 63 | final Thread current = Thread.currentThread(); 64 | int c = getState(); 65 | if (c == 0) { 66 | if (!hasQueuedPredecessors() && 67 | compareAndSetState(0, acquires)) { 68 | setExclusiveOwnerThread(current); 69 | return true; 70 | } 71 | } 72 | else if (current == getExclusiveOwnerThread()) { 73 | int nextc = c + acquires; 74 | if (nextc < 0) 75 | throw new Error("Maximum lock count exceeded"); 76 | setState(nextc); 77 | return true; 78 | } 79 | return false; 80 | } 81 | ``` 82 | 1. getState()获取锁数量 83 | 84 | 1. 如果锁数量为0,再基于CAS尝试将state(锁数量)从0设置为1一次,如果设置成功,设置当前线程为独占锁的线程; 85 | 86 | 2. 如果锁数量不为0或者上边的尝试又失败了,查看当前线程是不是已经是独占锁的线程了,如果是,则将当前的锁数量+1; 87 | 88 | 89 | ### ReentrantLock的公平锁 90 | 91 | 和非公平锁相比多了一个hasQueuedPredecessors方法 92 | ```java 93 | public final boolean hasQueuedPredecessors() { 94 | Node t = tail; // Read fields in reverse initialization order 95 | Node h = head; 96 | Node s; 97 | return h != t && 98 | ((s = h.next) == null || s.thread != Thread.currentThread()); 99 | } 100 | ``` 101 | 如果锁数量不为0或者当前线程不是等待队列中的头节点或者上边的尝试又失败了,查看当前线程是不是已经是独占锁的线程了,如果是,则将当前的锁数量+1;如果不是,则将该线程封装在一个Node 102 | 内,并加入到等待队列中去。等待被其前一个线程节点唤醒,保证串行化。 103 | 104 | 105 | 公平锁实现了先进先出的公平性,但是由于来一个线程就加入队列中,往往都需要阻塞,再由阻塞变为运行,这种上下文切换是非常耗性能的。非公平锁由于允许插队所以,上下文切换少的多,性能比较好,保证的大的吞吐量,但是容易出现饥饿问题 106 | 107 | ## 读写锁 ReentrantReadWriteLock 108 | 109 | JAVA的并发包提供了读写锁ReentrantReadWriteLock,它表示两个锁,一个是读操作相关的锁,称为共享锁;一个是写相关的锁,称为排他锁,描述如下: 110 | 111 | 线程进入读锁的前提条件: 112 | 1. 没有其他线程的写锁, 113 | 2. 没有写请求或者有写请求,但调用线程和持有锁的线程是同一个。 114 | 115 | 线程进入写锁的前提条件: 116 | 1. 没有其他线程的读锁 117 | 2. 没有其他线程的写锁 118 | 119 | 而读写锁有以下三个重要的特性: 120 | 1. 公平选择性:支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平。 121 | 2. 重进入:读锁和写锁都支持线程重进入。 122 | 3. 锁降级:遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为读锁。 123 | 124 | 125 | ```java 126 | protected final boolean tryAcquire(int acquires) { 127 | //当前线程 128 | Thread current = Thread.currentThread(); 129 | //获取状态 130 | int c = getState(); 131 | //写线程数量(即获取独占锁的重入数) 132 | int w = exclusiveCount(c); 133 | 134 | //当前同步状态state != 0,说明已经有其他线程获取了读锁或写锁 135 | if (c != 0) { 136 | // 当前state不为0,此时:如果写锁状态为0说明读锁此时被占用返回false; 137 | // 如果写锁状态不为0且写锁没有被当前线程持有返回false 138 | if (w == 0 || current != getExclusiveOwnerThread()) 139 | return false; 140 | 141 | //判断同一线程获取写锁是否超过最大次数(65535),支持可重入 142 | if (w + exclusiveCount(acquires) > MAX_COUNT) 143 | throw new Error("Maximum lock count exceeded"); 144 | //更新状态 145 | //此时当前线程已持有写锁,现在是重入,所以只需要修改锁的数量即可。 146 | setState(c + acquires); 147 | return true; 148 | } 149 | 150 | //到这里说明此时c=0,读锁和写锁都没有被获取 151 | //writerShouldBlock表示是否阻塞 152 | if (writerShouldBlock() || 153 | !compareAndSetState(c, c + acquires)) 154 | return false; 155 | 156 | //设置锁为当前线程所有 157 | setExclusiveOwnerThread(current); 158 | return true; 159 | } 160 | 161 | ``` 162 | 163 | ```java 164 | protected final int tryAcquireShared(int unused) { 165 | // 获取当前线程 166 | Thread current = Thread.currentThread(); 167 | // 获取状态 168 | int c = getState(); 169 | 170 | //如果写锁线程数 != 0 ,且独占锁不是当前线程则返回失败,因为存在锁降级 171 | if (exclusiveCount(c) != 0 && 172 | getExclusiveOwnerThread() != current) 173 | return -1; 174 | // 读锁数量 175 | int r = sharedCount(c); 176 | /* 177 | * readerShouldBlock():读锁是否需要等待(公平锁原则) 178 | * r < MAX_COUNT:持有线程小于最大数(65535) 179 | * compareAndSetState(c, c + SHARED_UNIT):设置读取锁状态 180 | */ 181 | // 读线程是否应该被阻塞、并且小于最大值、并且比较设置成功 182 | if (!readerShouldBlock() && 183 | r < MAX_COUNT && 184 | compareAndSetState(c, c + SHARED_UNIT)) { 185 | //r == 0,表示第一个读锁线程,第一个读锁firstRead是不会加入到readHolds中 186 | if (r == 0) { // 读锁数量为0 187 | // 设置第一个读线程 188 | firstReader = current; 189 | // 读线程占用的资源数为1 190 | firstReaderHoldCount = 1; 191 | } else if (firstReader == current) { // 当前线程为第一个读线程,表示第一个读锁线程重入 192 | // 占用资源数加1 193 | firstReaderHoldCount++; 194 | } else { // 读锁数量不为0并且不为当前线程 195 | // 获取计数器 196 | HoldCounter rh = cachedHoldCounter; 197 | // 计数器为空或者计数器的tid不为当前正在运行的线程的tid 198 | if (rh == null || rh.tid != getThreadId(current)) 199 | // 获取当前线程对应的计数器 200 | cachedHoldCounter = rh = readHolds.get(); 201 | else if (rh.count == 0) // 计数为0 202 | //加入到readHolds中 203 | readHolds.set(rh); 204 | //计数+1 205 | rh.count++; 206 | } 207 | return 1; 208 | } 209 | return fullTryAcquireShared(current); 210 | } 211 | ``` 212 | 213 | ## LockSupport工具 214 | 在没有LockSupport之前,线程的挂起和唤醒咱们都是通过Object的wait和notify/notifyAll方法实现。wait和notify/notifyAll方法只能在同步代码块里用 215 | 216 | LockSupport比Object的wait/notify有两大优势: 217 | 1. LockSupport不需要在同步代码块里 。所以线程间也不需要维护一个共享的同步对象了,实现了线程间的解耦。 218 | 2. unpark函数可以先于park调用,所以不需要担心线程间的执行的先后顺序。 219 | 220 | LockSupport 提供park()和unpark()方法实现阻塞线程和解除线程阻塞,LockSupport和每个使用它的线程都与一个许可(permit) 221 | 关联。permit相当于1,0的开关,默认是0,调用一次unpark就加1变成1,调用一次park会消费permit, 也就是将1变成0 222 | 223 | ```java 224 | public static void park(Object blocker) { 225 | // 获取当前线程 226 | Thread t = Thread.currentThread(); 227 | // 设置Blocker 228 | setBlocker(t, blocker); 229 | // 获取许可 230 | UNSAFE.park(false, 0L); 231 | // 重新可运行后再此设置Blocker 232 | setBlocker(t, null); 233 | } 234 | ``` 235 | 调用park函数时,首先获取当前线程,然后设置当前线程的parkBlocker字段,即调用setBlocker函数,之后调用Unsafe类的park函数,之后再调用setBlocker函数。 236 | 237 | 至于为什么要调用setBlocker两次?因为当前线程首先设置好parkBlocker字段后再调用Unsafe的park方法,之后,当前线程已经被阻塞,等待unpark方法被调用, unpark方法被调用,该线程获得许可后, 238 | 可以继续进行下面的代码,第二个setBlocker参数parkBlocker字段设置为null,这样就完成了整个park方法的逻辑。如果没有第二个setBlocker,那么之后没有调用park(Object blocker),而直接调用getBlocker方法,得到的还是前一个park(Object blocker)设置的blocker, 239 | 240 | ## Condition接口 241 | Condition的作用是对锁进行更精确的控制。Condition中的await()方法相当于Object的wait()方法,Condition中的signal()方法相当于Object的notify()方法,Condition中的signalAll()相当于Object的notifyAll()方法。不同的是,Object中的wait(),notify(),notifyAll()方法是和"同步锁"(synchronized关键字)捆绑使用的;而Condition是需要与"互斥锁"/"共享锁"捆绑使用的 242 | 243 | 244 | -------------------------------------------------------------------------------- /doc/chapter6.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [第6章 Java并发容器和框架](#%E7%AC%AC6%E7%AB%A0-java%E5%B9%B6%E5%8F%91%E5%AE%B9%E5%99%A8%E5%92%8C%E6%A1%86%E6%9E%B6) 6 | - [Java中的阻塞队列](#java%E4%B8%AD%E7%9A%84%E9%98%BB%E5%A1%9E%E9%98%9F%E5%88%97) 7 | - [ArrayBlockingQueue](#arrayblockingqueue) 8 | - [LinkedBlockingQueue](#linkedblockingqueue) 9 | - [PriorityBlockingQueue](#priorityblockingqueue) 10 | - [DelayQueue](#delayqueue) 11 | - [如何实现 Delayed 接口](#%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0-delayed-%E6%8E%A5%E5%8F%A3) 12 | - [如何实现延时队列](#%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%E5%BB%B6%E6%97%B6%E9%98%9F%E5%88%97) 13 | - [SynchronousQueue](#synchronousqueue) 14 | - [LinkedTransferQueue](#linkedtransferqueue) 15 | - [LinkedBlockingDeque](#linkedblockingdeque) 16 | - [实现原理](#%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86) 17 | 18 | 19 | 20 | 21 | 22 | # 第6章 Java并发容器和框架 23 | 24 | ## Java中的阻塞队列 25 | 阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作支持阻塞的插入和移除方法 26 | 27 | 28 | | 方法\处理方式 | 抛出异常 | 返回特殊值 | 一直阻塞 | 超时退出 | 29 | | ------------- | --------- | ---------- | -------- | ------------------ | 30 | | 插入方法 | add(e) | offer(e) | put(e) | offer(e,time,unit) | 31 | | 移除方法 | remove() | poll() | take() | poll(time,unit) | 32 | | 检查方法 | element() | peek() | 不可用 | 不可用 | 33 | 34 | - 抛出异常:是指当阻塞队列满时候,再往队列里插入元素,会抛出 IllegalStateException("Queue full") 异常。当队列为空时,从队列里获取元素时会抛出 NoSuchElementException 异常 。 35 | - 返回特殊值:插入方法会返回是否成功,成功则返回 true。移除方法,则是从队列里拿出一个元素,如果没有则返回 null 36 | - 一直阻塞:当阻塞队列满时,如果生产者线程往队列里 put 元素,队列会一直阻塞生产者线程,直到拿到数据,或者响应中断退出。当队列空时,消费者线程试图从队列里 take 元素,队列也会阻塞消费者线程,直到队列可用。 37 | - 超时退出:当阻塞队列满时,队列会阻塞生产者线程一段时间,如果超过一定的时间,生产者线程就会退出。 38 | 39 | 40 | 41 | JDK7 提供了 7 个阻塞队列。分别是 42 | 43 | - ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。 44 | - LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列。 45 | - PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。 46 | - DelayQueue:一个使用优先级队列实现的无界阻塞队列。 47 | - SynchronousQueue:一个不存储元素的阻塞队列。 48 | - LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。 49 | - LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。 50 | 51 | 52 | ### ArrayBlockingQueue 53 | 54 | ArrayBlockingQueue 是一个用数组实现的有界阻塞队列。此队列按照先进先出(FIFO)的原则对元素进行排序。 55 | 56 | 默认情况下不保证访问者公平的访问队列,所谓公平访问队列是指阻塞的所有生产者线程或消费者线程,当队列可用时,可以按照阻塞的先后顺序访问队列,即先阻塞的生产者线程,可以先往队列里插入元素,先阻塞的消费者线程,可以先从队列里获取元素。通常情况下为了保证公平性会降低吞吐量。我们可以使用以下代码创建一个公平的阻塞队列: 57 | 58 | ``` 59 | ArrayBlockingQueue fairQueue = new ArrayBlockingQueue(1000,true); 60 | ``` 61 | 62 | 访问者的公平性是使用可重入锁实现的,代码如下: 63 | 64 | ``` 65 | public ArrayBlockingQueue(int capacity, boolean fair) { 66 | if (capacity <= 0) 67 | throw new IllegalArgumentException(); 68 | this.items = new Object[capacity]; 69 | lock = new ReentrantLock(fair); 70 | notEmpty = lock.newCondition(); 71 | notFull = lock.newCondition(); 72 | } 73 | ``` 74 | ### LinkedBlockingQueue 75 | LinkedBlockingQueue 是一个用链表实现的有界阻塞队列。此队列的默认和最大长度为 Integer.MAX_VALUE。此队列按照先进先出的原则对元素进行排序。 76 | 77 | ### PriorityBlockingQueue 78 | PriorityBlockingQueue 是一个支持优先级的无界队列。默认情况下元素采取自然顺序排列,也可以通过比较器 comparator 来指定元素的排序规则。元素按照升序排列。 79 | ### DelayQueue 80 | DelayQueue 是一个支持延时获取元素的无界阻塞队列。队列使用 PriorityQueue 来实现。队列中的元素必须实现 Delayed 接口,在创建元素时可以指定多久才能从队列中获取当前元素。只有在延迟期满时才能从队列中提取元素。我们可以将 DelayQueue 运用在以下应用场景: 81 | - 缓存系统的设计:可以用 DelayQueue 保存缓存元素的有效期,使用一个线程循环查询 DelayQueue,一旦能从 DelayQueue 中获取元素时,表示缓存有效期到了。 82 | - 定时任务调度:使用 DelayQueue 保存当天将会执行的任务和执行时间,一旦从 DelayQueue 中获取到任务就开始执行,从比如 TimerQueue 就是使用 DelayQueue 实现的。 83 | 84 | 队列中的 Delayed 必须实现 compareTo 来指定元素的顺序。比如让延时时间最长的放在队列的末尾。实现代码如下: 85 | 86 | ```java 87 | public int compareTo(Delayed other) { 88 | if (other == this) // compare zero ONLY if same object 89 | return 0; 90 | if (other instanceof ScheduledFutureTask) { 91 | ScheduledFutureTask x = (ScheduledFutureTask)other; 92 | long diff = time - x.time; 93 | if (diff < 0) 94 | return -1; 95 | else if (diff > 0) 96 | return 1; 97 | else if (sequenceNumber < x.sequenceNumber) 98 | return -1; 99 | else 100 | return 1; 101 | } 102 | long d = (getDelay(TimeUnit.NANOSECONDS) - 103 | other.getDelay(TimeUnit.NANOSECONDS)); 104 | return (d == 0) ? 0 : ((d < 0) ? -1 : 1); 105 | } 106 | ``` 107 | 108 | #### 如何实现 Delayed 接口 109 | 110 | 我们可以参考 ScheduledThreadPoolExecutor 里 ScheduledFutureTask 类。这个类实现了 Delayed 接口。首先:在对象创建的时候,使用 time 记录前对象什么时候可以使用,代码如下: 111 | 112 | ```java 113 | ScheduledFutureTask(Runnable r, V result, long ns, long period) { 114 | super(r, result); 115 | this.time = ns; 116 | this.period = period; 117 | this.sequenceNumber = sequencer.getAndIncrement(); 118 | } 119 | ``` 120 | 121 | 然后使用 getDelay 可以查询当前元素还需要延时多久,代码如下: 122 | 123 | ```java 124 | public long getDelay(TimeUnit unit) { 125 | return unit.convert(time - now(), TimeUnit.NANOSECONDS); 126 | } 127 | ``` 128 | 129 | 通过构造函数可以看出延迟时间参数 ns 的单位是纳秒,自己设计的时候最好使用纳秒,因为 getDelay 时可以指定任意单位,一旦以纳秒作为单位,而延时的时间又精确不到纳秒就麻烦了。使用时请注意当 time 小于当前时间时,getDelay 会返回负数。 130 | 131 | #### 如何实现延时队列 132 | 133 | 延时队列的实现很简单,当消费者从队列里获取元素时,如果元素没有达到延时时间,就阻塞当前线程。 134 | 135 | ```java 136 | long delay = first.getDelay(TimeUnit.NANOSECONDS); 137 | if (delay <= 0) 138 | return q.poll(); 139 | else if (leader != null) 140 | available.await(); 141 | ``` 142 | ### SynchronousQueue 143 | SynchronousQueue 是一个不存储元素的阻塞队列。每一个 put 操作必须等待一个 take 操作,否则不能继续添加元素。 144 | 145 | SynchronousQueue 可以看成是一个传球手,负责把生产者线程处理的数据直接传递给消费者线程。 146 | 147 | 队列本身并不存储任何元素,非常适合于传递性场景, 比如在一个线程中使用的数据,传递给另外一个线程使用,SynchronousQueue 的吞吐量高于 LinkedBlockingQueue 和 ArrayBlockingQueue。 148 | 149 | ### LinkedTransferQueue 150 | LinkedTransferQueue 是一个由链表结构组成的无界阻塞 TransferQueue 队列。相对于其他阻塞队列,LinkedTransferQueue 多了 tryTransfer 和 transfer 方法。 151 | 152 | 如果当前有消费者正在等待接收元素(消费者使用 take() 方法或带时间限制的 poll() 方法时),transfer 方法可以把生产者传入的元素立刻 transfer(传输)给消费者。如果没有消费者在等待接收元素,transfer 方法会将元素存放在队列的 tail 节点,并等到该元素被消费者消费了才返回。transfer 方法的关键代码如下: 153 | 154 | ```java 155 | Node pred = tryAppend(s, haveData); 156 | return awaitMatch(s, pred, e, (how == TIMED), nanos); 157 | ``` 158 | 159 | 第一行代码是试图把存放当前元素的 s 节点作为 tail 节点。第二行代码是让 CPU 自旋等待消费者消费元素。因为自旋会消耗 CPU,所以自旋一定的次数后使用 Thread.yield() 方法来暂停当前正在执行的线程,并执行其他线程。 160 | 161 | tryTransfer 方法。则是用来试探下生产者传入的元素是否能直接传给消费者。如果没有消费者等待接收元素,则返回 false。和 transfer 方法的区别是 tryTransfer 方法无论消费者是否接收,方法立即返回。而 transfer 方法是必须等到消费者消费了才返回。 162 | 163 | 对于带有时间限制的 tryTransfer(E e, long timeout, TimeUnit unit) 方法,则是试图把生产者传入的元素直接传给消费者,但是如果没有消费者消费该元素则等待指定的时间再返回,如果超时还没消费元素,则返回 false,如果在超时时间内消费了元素,则返回 true。 164 | 165 | ### LinkedBlockingDeque 166 | 167 | LinkedBlockingDeque 是一个由链表结构组成的双向阻塞队列。所谓双向队列指的你可以从队列的两端插入和移出元素。双端队列因为多了一个操作队列的入口,在多线程同时入队时,也就减少了一半的竞争。 168 | 169 | 相比其他的阻塞队列,LinkedBlockingDeque 多了 addFirst,addLast,offerFirst,offerLast,peekFirst,peekLast 等方法,以 First 单词结尾的方法,表示插入,获取(peek)或移除双端队列的第一个元素。以 Last 单词结尾的方法,表示插入,获取或移除双端队列的最后一个元素。 170 | 171 | 在初始化 LinkedBlockingDeque 时可以设置容量防止其过渡膨胀。另外双向阻塞队列可以运用在“工作窃取”模式中。 172 | 173 | ### 实现原理 174 | 175 | 阻塞队列使用通知模式实现。所谓通知模式,就是当生产者往满的队列里添加元素时会阻塞住生产者,当消费者消费了一个队列中的元素后,会通知生产者当前队列可用。通过查看 JDK 源码发现 ArrayBlockingQueue 使用了 Condition 来实现,代码如下: 176 | 177 | ```java 178 | private final Condition notFull; 179 | private final Condition notEmpty; 180 | 181 | public ArrayBlockingQueue(int capacity, boolean fair) { 182 | // 省略其他代码 183 | notEmpty = lock.newCondition(); 184 | notFull = lock.newCondition(); 185 | } 186 | 187 | public void put(E e) throws InterruptedException { 188 | checkNotNull(e); 189 | final ReentrantLock lock = this.lock; 190 | lock.lockInterruptibly(); 191 | try { 192 | while (count == items.length) 193 | notFull.await(); 194 | insert(e); 195 | } finally { 196 | lock.unlock(); 197 | } 198 | } 199 | 200 | public E take() throws InterruptedException { 201 | final ReentrantLock lock = this.lock; 202 | lock.lockInterruptibly(); 203 | try { 204 | while (count == 0) 205 | notEmpty.await(); 206 | return extract(); 207 | } finally { 208 | lock.unlock(); 209 | } 210 | } 211 | 212 | private void insert(E x) { 213 | items[putIndex] = x; 214 | putIndex = inc(putIndex); 215 | ++count; 216 | notEmpty.signal(); 217 | } 218 | ``` 219 | 220 | 当我们往队列里插入一个元素时,如果队列不可用,阻塞生产者主要通过 LockSupport.park(this); 来实现 221 | 222 | ```java 223 | public final void await() throws InterruptedException { 224 | if (Thread.interrupted()) 225 | throw new InterruptedException(); 226 | Node node = addConditionWaiter(); 227 | int savedState = fullyRelease(node); 228 | int interruptMode = 0; 229 | while (!isOnSyncQueue(node)) { 230 | LockSupport.park(this); 231 | if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) 232 | break; 233 | } 234 | if (acquireQueued(node, savedState) && interruptMode != THROW_IE) 235 | interruptMode = REINTERRUPT; 236 | if (node.nextWaiter != null) // clean up if cancelled 237 | unlinkCancelledWaiters(); 238 | if (interruptMode != 0) 239 | 240 | reportInterruptAfterWait(interruptMode); 241 | } 242 | ``` 243 | 244 | 继续进入源码,发现调用 setBlocker 先保存下将要阻塞的线程,然后调用 unsafe.park 阻塞当前线程。 245 | 246 | ```java 247 | public static void park(Object blocker) { 248 | Thread t = Thread.currentThread(); 249 | setBlocker(t, blocker); 250 | unsafe.park(false, 0L); 251 | setBlocker(t, null); 252 | } 253 | ``` 254 | 255 | unsafe.park 是个 native 方法,代码如下: 256 | 257 | ```java 258 | public native void park(boolean isAbsolute, long time); 259 | ``` 260 | 261 | park 这个方法会阻塞当前线程,只有以下四种情况中的一种发生时,该方法才会返回 262 | - 与 park 对应的 unpark 执行或已经执行时。注意:已经执行是指 unpark 先执行,然后再执行的 park。 263 | - 线程被中断时。 264 | - 如果参数中的 time 不是零,等待了指定的毫秒数时。 265 | - 发生异常现象时。这些异常事先无法确定。 266 | 267 | 我们继续看一下 JVM 是如何实现 park 方法的,park 在不同的操作系统使用不同的方式实现,在 linux 下是使用的是系统方法 pthread_cond_wait 实现。实现代码在 JVM 源码路径 src/os/linux/vm/os_linux.cpp 里的 os::PlatformEvent::park 方法,代码如下: 268 | 269 | ```java 270 | void os::PlatformEvent::park() { 271 | int v ; 272 | for (;;) { 273 | v = _Event ; 274 | if (Atomic::cmpxchg (v-1, &_Event, v) == v) break ; 275 | } 276 | guarantee (v >= 0, "invariant") ; 277 | if (v == 0) { 278 | // Do this the hard way by blocking ... 279 | int status = pthread_mutex_lock(_mutex); 280 | assert_status(status == 0, status, "mutex_lock"); 281 | guarantee (_nParked == 0, "invariant") ; 282 | ++ _nParked ; 283 | while (_Event < 0) { 284 | status = pthread_cond_wait(_cond, _mutex); 285 | // for some reason, under 2.7 lwp_cond_wait() may return ETIME ... 286 | // Treat this the same as if the wait was interrupted 287 | if (status == ETIME) { status = EINTR; } 288 | assert_status(status == 0 || status == EINTR, status, "cond_wait"); 289 | } 290 | -- _nParked ; 291 | 292 | // In theory we could move the ST of 0 into _Event past the unlock(), 293 | // but then we'd need a MEMBAR after the ST. 294 | _Event = 0 ; 295 | status = pthread_mutex_unlock(_mutex); 296 | assert_status(status == 0, status, "mutex_unlock"); 297 | } 298 | guarantee (_Event >= 0, "invariant") ; 299 | } 300 | 301 | } 302 | ``` 303 | 304 | pthread_cond_wait 是一个多线程的条件变量函数,cond 是 condition 的缩写,字面意思可以理解为线程在等待一个条件发生,这个条件是一个全局变量。这个方法接收两个参数,一个共享变量 _cond,一个互斥量 _mutex。而 unpark 方法在 linux 下是使用 pthread_cond_signal 实现的。park 在 windows 下则是使用 WaitForSingleObject 实现的。 305 | 306 | 当队列满时,生产者往阻塞队列里插入一个元素,生产者线程会进入 WAITING (parking) 状态。我们可以使用 jstack dump 阻塞的生产者线程看到这点: 307 | 308 | ```java 309 | "main" prio=5 tid=0x00007fc83c000000 nid=0x10164e000 waiting on condition [0x000000010164d000] 310 | java.lang.Thread.State: WAITING (parking) 311 | at sun.misc.Unsafe.park(Native Method) 312 | - parking to wait for <0x0000000140559fe8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject) 313 | at java.util.concurrent.locks.LockSupport.park(LockSupport.java:186) 314 | at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2043) 315 | at java.util.concurrent.ArrayBlockingQueue.put(ArrayBlockingQueue.java:324) 316 | at blockingqueue.ArrayBlockingQueueTest.main(ArrayBlockingQueueTest.java:11) 317 | ``` 318 | 319 | -------------------------------------------------------------------------------- /doc/chapter7.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [第7章 Java中的13个原子操作类](#%E7%AC%AC7%E7%AB%A0-java%E4%B8%AD%E7%9A%8413%E4%B8%AA%E5%8E%9F%E5%AD%90%E6%93%8D%E4%BD%9C%E7%B1%BB) 6 | - [原子更新基本类型](#%E5%8E%9F%E5%AD%90%E6%9B%B4%E6%96%B0%E5%9F%BA%E6%9C%AC%E7%B1%BB%E5%9E%8B) 7 | - [原子更新数组](#%E5%8E%9F%E5%AD%90%E6%9B%B4%E6%96%B0%E6%95%B0%E7%BB%84) 8 | - [原子更新引用类型](#%E5%8E%9F%E5%AD%90%E6%9B%B4%E6%96%B0%E5%BC%95%E7%94%A8%E7%B1%BB%E5%9E%8B) 9 | - [原子更新字段类](#%E5%8E%9F%E5%AD%90%E6%9B%B4%E6%96%B0%E5%AD%97%E6%AE%B5%E7%B1%BB) 10 | 11 | 12 | 13 | # 第7章 Java中的13个原子操作类 14 | 15 | ![](https://github.com/zaiyunduan123/java-concurrent-art/blob/master/image/7-1.png) 16 | 17 | ## 原子更新基本类型 18 | 19 | atomic包里面一共提供了13个类,分为4种类型,分别是:原子更新基本类型,原子更新数组,原子更新引用,原子更新属性,这13个类都是使用Unsafe实现的包装类。 20 | 21 | AtomicInteger的常用方法有: 22 | 1. int addAndGet(int delta):以原子的方式将输入的值与实例中的值相加,并把结果返回 23 | 2. boolean compareAndSet(int expect, int update):如果输入值等于预期值,以原子的方式将该值设置为输入的值 24 | 3. final int getAndIncrement():以原子的方式将当前值加1,并返回加1之前的值 25 | 4. void lazySet(int newValue):最终会设置成newValue,使用lazySet设置值后,可能导致其他线程在之后的一小段时间内还是可以读到旧的值。 26 | 5. int getAndSet(int newValue):以原子的方式将当前值设置为newValue,并返回设置之前的旧值 27 | 28 | 29 | 30 | getAndIncremen的实现代码如下:   31 | ```java 32 | public final int getAndIncrement() { 33 | return unsafe.getAndAddInt(this, valueOffset, 1); 34 | } 35 | ``` 36 | 我们可以看到getAndIncrement调用了unsafe的getAndAddInt,getAndAddInt的实现: 37 | 38 | ```java 39 | public final int getAndAddInt(Object var1, long var2, int var4) { 40 | int var5; 41 | do { 42 | var5 = this.getIntVolatile(var1, var2); 43 | } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); 44 | 45 | return var5; 46 | } 47 | ``` 48 | getAndAddInt调用了Unsafe的native方法:getIntVolatile和compareAndSwapInt,在do-while循环中先取得当前值,然后通过CAS判断当前值是否和current一致,如果一致意味着值没被其他线程修改过,把当前值设置为当前值+var4,,如果不相等程序进入信的CAS循环。 49 | 50 | 由于atomic只提供了int,long和boolean的原子操作类,那么其他的基本类型,如byte,char,float,double如何实现原子操作呢,原子操作都是通过Unsafe实现的,让我们看一下Unsafe的实现 51 | ```java 52 | public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5); 53 | 54 | public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5); 55 | 56 | public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6); 57 | ``` 58 | 通过代码,我们发现Unsafe只提供了3种CAS方法:compareAndSwapObject、compareAndSwapInt和compareAndSwapLong,再看AtomicBoolean源码,发现它是先把Boolean转换成整型,再使用compareAndSwapInt进行CAS,所以原子更新char、float和double变量也可以用类似的思路来实现。 59 | 60 | ## 原子更新数组 61 | 62 | AtomicIntegerArray主要提供了以原子方式更新数组里的整数,常见方法如下: 63 | 1. int addAndGet(int i, int delta):以原子的方式将输入值与数组中索引为i的元素相加 64 | 2. boolean compareAndSet(int i, int expect, int update):如果当前值等于预期值,则以原子方式将数组位置i的元素设置成update值。   65 | 66 | ```java 67 | public class AtomicIntegerArrayTest { 68 | 69 | private static int[] value = new int[]{1,2,3}; 70 | private static AtomicIntegerArray atomicInteger = new AtomicIntegerArray(value); 71 | 72 | public static void main(String[] args){ 73 | atomicInteger.getAndSet(0,12); 74 | System.out.println(atomicInteger.get(0)); 75 | System.out.println(value[0]); 76 | } 77 | } 78 | ``` 79 | 需要注意的是,数组value通过构造方法传递进去,然后AtomicIntegerArray会将当前数组复制一份,所以当AtomicIntegerArray对内部的数组元素进行修改时,不会影响传入的数组。 80 | 81 | 82 | ## 原子更新引用类型 83 | 原子更新基本类型的AtomicInteger只能更新一个变量,如果要原子更新多个变量,就需要使用原子更新引用类型提供的类了。原子引用类型atomic包主要提供了以下几个类: 84 | 1. AtomicReference:原子更新引用类型 85 | 2. AtomicReferenceFieldUpdater:原子更新引用类型里的字段 86 | 3. AtomicMarkableReference:原子更新带有标记位的引用类型。可以原子更新一个布尔类型的标记位和引用类型。构造方法是AtomicMarkableReference(V 87 | initialRef,booleaninitialMark) 88 | 89 | ## 原子更新字段类 90 | 如果需要原子更新某个对象的某个字段,就需要使用原子更新属性的相关类,atomic中提供了一下几个类用于原子更新属性: 91 | 1. tomicIntegerFieldUpdater:原子更新整形属性的更新器 92 | 2. AtomicLongFieldUpdater:原子更新长整形的更新器 93 | 3. AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于原子的更新数据和数据的版本号,可以解决使用CAS进行原子更新时可能出现的ABA问题。 -------------------------------------------------------------------------------- /doc/chapter8.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [第8章 Java中的并发工具类](#%E7%AC%AC8%E7%AB%A0-java%E4%B8%AD%E7%9A%84%E5%B9%B6%E5%8F%91%E5%B7%A5%E5%85%B7%E7%B1%BB) 6 | - [等待多线程完成CountDownLatch](#%E7%AD%89%E5%BE%85%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%AE%8C%E6%88%90countdownlatch) 7 | - [用法](#%E7%94%A8%E6%B3%95) 8 | - [原理](#%E5%8E%9F%E7%90%86) 9 | - [初始化](#%E5%88%9D%E5%A7%8B%E5%8C%96) 10 | - [获得锁](#%E8%8E%B7%E5%BE%97%E9%94%81) 11 | - [释放锁](#%E9%87%8A%E6%94%BE%E9%94%81) 12 | - [总结](#%E6%80%BB%E7%BB%93) 13 | - [同步屏障CyclicBarrier](#%E5%90%8C%E6%AD%A5%E5%B1%8F%E9%9A%9Ccyclicbarrier) 14 | - [原理](#%E5%8E%9F%E7%90%86-1) 15 | - [CyclicBarrier和CountDownLatch的区别](#cyclicbarrier%E5%92%8Ccountdownlatch%E7%9A%84%E5%8C%BA%E5%88%AB) 16 | - [控制并发线程数的Semaphore](#%E6%8E%A7%E5%88%B6%E5%B9%B6%E5%8F%91%E7%BA%BF%E7%A8%8B%E6%95%B0%E7%9A%84semaphore) 17 | - [线程间交换数据的Exchanger](#%E7%BA%BF%E7%A8%8B%E9%97%B4%E4%BA%A4%E6%8D%A2%E6%95%B0%E6%8D%AE%E7%9A%84exchanger) 18 | 19 | 20 | 21 | # 第8章 Java中的并发工具类 22 | 23 | ## 等待多线程完成CountDownLatch 24 | CountDownLatch这个类,作用感觉和join很像,首先来看一下join,join用于让当前执行线程等待join线程执行结束,其实现原理是不停检查join线程是否存活,如果join线程存活则让当前线程永远等待。 25 | 26 | CountDownLatch是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。 27 | 28 | ### 用法 29 | CountDownLatch的构造函数接收一个int类型的参数作为计数器,如果你想等待N个点完成,这里就传入N。 30 | 31 | 当我们调用CountDownLatch的countDown方法时,N就会减1,CountDownLatch的await方法会阻塞当前线程,直到N变成零。由于countDown方法可以用在任何地方,所以这里说的N个点,可以是N个线程,也可以是1个线程里的N个执行步骤。用在多个线程时,只需要把这个CountDownLatch的引用传递到线程里即可。 32 | 33 | ### 原理 34 | 35 | #### 初始化 36 | CountDownLatch内部使用了共享锁,new CountDownLatch(int )的构造函数传入n为计数器,其实设置state值为n,表示n个锁资源占用 37 | ```java 38 | public CountDownLatch(int var1) { 39 | if (var1 < 0) { 40 | throw new IllegalArgumentException("count < 0"); 41 | } else { 42 | this.sync = new CountDownLatch.Sync(var1); 43 | } 44 | } 45 | 46 | 47 | Sync(int var1) { 48 | this.setState(var1); 49 | } 50 | ``` 51 | #### 获得锁 52 | ```java 53 | protected int tryAcquireShared(int acquires) { 54 | return (getState() == 0) ? 1 : -1; 55 | } 56 | ``` 57 | 共享锁有个约定,返回有三种情况。 58 | - 0为获取锁且没有其他资源 59 | - 正数 获取锁并且还有其他资源 60 | - 负数 获取锁资源失败 61 | 62 | 共享锁在tryAcquireShared返回大于0的值的时候,会唤醒其他停顿状态加锁线程。由于没有对state的增加操作,所以当state变成0的时候,所有尝试加锁的线程都会被唤醒。 63 | 64 | #### 释放锁 65 | 66 | ```java 67 | protected boolean tryReleaseShared(int var1) { 68 | int var2; 69 | int var3; 70 | do { 71 | var2 = this.getState(); 72 | if (var2 == 0) { 73 | return false; 74 | } 75 | 76 | var3 = var2 - 1; 77 | } while(!this.compareAndSetState(var2, var3)); 78 | 79 | return var3 == 0; 80 | } 81 | ``` 82 | 释放锁的操作,获取state,如果等于0,说明当前没有锁资源也就无法释放,返回false;否则执行正常操作 state - 1,当只有state变成0的时候,才返回true,tryReleaseShared返回true的时候会触发唤醒其他加锁线程的操作。 83 | 84 | countDown是释放锁,最终会调用到tryReleaseShared。 85 | ```java 86 | public void countDown() { 87 | sync.releaseShared(1); 88 | } 89 | ``` 90 | 91 | #### 总结 92 | CountDownLatch就是一个通过构造方法初始化锁资源占用数后,然后通过countDown方法不断释放锁的过程。 93 | 94 | 95 | 96 | 97 | ## 同步屏障CyclicBarrier 98 | CyclicBarrier要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。 99 | 100 | 说白了,就是等全部人都到齐了才出发,比如 101 | 102 | 如果把new CyclicBarrier(2)修改成new CyclicBarrier(3),则主线程和子线程会永远等待,因为没有第三个线程执行await方法,即没有第三个线程到达屏障,所以之前到达屏障的两个线程都不会继续执行。 103 | 104 | 因为三个线程不到齐,所以两个线程是无法执行的 105 | 106 | 107 | 108 | ### 原理 109 | 在CyclicBarrier类的内部有一个计数器,每个线程在到达屏障点的时候都会调用await方法将自己阻塞,此时计数器会减1,当计数器减为0的时候所有因调用await方法而被阻塞的线程将被唤醒。 110 | 111 | CyclicBarrier内部是通过条件队列trip来对线程进行阻塞的,并且其内部维护了两个int型的变量parties和count 112 | 1. parties表示每次拦截的线程数,该值在构造时进行赋值。 113 | 2. count是内部计数器,它的初始值和parties相同,以后随着每次await方法的调用而减1,直到减为0就将所有线程唤醒。 114 | 115 | ```java 116 | private int dowait(boolean timed, long nanos) 117 | throws InterruptedException, BrokenBarrierException, TimeoutException { 118 | final ReentrantLock lock = this.lock; 119 | lock.lock(); 120 | try { 121 | final Generation g = generation; 122 | //检查当前栅栏是否被打翻 123 | if (g.broken) { 124 | throw new BrokenBarrierException(); 125 | } 126 | //检查当前线程是否被中断 127 | if (Thread.interrupted()) { 128 | //如果当前线程被中断会做以下三件事 129 | //1.打翻当前栅栏 130 | //2.唤醒拦截的所有线程 131 | //3.抛出中断异常 132 | breakBarrier(); 133 | throw new InterruptedException(); 134 | } 135 | //每次都将计数器的值减1 136 | int index = --count; 137 | //计数器的值减为0则需唤醒所有线程并转换到下一代 138 | if (index == 0) { 139 | boolean ranAction = false; 140 | try { 141 | //唤醒所有线程前先执行指定的任务 142 | final Runnable command = barrierCommand; 143 | if (command != null) { 144 | command.run(); 145 | } 146 | ranAction = true; 147 | //唤醒所有线程并转到下一代 148 | nextGeneration(); 149 | return 0; 150 | } finally { 151 | //确保在任务未成功执行时能将所有线程唤醒 152 | if (!ranAction) { 153 | breakBarrier(); 154 | } 155 | } 156 | } 157 | 158 | //如果计数器不为0则执行此循环 159 | for (;;) { 160 | try { 161 | //根据传入的参数来决定是定时等待还是非定时等待 162 | if (!timed) { 163 | trip.await(); 164 | }else if (nanos > 0L) { 165 | nanos = trip.awaitNanos(nanos); 166 | } 167 | } catch (InterruptedException ie) { 168 | //若当前线程在等待期间被中断则打翻栅栏唤醒其他线程 169 | if (g == generation && ! g.broken) { 170 | breakBarrier(); 171 | throw ie; 172 | } else { 173 | //若在捕获中断异常前已经完成在栅栏上的等待, 174 | //则直接调用中断操作 175 | Thread.currentThread().interrupt(); 176 | } 177 | } 178 | //如果线程因为打翻栅栏操作而被唤醒则抛出异常 179 | if (g.broken) { 180 | throw new BrokenBarrierException(); 181 | } 182 | //如果线程因为换代操作而被唤醒则返回计数器的值 183 | if (g != generation) { 184 | return index; 185 | } 186 | //如果线程因为时间到了而被唤醒则打翻栅栏并抛出异常 187 | if (timed && nanos <= 0L) { 188 | breakBarrier(); 189 | throw new TimeoutException(); 190 | } 191 | } 192 | } finally { 193 | lock.unlock(); 194 | } 195 | } 196 | ``` 197 | 198 | dowait方法中每次都将count减1,减完后立马进行判断看看是否等于0 199 | 1. 如果等于0的话就会先去执行之前指定好的任务,执行完之后再调用nextGeneration方法将栅栏转到下一代,在该方法中会将所有线程唤醒,将计数器的值重新设为parties,最后会重新设置栅栏代次。 200 | 2. 如果不等于0的话就进入for循环,根据参数来决定是调用trip.awaitNanos(nanos)还是trip.await()方法,这两方法对应着定时和非定时等待。如果在等待过程中当前线程被中断就会执行breakBarrier方法,该方法叫做打破栅栏,意味着游戏在中途被掐断,设置generation的broken状态为true并唤醒所有线程。同时这也说明在等待过程中有一个线程被中断整盘游戏就结束,所有之前被阻塞的线程都会被唤醒。 201 | 202 | 203 | 204 | ### CyclicBarrier和CountDownLatch的区别 205 | #### 定义和特点 206 | - CountDownLatch,允许一个或多个线程等待其他线程完成操作,首先初始化一个计数值N,然后通过调用 await可以让当前线程阻塞,直到计数值N在被调用N次countDown置为0后,再继续执行。 207 | - CyclicBarrier ,见名知意,是一个可以循环使用(Cyclic)的屏障(Barrier),让一组线程到达一个屏障时被阻塞,直到最后一个线程到达屏障时,屏障才会打开,所有被屏障拦截的线程才继续工作。 208 | 209 | #### 实现原理 210 | - CountDownLatch :同步功能是基于 AQS 实现的,CountDownLatch 使用 AQS 中的 state 成员变量作为计数器,在 state 不为0的情况下,凡是调用 await 方法的线程将会被阻塞,并被放入 AQS 所维护的同步队列中进行等待。 211 | - CyclicBarrier :基于重入锁 ReentrantLock 实现,线程调用 await 方法需要先获取锁才能访问。在最后一个线程访问 await 方法前,其他线程进入 await 方法中后,会调用 Condition 的 await 方法进入等待状态;在最后一个线程进入 CyclicBarrier的 await 方法后,该线程将会调用 Condition 的 signalAll 方法唤醒所有处于等待状态中的线程;最后一个进入 await 的线程还会重置 CyclicBarrier 的状态,使其可以重复使用。 212 | 213 | #### 适用场景 214 | - CountDownLatch :一个线程需要等待其它线程完成操作后,才能进行后续的操作、 215 | - CyclicBarrier :需要所有的子任务都完成时,才执行主任务。 216 | 217 | #### 计数方式 218 | CountDownLatch:减计数方式;计数为0的时候释放所有等待的线程;计数为0时,无法重置,只能使用一次;调用countDown方法减一,await方法阻塞。 219 | CyclicBarrier:加计数方式;计数达到指定值时释放所有等待线程;计数达到指定值时,计数置为0,重新开始;可重复使用。 220 | 221 | ## 控制并发线程数的Semaphore 222 | 223 | Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源。 224 | 225 | Semaphore可以用于做流量控制,特别是公用资源有限的应用场景,比如数据库连接。假如有一个需求,要读取几万个文件的数据,因为都是IO密集型任务,我们可以启动几十个线程并发地读取,但是如果读到内存后,还需要存储到数据库中,而数据库的连接数只有10个,这时我们必须控制只有10个线程同时获取数据库连接保存数据,否则会报错无法获取数据库连接。这个时候,就可以使用Semaphore来做流量控制 226 | 227 | ```java 228 | 229 | public class SemaphoreTest { 230 | private static final int THREAD_COUNT = 30; 231 | private static ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_COUNT); 232 | private static Semaphore s = new Semaphore(10); 233 | 234 | public static void main(String[] args) { 235 | for (int i = 0; i < THREAD_COUNT; i++) { 236 | threadPool.execute(new Runnable() { 237 | @Override 238 | public void run() { 239 | try { 240 | s.acquire(); 241 | System.out.println("save data"); 242 | s.release(); 243 | } catch (InterruptedException e) { 244 | } 245 | } 246 | }); 247 | } 248 | threadPool.shutdown(); 249 | } 250 | } 251 | ``` 252 | 253 | 虽然有30个线程在执行,但是只允许10个并发执行。Semaphore的构造方法Semaphore(int permits)接受一个整型的数字,表示可用的许可证数量。Semaphore(10)表示允许10个线程获取许可证,也就是最大并发数是10。Semaphore的用法也很简单,首先线程使用Semaphore的acquire()方法获取一个许可证,使用完之后调用release()方法归还许可证。 254 | 255 | ## 线程间交换数据的Exchanger 256 | 257 | Exchanger(交换者)是一个用于线程间协作的工具类。Exchanger用于进行线程间的数据交换。它提供一个同步点,在这个同步点,两个线程可以交换彼此的数据。这两个线程通过exchange方法交换数据,如果第一个线程先执行exchange()方法,它会一直等待第二个线程也执行exchange方法,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。 258 | 259 | 260 | -------------------------------------------------------------------------------- /image/0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaiyunduan123/java-concurrent-art/6ef1cf884385bf050cec7140fc5127ca900e0e6b/image/0.jpg -------------------------------------------------------------------------------- /image/10-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaiyunduan123/java-concurrent-art/6ef1cf884385bf050cec7140fc5127ca900e0e6b/image/10-1.png -------------------------------------------------------------------------------- /image/10-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaiyunduan123/java-concurrent-art/6ef1cf884385bf050cec7140fc5127ca900e0e6b/image/10-2.png -------------------------------------------------------------------------------- /image/10-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaiyunduan123/java-concurrent-art/6ef1cf884385bf050cec7140fc5127ca900e0e6b/image/10-3.png -------------------------------------------------------------------------------- /image/10-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaiyunduan123/java-concurrent-art/6ef1cf884385bf050cec7140fc5127ca900e0e6b/image/10-4.png -------------------------------------------------------------------------------- /image/10-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaiyunduan123/java-concurrent-art/6ef1cf884385bf050cec7140fc5127ca900e0e6b/image/10-5.png -------------------------------------------------------------------------------- /image/2-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaiyunduan123/java-concurrent-art/6ef1cf884385bf050cec7140fc5127ca900e0e6b/image/2-1.png -------------------------------------------------------------------------------- /image/5-1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaiyunduan123/java-concurrent-art/6ef1cf884385bf050cec7140fc5127ca900e0e6b/image/5-1.jpeg -------------------------------------------------------------------------------- /image/5-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaiyunduan123/java-concurrent-art/6ef1cf884385bf050cec7140fc5127ca900e0e6b/image/5-1.png -------------------------------------------------------------------------------- /image/5-2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaiyunduan123/java-concurrent-art/6ef1cf884385bf050cec7140fc5127ca900e0e6b/image/5-2.jpeg -------------------------------------------------------------------------------- /image/5-3.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaiyunduan123/java-concurrent-art/6ef1cf884385bf050cec7140fc5127ca900e0e6b/image/5-3.jpeg -------------------------------------------------------------------------------- /image/5-4.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaiyunduan123/java-concurrent-art/6ef1cf884385bf050cec7140fc5127ca900e0e6b/image/5-4.jpeg -------------------------------------------------------------------------------- /image/5-5.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaiyunduan123/java-concurrent-art/6ef1cf884385bf050cec7140fc5127ca900e0e6b/image/5-5.jpeg -------------------------------------------------------------------------------- /image/6-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaiyunduan123/java-concurrent-art/6ef1cf884385bf050cec7140fc5127ca900e0e6b/image/6-1.png -------------------------------------------------------------------------------- /image/7-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaiyunduan123/java-concurrent-art/6ef1cf884385bf050cec7140fc5127ca900e0e6b/image/7-1.png -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.1.1.RELEASE 9 | 10 | 11 | com.concurrent 12 | java 13 | 0.0.1-SNAPSHOT 14 | java 15 | Demo project for Spring Boot 16 | 17 | 18 | 1.8 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-web 25 | 26 | 27 | 28 | org.springframework.boot 29 | spring-boot-starter-test 30 | test 31 | 32 | 33 | 34 | 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-maven-plugin 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/main/java/com/concurrent/JavaApplication.java: -------------------------------------------------------------------------------- 1 | package com.concurrent; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class JavaApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(JavaApplication.class, args); 11 | } 12 | 13 | } 14 | 15 | -------------------------------------------------------------------------------- /src/main/java/com/concurrent/chapter1/ConcurrentTest.java: -------------------------------------------------------------------------------- 1 | package com.concurrent.chapter1; 2 | 3 | /** 4 | * @Author jiangyunxiong 5 | * @Date 2019/1/6 下午9:17 6 | * 7 | * 并发pk串行 8 | */ 9 | public class ConcurrentTest { 10 | 11 | private static final long count = 10000L; 12 | 13 | public static void main(String[] args) throws InterruptedException { 14 | concurrency(); 15 | serial(); 16 | 17 | } 18 | 19 | private static void concurrency() throws InterruptedException { 20 | long start = System.currentTimeMillis(); 21 | 22 | Thread thread = new Thread(new Runnable() { 23 | @Override 24 | public void run() { 25 | int a = 0; 26 | for (long i = 0; i < count; i++) { 27 | a += 5; 28 | } 29 | } 30 | }); 31 | thread.start(); 32 | int b = 0; 33 | for (int i = 0; i < count; i++) { 34 | b--; 35 | } 36 | thread.join(); 37 | long t = System.currentTimeMillis() - start; 38 | System.out.println("concurrency:" + t + "ms,b=" + b); 39 | } 40 | 41 | private static void serial() { 42 | long start = System.currentTimeMillis(); 43 | 44 | int a = 0; 45 | for (long i = 0; i < count; i++) { 46 | a += 5; 47 | } 48 | int b = 0; 49 | for (int i = 0; i < count; i++) { 50 | b--; 51 | } 52 | long t = System.currentTimeMillis() - start; 53 | System.out.println("concurrency:" + t + "ms,b=" + b + "a=" + a); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/concurrent/chapter1/DeadLockDemo.java: -------------------------------------------------------------------------------- 1 | package com.concurrent.chapter1; 2 | 3 | /** 4 | * @Author jiangyunxiong 5 | * @Date 2019/1/6 下午10:16 6 | * 7 | * 死锁 8 | */ 9 | public class DeadLockDemo { 10 | private static String A = "A"; 11 | private static String B = "B"; 12 | 13 | public static void main(String[] args) { 14 | new DeadLockDemo().deadLock(); 15 | } 16 | 17 | private void deadLock() { 18 | 19 | Thread t1 = new Thread(new Runnable() { 20 | @Override 21 | public void run() { 22 | synchronized (A) { 23 | try { 24 | Thread.currentThread().sleep(2000); 25 | } catch (InterruptedException e) { 26 | e.printStackTrace(); 27 | } 28 | synchronized (B){ 29 | System.out.println("1"); 30 | } 31 | } 32 | } 33 | }); 34 | Thread t2 = new Thread(new Runnable() { 35 | @Override 36 | public void run() { 37 | synchronized (B) { 38 | synchronized (A){ 39 | System.out.println("2"); 40 | } 41 | } 42 | } 43 | }); 44 | 45 | t1.start(); 46 | t2.start(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/concurrent/chapter2/Counter.java: -------------------------------------------------------------------------------- 1 | package com.concurrent.chapter2; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.concurrent.atomic.AtomicInteger; 6 | 7 | /** 8 | * @Author jiangyunxiong 9 | * @Date 2019/1/6 下午10:25 10 | * 11 | * 实现基于CAS线程安全的计数器方法safeCount和一个非线程安全的计数器count 12 | */ 13 | public class Counter { 14 | private AtomicInteger atomic1 = new AtomicInteger(0); 15 | 16 | private int i = 0; 17 | 18 | public static void main(String[] args) { 19 | 20 | final Counter cas = new Counter(); 21 | List ts = new ArrayList<>(600); 22 | long start = System.currentTimeMillis(); 23 | for (int j = 0; j < 100; j++) { 24 | Thread t = new Thread(new Runnable() { 25 | @Override 26 | public void run() { 27 | for (int i = 0; i < 10000; i++) { 28 | cas.count(); 29 | cas.safeCount(); 30 | } 31 | } 32 | }); 33 | ts.add(t); 34 | } 35 | for (Thread t : ts) { 36 | t.start(); 37 | } 38 | for (Thread t : ts) { 39 | try { 40 | t.join(); 41 | } catch (InterruptedException e) { 42 | e.printStackTrace(); 43 | } 44 | } 45 | System.out.println(cas.i); 46 | System.out.println(cas.atomic1.get()); 47 | System.out.println(System.currentTimeMillis() - start); 48 | } 49 | 50 | //使用CAS实现线程安全计数器 51 | private void safeCount() { 52 | 53 | for (; ; ) { 54 | int i = atomic1.get(); 55 | boolean suc = atomic1.compareAndSet(i, ++i); 56 | if (suc) { 57 | break; 58 | } 59 | } 60 | } 61 | 62 | //非线程安全计数器 63 | private void count() { 64 | i++; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/concurrent/chapter3/DoubleCheckedLocking.java: -------------------------------------------------------------------------------- 1 | package com.concurrent.chapter3; 2 | 3 | /** 4 | * @Author jiangyunxiong 5 | * @Date 2019/1/7 上午12:04 6 | * 7 | * 有问题的双重校验锁定 8 | */ 9 | public class DoubleCheckedLocking { 10 | private static Instance instance; 11 | 12 | public static Instance getInstance() { 13 | if (instance == null) {//第一次检查 14 | synchronized (DoubleCheckedLocking.class) {//加锁 15 | if (instance == null) {//第二次检查 16 | instance = new Instance();//问题的根源,instance不为null不代表已经初始化了,分配内存地址和初始化可能会发生重排序 17 | } 18 | } 19 | } 20 | return instance; 21 | } 22 | 23 | 24 | static class Instance {} 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/concurrent/chapter3/FinalReferenceEscapeExample.java: -------------------------------------------------------------------------------- 1 | package com.concurrent.chapter3; 2 | 3 | /** 4 | * @Author jiangyunxiong 5 | * @Date 2019/1/6 下午11:54 6 | * 7 | * 对象引用逃逸 8 | */ 9 | public class FinalReferenceEscapeExample { 10 | final int i; 11 | static FinalReferenceEscapeExample obj; 12 | 13 | public FinalReferenceEscapeExample() { 14 | i = 1;//写final域 15 | obj = this;//this引用在这里"逸出" 16 | } 17 | 18 | public static void writer() { 19 | new FinalReferenceEscapeExample(); 20 | } 21 | 22 | public static void reader() { 23 | if (obj != null) {//仍然可能无法看到final域被初始化后的值 24 | int temp = obj.i;//这里将读取到final域初始化之前的值 25 | } 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/concurrent/chapter3/ReentrantLockExample.java: -------------------------------------------------------------------------------- 1 | package com.concurrent.chapter3; 2 | 3 | import java.util.concurrent.locks.ReentrantLock; 4 | 5 | /** 6 | * @Author jiangyunxiong 7 | * @Date 2019/1/6 下午11:49 8 | * 9 | * 锁内存 10 | */ 11 | public class ReentrantLockExample { 12 | 13 | int a = 0; 14 | ReentrantLock lock = new ReentrantLock(); 15 | 16 | public void writer() { 17 | lock.lock(); //获取锁 18 | try { 19 | a++; 20 | } finally { 21 | lock.unlock();//释放锁 22 | } 23 | } 24 | 25 | public void reader() { 26 | lock.lock(); //获取锁 27 | try { 28 | int i = a; 29 | } finally { 30 | lock.unlock();//释放锁 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/concurrent/chapter4/ConnectionDriver.java: -------------------------------------------------------------------------------- 1 | package com.concurrent.chapter4; 2 | import java.lang.reflect.InvocationHandler; 3 | import java.lang.reflect.Method; 4 | import java.lang.reflect.Proxy; 5 | import java.sql.Connection; 6 | import java.util.concurrent.TimeUnit; 7 | /** 8 | * @Auther: Jesper 9 | * @Date: 2019/1/8 10:09 10 | * @Description: 11 | */ 12 | public class ConnectionDriver { 13 | 14 | static class ConnectionHandler implements InvocationHandler{ 15 | @Override 16 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 17 | if(method.getName().equals("commit")){ 18 | TimeUnit.MILLISECONDS.sleep(100); 19 | } 20 | return null; 21 | } 22 | } 23 | 24 | public static Connection createConnection() { 25 | 26 | return (Connection) Proxy.newProxyInstance(ConnectionDriver.class.getClassLoader(),new Class[] {Connection.class},new ConnectionHandler()); 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/concurrent/chapter4/ConnectionPool.java: -------------------------------------------------------------------------------- 1 | package com.concurrent.chapter4; 2 | 3 | import java.sql.Connection; 4 | import java.util.LinkedList; 5 | 6 | /** 7 | * @Auther: Jesper 8 | * @Date: 2019/1/8 09:55 9 | * @Description: 使用等待超时模式来构造一个简单的数据库连接池 10 | */ 11 | public class ConnectionPool { 12 | private LinkedList pool = new LinkedList<>(); 13 | 14 | public ConnectionPool(int initialSize) { 15 | if (initialSize > 0) { 16 | for (int i = 0; i < initialSize; i++) { 17 | pool.addLast(ConnectionDriver.createConnection()); 18 | } 19 | } 20 | } 21 | 22 | public void releaseConnection(Connection connection) { 23 | if (connection == null) { 24 | synchronized (pool) { 25 | //连接释放后需要进行通知,这样其他消费者能够感知到连接池中已经归还了一个连接 26 | pool.addLast(connection); 27 | pool.notifyAll(); 28 | } 29 | } 30 | } 31 | 32 | //在mills内无法获取连接,将返回null 33 | public Connection fetchConnection(long mills) throws InterruptedException { 34 | synchronized (pool) { 35 | //完全超时 36 | if (mills <= 0) { 37 | while (pool.isEmpty()) { 38 | pool.wait(); 39 | } 40 | return pool.removeFirst(); 41 | } else { 42 | long future = System.currentTimeMillis() + mills; 43 | long remaining = mills; 44 | while (pool.isEmpty() && remaining > 0) { 45 | pool.wait(remaining); 46 | remaining = future - System.currentTimeMillis(); 47 | } 48 | Connection result = null; 49 | if (!pool.isEmpty()){ 50 | result = pool.removeFirst(); 51 | } 52 | return result; 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/concurrent/chapter4/MultiThread.java: -------------------------------------------------------------------------------- 1 | package com.concurrent.chapter4; 2 | 3 | import java.lang.management.ManagementFactory; 4 | import java.lang.management.ThreadInfo; 5 | import java.lang.management.ThreadMXBean; 6 | 7 | /** 8 | * @Author jiangyunxiong 9 | * @Date 2019/1/7 下午9:04 10 | *

11 | * 使用JMX来查看一个普通的Java程序包含那些线程 12 | */ 13 | public class MultiThread { 14 | public static void main(String[] args) { 15 | 16 | //获取Java线程管理MXBean 17 | ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); 18 | //不需要获取同步的monitor和synchronizer信息,仅获取线程和线程堆栈信息 19 | ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false); 20 | //遍历线程信息,打印线程id和线程名称信息 21 | for (ThreadInfo threadInfo : threadInfos) { 22 | System.out.println("[" + threadInfo.getThreadId() + "]" + threadInfo.getThreadName()); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/concurrent/chapter4/Priority.java: -------------------------------------------------------------------------------- 1 | package com.concurrent.chapter4; 2 | 3 | import java.util.ArrayList; 4 | import java.util.concurrent.TimeUnit; 5 | 6 | /** 7 | * @Author jiangyunxiong 8 | * @Date 2019/1/7 上午12:13 9 | *

10 | * 线程优先级 11 | * 优先级的范围从1到10,默认是5,优先级高的线程分配的时间片的数量要多于优先级低的线程,优先级高的线程被执行的概率高,并不是优先执行。 12 | */ 13 | public class Priority { 14 | private static volatile boolean notStart = true; 15 | private static volatile boolean notEnd = true; 16 | 17 | public static void main(String[] args) throws InterruptedException { 18 | ArrayList jobs = new ArrayList<>(); 19 | for (int i = 0; i < 10; i++) { 20 | int priority = i < 5 ? Thread.MIN_PRIORITY : Thread.MAX_PRIORITY; 21 | Job job = new Job(priority); 22 | jobs.add(job); 23 | Thread thread = new Thread(job, "Thread:" + i); 24 | thread.setPriority(priority); 25 | thread.start(); 26 | } 27 | notStart = false; 28 | TimeUnit.SECONDS.sleep(10); 29 | notEnd = false; 30 | for (Job job : jobs) { 31 | System.out.println("Job Priority:" + job.priority + ",Count:" 32 | + job.jobCount); 33 | } 34 | } 35 | 36 | static class Job implements Runnable { 37 | 38 | private int priority; 39 | private long jobCount; 40 | 41 | public Job(int priority) { 42 | this.priority = priority; 43 | } 44 | 45 | @Override 46 | public void run() { 47 | while (notStart) { 48 | Thread.yield(); 49 | } 50 | while (notEnd) { 51 | Thread.yield(); 52 | jobCount++; 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/concurrent/chapter4/ThreadState.java: -------------------------------------------------------------------------------- 1 | package com.concurrent.chapter4; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | /** 6 | * @Author jiangyunxiong 7 | * @Date 2019/1/7 下午9:39 8 | *

9 | * 线程的状态 10 | */ 11 | public class ThreadState { 12 | 13 | public static void main(String[] args) { 14 | 15 | new Thread(new TimeWaiting(), "TimeWaitingThread").start(); 16 | new Thread(new Waiting(), "WaitingThread").start(); 17 | //使用两个Blocked线程,一个获取锁成功,另一个被阻塞 18 | new Thread(new Blocked(), "BlockedThread-1").start(); 19 | Thread thread = new Thread(new Blocked(), "BlockedThread-2"); 20 | thread.start(); 21 | thread.interrupt(); 22 | } 23 | 24 | //该线程不断地进行睡眠 25 | static class TimeWaiting implements Runnable { 26 | @Override 27 | public void run() { 28 | while (true) { 29 | try { 30 | TimeUnit.SECONDS.sleep(100); 31 | } catch (InterruptedException e) { 32 | e.printStackTrace(); 33 | } 34 | } 35 | } 36 | } 37 | 38 | //该线程在Waiting.class实例上等待 39 | static class Waiting implements Runnable { 40 | 41 | @Override 42 | public void run() { 43 | while (true){ 44 | synchronized (Waiting.class){ 45 | try { 46 | Waiting.class.wait(); 47 | }catch (InterruptedException e){ 48 | e.printStackTrace(); 49 | } 50 | } 51 | } 52 | } 53 | } 54 | 55 | //该线程在Blocked.class实例上加锁后,不会释放该锁 56 | static class Blocked implements Runnable{ 57 | @Override 58 | public void run() { 59 | synchronized (Blocked.class){ 60 | while (true){ 61 | try { 62 | TimeUnit.SECONDS.sleep(100); 63 | } catch (InterruptedException e) { 64 | e.printStackTrace(); 65 | } 66 | } 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/concurrent/chapter5/BoundedQueue.java: -------------------------------------------------------------------------------- 1 | package com.concurrent.chapter5; 2 | 3 | import java.util.concurrent.locks.Condition; 4 | import java.util.concurrent.locks.Lock; 5 | import java.util.concurrent.locks.ReentrantLock; 6 | 7 | /** 8 | * @Author jiangyunxiong 9 | * @Date 2019/1/9 下午11:58 10 | *

11 | * Condition使用示例 12 | *

13 | * 实现一个简单的有界队列,队列为空时,队列的删除操作将会阻塞直到队列中有新的元素,队列已满时,队列的插入操作将会阻塞直到队列出现空位。 14 | */ 15 | public class BoundedQueue { 16 | 17 | private Object[] items; 18 | 19 | //添加的下标,删除的下标和数组当前数量 20 | private int addIndex, removeIndex, count; 21 | 22 | private Lock lock = new ReentrantLock(); 23 | private Condition notEmpty = lock.newCondition(); 24 | private Condition notFull = lock.newCondition(); 25 | 26 | public BoundedQueue(int size) { 27 | items = new Object[size]; 28 | } 29 | 30 | //队列已满时,队列的插入操作将会阻塞直到队列出现空位 31 | public void add(T t) throws InterruptedException { 32 | lock.lock(); 33 | try { 34 | while (count == items.length) { 35 | notFull.await(); 36 | } 37 | items[addIndex] = t; 38 | if (++addIndex == items.length) { 39 | addIndex = 0; 40 | } 41 | ++count; 42 | notEmpty.signal(); 43 | } finally { 44 | lock.unlock(); 45 | } 46 | } 47 | 48 | //队列为空时,队列的删除操作将会阻塞直到队列中有新的元素 49 | public T remove() throws InterruptedException { 50 | lock.lock(); 51 | try { 52 | while (count == 0) { 53 | notEmpty.await(); 54 | } 55 | Object x = items[removeIndex]; 56 | if (++removeIndex == items.length) { 57 | removeIndex = 0; 58 | } 59 | --count; 60 | notFull.signal(); 61 | return (T) x; 62 | } finally { 63 | lock.unlock(); 64 | } 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/concurrent/chapter5/Cache.java: -------------------------------------------------------------------------------- 1 | package com.concurrent.chapter5; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.concurrent.locks.Lock; 6 | import java.util.concurrent.locks.ReentrantReadWriteLock; 7 | 8 | /** 9 | * @Author jiangyunxiong 10 | * @Date 2019/1/9 下午10:00 11 | * 12 | * 实现多读单写的缓存(读写锁) 13 | */ 14 | public class Cache { 15 | 16 | static Map map = new HashMap(); 17 | static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); 18 | static Lock r = rwl.readLock(); 19 | static Lock w = rwl.writeLock(); 20 | 21 | public static final Object get(String key){ 22 | r.lock(); 23 | try { 24 | return map.get(key); 25 | }finally { 26 | r.unlock(); 27 | } 28 | } 29 | 30 | public static final Object put(String key, Object value){ 31 | w.lock(); 32 | try { 33 | return map.put(key, value); 34 | }finally { 35 | w.unlock(); 36 | } 37 | } 38 | 39 | //清除所有内容 40 | public static final void clear(){ 41 | w.lock(); 42 | try { 43 | map.clear(); 44 | }finally { 45 | w.unlock(); 46 | } 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/concurrent/chapter5/ConditionUseCase.java: -------------------------------------------------------------------------------- 1 | package com.concurrent.chapter5; 2 | 3 | import java.util.concurrent.locks.Condition; 4 | import java.util.concurrent.locks.Lock; 5 | import java.util.concurrent.locks.ReentrantLock; 6 | 7 | /** 8 | * @Author jiangyunxiong 9 | * @Date 2019/1/9 下午11:49 10 | */ 11 | public class ConditionUseCase { 12 | 13 | Lock lock = new ReentrantLock(); 14 | Condition condition = lock.newCondition(); 15 | 16 | public void conditionWait() throws InterruptedException{ 17 | lock.lock(); 18 | try{ 19 | condition.await(); 20 | }finally { 21 | lock.unlock(); 22 | } 23 | } 24 | public void conditionSignal() throws InterruptedException{ 25 | lock.lock(); 26 | try{ 27 | condition.signal(); 28 | }finally { 29 | lock.unlock(); 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/concurrent/chapter5/Mutex.java: -------------------------------------------------------------------------------- 1 | package com.concurrent.chapter5; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | import java.util.concurrent.locks.AbstractQueuedSynchronizer; 5 | import java.util.concurrent.locks.Condition; 6 | 7 | /** 8 | * @Auther: Jesper 9 | * @Date: 2019/1/8 14:58 10 | * @Description: 自定义同步器-独占锁 11 | *

12 | * 同步器自身没有实现任何接口,仅仅是定义了若干同步状态获取和释放的方法来供自定义同步组件使用 13 | * 同步器可以支持独占地获取同步状态,也可以支持共享式获取同步状态 14 | */ 15 | public class Mutex { 16 | //静态内部类,自定义同步器 17 | private static class Sync extends AbstractQueuedSynchronizer { 18 | 19 | // 当状态为0的时候获取锁 20 | @Override 21 | protected boolean tryAcquire(int arg) { 22 | if (compareAndSetState(0, 1)) {//使用CAS设置当前状态,保证状态设置的原子性 23 | setExclusiveOwnerThread(Thread.currentThread()); 24 | return true; 25 | } 26 | return false; 27 | } 28 | 29 | //释放锁,将状态设置为0 30 | @Override 31 | protected boolean tryRelease(int arg) { 32 | if (getState() == 0) { 33 | throw new IllegalMonitorStateException(); 34 | } 35 | setExclusiveOwnerThread(null); 36 | setState(0);// 设置当前同步状态 37 | return true; 38 | } 39 | 40 | // 是否处于占用状态 41 | @Override 42 | protected boolean isHeldExclusively() { 43 | return getState() == 1; 44 | } 45 | 46 | //返回一个Condition,每个condition都包含了一个condition队列 47 | Condition newCondition() { 48 | return new ConditionObject(); 49 | } 50 | } 51 | 52 | // 仅需要将操作代理到自定义同步器上即可 53 | private final Sync sync = new Sync(); 54 | 55 | public void lock() { 56 | sync.acquire(1); 57 | } 58 | 59 | public boolean tryLock() { 60 | return sync.tryAcquire(1); 61 | } 62 | 63 | public void unlock() { 64 | sync.release(1); 65 | } 66 | 67 | public Condition newCondition() { 68 | return sync.newCondition(); 69 | } 70 | 71 | public boolean isLocked() { 72 | return sync.isHeldExclusively(); 73 | } 74 | 75 | public boolean hasQueuedThreads() { 76 | return sync.hasQueuedThreads(); 77 | } 78 | 79 | public void lockInterruptibly() throws InterruptedException { 80 | sync.acquireInterruptibly(1); 81 | } 82 | 83 | public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { 84 | return sync.tryAcquireNanos(1, unit.toNanos(timeout)); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/concurrent/chapter5/TwinsLock.java: -------------------------------------------------------------------------------- 1 | package com.concurrent.chapter5; 2 | 3 | import java.util.concurrent.locks.AbstractQueuedSynchronizer; 4 | 5 | /** 6 | * @Author jiangyunxiong 7 | * @Date 2019/1/9 下午9:08 8 | * 自定义同步器-共享锁 9 | *

10 | * 在同一时刻,只允许至多两个线程同时访问,超过两个线程的访问被阻塞 11 | */ 12 | public class TwinsLock { 13 | 14 | private final Sync sync = new Sync(2); 15 | 16 | private static final class Sync extends AbstractQueuedSynchronizer { 17 | Sync(int count) { 18 | if (count <= 0) { 19 | throw new IllegalArgumentException("count must large than zero"); 20 | } 21 | setState(count); 22 | } 23 | 24 | // 自旋 + CAS 25 | public int tryAcquireShared(int reduceCount) { 26 | for (; ; ) { 27 | int current = getState(); 28 | int newCount = current - reduceCount; 29 | if (newCount < 0 || compareAndSetState(current, newCount)) { 30 | return newCount; 31 | } 32 | } 33 | } 34 | 35 | public boolean tryReleaseShare(int returnCount) { 36 | for (; ; ) { 37 | int current = getState(); 38 | int newCount = current - returnCount; 39 | if (compareAndSetState(current, newCount)){ 40 | return true; 41 | } 42 | } 43 | } 44 | } 45 | 46 | public void Lock(){ 47 | sync.tryAcquireShared(1); 48 | } 49 | 50 | public void unlock(){ 51 | sync.tryReleaseShare(1); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/concurrent/chapter7/AtomicReferenceTest.java: -------------------------------------------------------------------------------- 1 | package com.concurrent.chapter7; 2 | 3 | import java.util.concurrent.atomic.AtomicReference; 4 | 5 | /** 6 | * @Author jiangyunxiong 7 | * @Date 2019/1/20 下午8:52 8 | */ 9 | public class AtomicReferenceTest { 10 | 11 | public static AtomicReference atomicReference = new 12 | AtomicReference<>(); 13 | 14 | public static void main(String[] args) { 15 | User user = new User("conan", 15); 16 | atomicReference.set(user); 17 | User updateUser = new User("Shinici", 17); 18 | atomicReference.compareAndSet(user, updateUser); 19 | System.out.println(atomicReference.get().getName()); 20 | System.out.println(atomicReference.get().getName()); 21 | } 22 | 23 | static class User{ 24 | private String name; 25 | private int old; 26 | 27 | public User(String name, int old) { 28 | this.name = name; 29 | this.old = old; 30 | } 31 | 32 | public String getName() { 33 | return name; 34 | } 35 | 36 | public void setName(String name) { 37 | this.name = name; 38 | } 39 | 40 | public int getOld() { 41 | return old; 42 | } 43 | 44 | public void setOld(int old) { 45 | this.old = old; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/concurrent/chapter8/BankWaterService.java: -------------------------------------------------------------------------------- 1 | package com.concurrent.chapter8; 2 | 3 | import java.util.Map; 4 | import java.util.concurrent.*; 5 | 6 | /** 7 | * @Author jiangyunxiong 8 | * @Date 2019/1/20 下午10:05 9 | */ 10 | public class BankWaterService implements Runnable{ 11 | 12 | /** 13 | * 创建4个屏障,处理完之后执行当前类的run方法 14 | */ 15 | private CyclicBarrier c = new CyclicBarrier(4, this); 16 | /** 17 | * 假设只有4个sheet,所以只启动4个线程 18 | */ 19 | private Executor executor = Executors.newFixedThreadPool(4); 20 | /** 21 | * 保存每个sheet计算出的银流结果 22 | */ 23 | private ConcurrentHashMap sheetBankWaterCount = new ConcurrentHashMap(); 24 | private void count(){ 25 | for (int i = 0; i < 4; i++) { 26 | executor.execute(new Runnable() { 27 | @Override 28 | public void run() { 29 | // 计算当前sheet的银流数据,计算代码省略 30 | sheetBankWaterCount.put(Thread.currentThread().getName(), 1); 31 | // 银流计算完成,插入一个屏障 32 | try { 33 | c.await(); 34 | } catch (InterruptedException e) { 35 | e.printStackTrace(); 36 | } catch (BrokenBarrierException e) { 37 | e.printStackTrace(); 38 | } 39 | } 40 | }); 41 | } 42 | } 43 | 44 | @Override 45 | public void run() { 46 | int result = 0; 47 | for (Map.Entry sheet : sheetBankWaterCount.entrySet()) { 48 | result += sheet.getValue(); 49 | } 50 | sheetBankWaterCount.put("result", result); 51 | System.out.println(result); 52 | } 53 | 54 | public static void main(String[] args) { 55 | BankWaterService bankWaterService = new BankWaterService(); 56 | bankWaterService.count(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/com/concurrent/chapter8/CountDownLatchTest.java: -------------------------------------------------------------------------------- 1 | package com.concurrent.chapter8; 2 | 3 | import java.util.concurrent.CountDownLatch; 4 | 5 | /** 6 | * @Author jiangyunxiong 7 | * @Date 2019/1/20 下午9:11 8 | */ 9 | public class CountDownLatchTest { 10 | 11 | static CountDownLatch c = new CountDownLatch(2); 12 | 13 | public static void main(String[] args) { 14 | new Thread(new Runnable() { 15 | @Override 16 | public void run() { 17 | System.out.println(1); 18 | c.countDown(); 19 | System.out.println(2); 20 | c.countDown(); 21 | } 22 | }).start(); 23 | System.out.println(3); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/concurrent/chapter8/CyclicBarrierTest.java: -------------------------------------------------------------------------------- 1 | package com.concurrent.chapter8; 2 | 3 | import java.util.concurrent.BrokenBarrierException; 4 | import java.util.concurrent.CyclicBarrier; 5 | 6 | /** 7 | * @Author jiangyunxiong 8 | * @Date 2019/1/20 下午9:58 9 | */ 10 | public class CyclicBarrierTest { 11 | 12 | static CyclicBarrier c = new CyclicBarrier(2); 13 | 14 | public static void main(String[] args) { 15 | new Thread(new Runnable() { 16 | @Override 17 | public void run() { 18 | try { 19 | c.await(); 20 | } catch (InterruptedException e) { 21 | e.printStackTrace(); 22 | } catch (BrokenBarrierException e) { 23 | e.printStackTrace(); 24 | } 25 | System.out.println(1); 26 | } 27 | }).start(); 28 | try { 29 | c.await(); 30 | } catch (InterruptedException e) { 31 | e.printStackTrace(); 32 | } catch (BrokenBarrierException e) { 33 | e.printStackTrace(); 34 | } 35 | 36 | System.out.println(2); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/concurrent/chapter8/JoinCountDownLatchTest.java: -------------------------------------------------------------------------------- 1 | package com.concurrent.chapter8; 2 | 3 | /** 4 | * @Author jiangyunxiong 5 | * @Date 2019/1/20 下午9:13 6 | * 7 | * Join方法 8 | * 9 | * join用于让当前执行线程等待join线程执行结束,其实现原理是不停检查join线程是否存活,如果join线程存活则让当前线程永远等待。 10 | */ 11 | public class JoinCountDownLatchTest { 12 | 13 | public static void main(String[] args) throws InterruptedException { 14 | Thread parser1 = new Thread(new Runnable() { 15 | @Override 16 | public void run() { 17 | System.out.println("parser1 finish"); 18 | } 19 | }); 20 | Thread parser2 = new Thread(new Runnable() { 21 | @Override 22 | public void run() { 23 | System.out.println("parser2 finish"); 24 | } 25 | }); 26 | parser1.start(); 27 | parser2.start(); 28 | 29 | parser1.join(); 30 | parser2.join(); 31 | System.out.println("all parser finish"); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/concurrent/chapter8/SemaphoreTest.java: -------------------------------------------------------------------------------- 1 | package com.concurrent.chapter8; 2 | 3 | import java.util.concurrent.ExecutorService; 4 | import java.util.concurrent.Executors; 5 | import java.util.concurrent.Semaphore; 6 | 7 | /** 8 | * @Author jiangyunxiong 9 | * @Date 2019/1/20 下午10:54 10 | * 11 | * 12 | */ 13 | public class SemaphoreTest { 14 | private static final int THREAD_COUNT = 1000; 15 | private static ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_COUNT); 16 | 17 | private static Semaphore s = new Semaphore(3); 18 | 19 | public static void main(String[] args) { 20 | for (int i=0;i