Java 并发系列之一:java 并发体系
1. java 并发机制的底层原理实现
1.1 volatile
1.2 synchronized
1.3 原子操作
2. java 内存模型(JMM)
3. java并发基础线程
4. java 锁
5. java 并发容器
6. java阻塞队列(7个)
7. java 并发工具(4个)
8. java 原子操作类Atomic(13个)
9. java并发框架(2个)
10. txt
1 java 并发机制的底层原理实现 2 volatile 3 特性 4 volatile可见性:对一个volatile的读,总可以看到任意线程对这个变量最终的写 5 volatile原子性:volatile对单个读/写具有原子性(64位Long、Double),但是复合操作除外,例如 i++ 6 volatile有序性:编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。 7 内存语义 8 当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值立即刷新到主内存中 9 当读一个volatile变量时,JMM会把该线程对应的本地内存设置为无效,直接从主存中读取共享变量 10 volatile的写-读与 锁的释放-获取 有相同的内存效果:volatile写和锁的释放有相同的内存语义,volatile读和锁的获取有相同的内存语义。这也是增强volatile的原因,提供了一种比锁更轻量级的线程之间的通信机制。 11 重排序规则 12 1. 当第二个操作是volatile写操作时,不管第一个操作是什么,都不能重排序 13 2. 当第一个操作是volatile读操作时,不管第二个操作是什么,都不能重排序 14 3. 当第一个操作是volatile写操作,第二个操作是volatile读操作,不能重排序 15 内存语义的实现 16 内存屏障 17 内存屏障是一组处理器指令,用于实现对内存操作的顺序限制 18 基于保守策略的JMM内存屏障插入策略 19 在每个volatile写操作的前面插入一个StoreStore屏障 20 保证之前的都能刷新到主存 21 在每个volatile写操作的后面插入一个StoreLoad屏障 22 保证先写后读,能提高效率 23 在每个volatile读操作的后面插入一个LoadLoad屏障 24 在每个volatile读操作的后面插入一个LoadStore屏障 25 实际执行时,只要不改变volatile写-读的内存语义,编译器可以根据具体情况省略不必要的屏障 26 x86处理器只会对写读作重排序,故只有一个屏障StoreLoad即可正确实现volatile写-读的内存语义 27 操作系统语义 28 主存、高速缓存(线程私有)缓存一致? 29 解决方案 30 通过在总线加 Lock 锁的方式,有些是缓存锁定 31 通过缓存一致性协议 MESI协议(修改、独占、共享、无效) 32 实现原则 33 Lock前缀指令会引起处理器缓存回写到内存 34 一个处理器的缓存回写到内存会导致其他处理器的缓存失效 35 内存模型 36 内存屏障 限制重排序 37 happens-before中的volatile规则 38 volatile VS synchronized 39 volatile是轻量级的synchronized 40 使用恰当,它比synchronized使用和执行成本更低,因为不会引起线程上下文的切换和调度 41 synchronized 42 同步、重量级锁 43 只有使用Synchronized线程处于阻塞,其他Lock, AQS, LockSupport等线程都是处于等待状态 44 原理 45 synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证变量的内存可见性。 46 锁对象 47 1. 普通同步方法锁,是当前实例对象 48 2. 静态同步方法,锁是当前类的class对象 49 3. 同步方法块,锁是括号里面的对象 50 java中的每一个对象都可以作为锁 51 实现机制 52 Java对象头 53 synchronized用的锁是保存在Java对象头里的 54 包括两部分数据 55 Mark Word(标记字段) 56 Mark Word被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据,它会根据对象的状态复用自己的存储空间 57 包括:哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳 58 Klass Pointer(类型指针) 59 monitor 60 java 中 Object和Class中都关联了一个Monitor,一个monitor只能被一个线程拥有 61 Owner 活动线程 62 初始时为NULL表示当前没有任何线程拥有该monitor record, 当线程成功拥有该锁后保存线程唯一标识,当锁被释放时又设置为NULL 63 实现 64 同步代码块采用 monitorenter、monitorexit指令显示的同步 65 同步方法使用ACC_SYNCHRONIZED标记符隐式的实现 66 锁优化 67 自旋锁 68 该线程等待一段时间,不会被立即挂起,循环看持有锁的线程是否会很快释放锁 69 自旋数字难以控制(XX: preBlockSpin) 70 存在理论:线程的频繁挂起、唤醒负担较重,可以认为每个线程占有锁的时间很短,线程挂起再唤醒得不偿失。 71 缺点 72 自旋次数无法确定 73 适应性自旋锁 74 自旋的次数不再是固定的,它是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定 75 自旋成功,则可以增加自旋次数,如果获取锁经常失败,那么自旋次数会减少 76 锁消除 77 若不存在数据竞争的情况,JVM会消除锁机制 78 判断依据 79 变量逃逸 80 锁粗化 81 将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁。例如for循环内部获得锁 82 轻量级锁 83 在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗 84 通过CAS(CompareandSwap),即比较并替换,来获取和释放锁 85 缺点 86 在多线程环境下,其运行效率比重量级锁还会慢 87 性能依据 88 对于绝大部分的锁,在整个生命周期内部都是不会存在竞争的 89 偏向锁 90 为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径 91 主要尽可能避免不必要的CAS操作,如果竞争锁失败,则升级为轻量级锁 92 原子操作 93 处理器 94 使用总线锁保证原子性 95 使用缓存锁保证原子性 96 JMM 97 锁 98 JVM中除了偏向锁,其他锁(轻量级锁、互斥锁)的实现方式都用了循环CAS,即当一个线程想进入同步块的时候使用循环CAS的方式来获取锁,当它退出同步块的时候使用循环CAS释放锁。 99 JAVA1.6中,锁一共有4中状态,级别从低到高依次是:无锁状态,偏向锁状态,轻量级锁状态和重量级锁状态 100 CAS 101 Compare And Swap, 整个JUC体系最核心、最基础理论,Java中通过锁和CAS实现原子操作 102 内存地址V,旧的预期值A,要更新的值B,当且仅当内存地址V中的值等于旧的预期值A时才会将内存值V得值修改为B,否则什么也不干 103 native中存在四个参数 104 JVM中的CAS操作利用了处理器提供的CMPXCHG指令实现的。 105 缺陷 106 ABA问题 107 检查不到值的变化,实际上却变化了 108 解决方案 109 变量前追加版本号版本号 110 AtomicStampedReference 111 这个类的compareAndSet方法的作用是首先检查当前引用是否等于预期引用,并且检查当前标志是否等于预期标志,如果全部相等,则以原子的方式将该引用和该标志的值设置为给定的更新值。 112 循环时间太长 113 自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销 114 解决方法 115 JVM如果能支持处理器提供的pause指令,那么效率一定会提升 116 pause作用 117 1. 可以延迟流水线执行指令(depipeline),使CPU不会消耗过多的执行资源 118 2. 避免在退出循环的时候因内存顺序冲突而引起的CPU流水线被清空,从而提高CPU的执行效率 119 只能保证一个共享变量原则操作 120 对多个共享变量操作时,CAS无法保证操作的原子性,需要用锁 121 解决方案 122 AtomicReference类保证引用对象之间的原子性,可以把多个变量放在一个对象里来进行CAS操作 123 原子操作类Atomic 124 java.util.concurrent.atomic里的原子操作类提供了线程安全地更新一个变量的方式 125 4大类型13个原子操作类 126 基本类型类 127 AtomicBoolean 128 AtomicInteger 129 AtomicLong 130 数组 131 AtomicIntegerArray 132 AtomicLongArray 133 AtomicReferenceArray 134 引用 135 AtomicReference 136 AtomicReferenceFieldUpdater 137 AtomicMarkableReference 138 属性 139 AtomicIntegerFieldUpdater 140 AtomicLongFieldUpdater 141 AtomicStampedReference 142 核心底层 143 CAS 144 Unsafe只提供了3中CAS方法 145 final native boolean compareAndSwapObject() 146 final native boolean compareAndSwapInt() 147 final native boolean compareAndSwapLong() 148 CAS V.S. 锁 149 JVM中除了偏向锁,其他锁(轻量级锁、互斥锁)的实现方式都用了循环CAS,即当一个线程想进入同步块的时候使用循环CAS的方式来获取锁,当它退出同步块的时候使用循环CAS释放锁。 150 java 内存模型(JMM) 151 并发编程的挑战 152 线程上下文切换 153 死锁 154 资源限制的挑战 155 并发编程需要解决的两大问题 156 线程之间如何通信 157 线程通信机制 158 内存共享 159 消息传递 160 线程之间如何同步 161 同步是指程序中用于控制不同线间操作发生相对顺序的机制 162 内存模型 163 处理器内存模型 164 硬件级的内存模型 165 处理器的内存屏障 166 内存屏障是一组处理器指令,用于实现对内存操作的顺序限制 167 为了保证内存可见性,Java编译器再生产指令序列的适当位置会插入内存屏障制定来禁止特定类型的处理器重排序 168 内存屏障(Memory Barrier,或有时叫做内存栅栏,Memory Fence)是一种CPU指令,用于控制特定条件下的重排序和内存可见性问题。 169 4大类内存屏障指令 170 LoadLoad Barriers 171 LoadStore Barriers 172 StoreStore Barriers 173 StoreLoad Barriers 174 全能型的屏障,同时具有其他三个屏障的效果,但执行该屏障开销会很大,因为Buffer Fully Flush 175 顺序一致性内存模型 176 多线程环境下的理想化的理论参考模型,处理器的内存模型和编程语言的内存模型都会以顺序一致性内存模型作为参照 177 为程序提供了极强的内存可见性保证 178 数据竞争 179 定义 180 在一个线程中写一个变量,在另一个线程中读同一个变量,而且写和读没有通过同步来排序 181 当程序未正确同步时,就可能会存在数据竞争 182 如果程序是正确同步的,程序的 执行将具有顺序一致性 183 这里的同步是广义上的同步,包括常用同步原语的正确使用(Synchronized、volatile 、lock和 final) 184 特性 185 一个线程中的所有操作必须按照程序的顺序来执行 186 不管程序是否同步,所有程序都只能看到一个单一的操作执行顺序,每个操作都必须原子执行且立即对所有的线程可见 187 JMM 188 语言级的内存模型 189 JMM的内存可见性保证3方面 190 基本方针:在不改变(正确同步的)程序执行结果的前提下,尽可能地为编译器和处理器的优化打开方便之门(重排序) 191 1. 单线程程序。 192 2. 正确同步的多线程程序。 193 3. 未同步或未正确同步的多线程程序。 194 happens-before 195 JMM中最核心的理论,保证跨线程的内存的可见性(可以在一个线程之内,也可以再不用线程之间)通过内存屏障实现 196 背景 197 JMM设计的两大核心目标 198 为程序员提供足够强的内存可见性保证 199 对编译器和处理器的约束尽可能放松(只要不改变程序的执行结果,编译器处理器怎么优化都行)首先保证正确性,然后再追求执行效率 200 简单定义 201 在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须存在happens-before关系 202 定义 203 JMM对程序员保证 204 如果一个操作happens-before另一个操作,那么第一个操作的执行结果对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前 205 JMM对编译器和处理器重排序的约束原则 206 两个操作之间存在happens-before关系,并不意味着一定要按照happens-before原则判定的顺序来执行。如果重排序之后的执行结果与按照happens-before关系来执行的结果一致,那么这种重排序并不非法。 207 原生java满足8大规则 208 1. 程序顺序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作; 209 2. 监视器锁规则:一个unLock操作先行发生于后面对同一个锁额lock操作; 210 3. volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作; 211 4. 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C; 212 5. 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作; 213 6. 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生; 214 7. 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行; 215 8. 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始; 216 happens-before与JMM的关系 217 happens-before规则是JMM呈现给程序员的视图 218 一个happens-before规则对应于一个或者多个编译器和处理器重排序规则 219 happens-before VS as-if-serial 220 as-if-serial 语义保证单线程内程序的执行结果不被改变;程序顺序执行 221 happens-before 关系保证正确同步的多线程程序的执行结果不被改变;happens-before制定的顺序执行 222 java并发基础线程 223 线程的状态 224 6大状态 225 NEW 226 初始状态,线程被构建,但是还没有调用start()方法 227 RUNNABLE 228 运行状态,Java线程将操作系统中的就绪和运行两种状态统称为“运行状态” 229 BLOCK 230 阻塞状态,表示线程阻塞于锁 231 WAITING 232 等待状态,进入该状态表示当前线程需要等待其他线程做出一些特定动作(通知或者等待) 233 TIME_WAITING 234 超时等待状态,可以在等待的时间自行返回的 235 TERMINATED 236 终止状态,表示当前线程已经执行完毕 237 Java状态转移 238 WAITING-->RUNNABLE 239 Object.notify() 240 Object.notifyAll() 241 LockSupport.unpark(Thread) 242 TIME_WAITING-->RUNNABLE 243 Object.notify() 244 Object.notifyAll() 245 LockSupport.unpark(Thread) 246 BLOCK-->RUNNABLE 247 获取到锁 248 1. 实例化后New 249 2. New-->RUNNABLE 250 系统调度 251 Thread.start() 252 running-->ready 253 Thread.yield 254 ready-->running 255 3. RUNNABLE-->WAITING 256 Object.wait() 257 Thread.join() 258 LockSupport.park() 259 4. RUNNABLE-->TIME_WAITING 260 Object.wait(long) 261 Thread.sleep(long) 262 Thread.join(long) 263 LockSupport.parkNanos() 264 LockSupport.parkUntil() 265 5. RUNNABLE-->BLOCKED 266 等待进入synchronized方法 267 等待进入synchronized块 268 6. RUNNABLE-->TERMINATED 269 Java 线程状态变迁 270 yield 271 暂停当前正在执行的线程对象。 272 yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。 273 yield()只能使同优先级或更高优先级的线程有执行的机会。 274 注意:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。 275 注意 276 等待进入synchronized方法/块 为阻塞状态 277 java.concurrent.Lock为 等待状态,因为Lock接口对于阻塞的实现使用了LockSupport类中的相关方法 278 程序 VS 进程 VS 线程 279 程序 280 一组指令的有序结合,是静态的指令,是永久存在的 281 进程 282 具有一定独立功能的程序关于某个数据集合上的一次运行活动,是系统进行资源(打开的文件,创建的socket)分配和调度的一个独立单元。进程的存在是暂时的,是一个动态的概念。 283 线程 284 线程是CPU调度和分配的基本单位,是比进程更小的能独立运行的基本单元。本身基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器、一组寄存器和栈)。一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行。 285 进程 VS 线程 286 定义 287 进程是资源分配和调度的基本单位;线程是CPU/任务调度和执行的基本单位 288 包含关系 289 1个进程包含有多(大于等于1)个线程; 线程是进程的一部分(轻量级进程) 290 地址空间 291 进程之间地址空间独立; 同一进程内的线程共享本进程的地址空间 292 切换开销 293 进程之间切换开销大; 线程之间切换开销小(创建和销毁) 294 创建 295 进程fork/vfork; 线程pthread_create 296 销毁 297 进程结束,它拥有的所有线程都将销毁; 线程结束,不会影响同个进程中的其他线程 298 私有属性 299 进程:PCB(进程控制块); 线程:TCB(线程控制块),线程Id,寄存器,上下文 300 一个程序至少只有一个进程,一个进程至少有一个线程 301 启动和终止线程 302 线程间通信 303 volatile & synchronized 304 共享内存和本地内存拷贝的同步更新问题,使得变量不一定能是最新的 305 volatile: 保证所有线程对变量的可见性 306 synchronized:保证线程对变量访问的可见性和排他性,获取monitor; 主要确保多个线程在同一时刻,智能有一个线程处于方法或者同步块中。 307 等待/通知机制 308 任意java对象所具备的 309 依托于同步机制,目的就是确保等待线程从wait()方法返回时能够感知到通知线程对变量作出的修改 310 等待通知机制:线程A调用了对象O的wait()方法进入了等待状态,而线程B调用了对象O的notify()或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,进而执行后续操作。上述两个线程通过对象O来完成交互,而对象的wait()与notify()或notifyAll()的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作。 311 等待超时模式 312 等待超时模式就是在等待/通知范式基础上增加了超时控制,避免执行时间过长,也不会“永久”阻塞调用者,而是按照调用者的要求返回。 313 超时等待:调用一个方法时,等待一段时间(一般给定一个时间段),如果该方法能够在给定的时间段内得到结果,那么将结果立刻返回,反之,超时返回默认结果。 314 管道输入/输出流 315 管道输入输出流与普通的文件输入输出流或者网络输入输出流不同之处在于,它主要用于线程之间的数据传输,而传输的媒介是内存 316 4种具体的实现 317 PipedOutputStream 318 PipedInputStream 319 PipedReader 320 PipedWriter 321 面向字节 322 面向字符 323 Thread.join()的使用 324 涉及了等待/通知机制,等待前驱线程结束,接收前驱线程结束通知,源码本质也是wait()和notifyAll() 325 ThreadLocal的使用 326 ThreadLocal,即线程变量,是一个以ThreadLocal对象为键,任意对象为值的存储结构。这个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值。 327 ThreadLocal的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用。作用:提供一个线程内公共变量,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度,或者为线程提供一个私有的变量副本,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。 328 方法 329 initialValue()是一个protected方法,一般是用来在使用时进行重写的,它是一个延迟加载方法。如果set()方法没有调用,第一次get()方法调用时会进行初始化 initialValue(),每个线程会调用一次。 330 get()方法是用来获取ThreadLocal在当前线程中保存的变量副本 331 set(T value)用来设置当前线程中变量的副本 332 remove()用来移除当前线程中变量的副本 333 工作原理 334 Thread类中有一个成员变量属于ThreadLocalMap类(一个定义在ThreadLocal类中的内部类),它是一个Map,他的key是ThreadLocal实例对象。 335 当为ThreadLocal类的对象set值时,首先获得当前线程的ThreadLocalMap类属性,然后以ThreadLocal类的对象为key,设定value,值时则类似。 336 ThreadLocal变量的活动范围为某线程,是该线程“专有的,独自霸占”的,对该变量的所有操作均由该线程完成!也就是说,ThreadLocal 不是用来解决共享对象的多线程访问的竞争问题的,因为ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。当线程终止后,这些值会作为垃圾回收。 337 由ThreadLocal的工作原理决定了:每个线程独自拥有一个变量,并非是共享的, 338 存储结构的好处 339 1、线程死去的时候,线程共享变量ThreadLocalMap则销毁。 340 2、ThreadLocalMap<ThreadLocal,Object>键值对数量为ThreadLocal的数量,一般来说ThreadLocal数量很少,相比在ThreadLocal中用Map<Thread, Object>键值对存储线程共享变量(Thread数量一般来说比ThreadLocal数量多),性能提高很多。 341 弱引用GC 342 1、使用完线程共享变量后,显示调用remove方法清除线程共享变量可以及时清除 343 2、JDK建议ThreadLocal定义为private static,这样ThreadLocal的弱引用问题则不存在了。 344 3、对于ThreadLocal变量,我们可以手动的将其置为Null,比如tl =null。那么这个ThreadLocal对应的所有线程的局部变量都有可能被回收。 345 解决Hash冲突方法 346 线性探测 347 应用场景: 用来解决 数据库连接、Session管理、AOP耗时统计等。 348 注意 349 不过有点遗憾的是只能放一个值,再次调用set设置值,会覆盖前一次set的值。如果要多个变量,新建多个ThreadLocal对象 350 是单个线程内函数和组件的共享变量,不是多线程的共享变量,线程隔离 351 锁 352 Lock接口 353 锁是用来控制多个线程访问共享资源的方式。一般来说一个锁可以防止多个线程同时访问共享资源(但有些锁可以允许多个线程访问共享资源,如读写锁)。 354 在Lock接口出现前,java使用synchronized关键字实现锁的功能,但是在javaSE5之后,并发包中提供了Lock接口(以及其实现类)用来实现锁的功能。 355 Lock V.S. synchronized 356 Lock接口有而synchronized不具备的主要特性 357 尝试非阻塞地获取锁 358 当前线程尝试获取锁,如果这一时刻锁没有被其他线程获取到,则成功获取并持有锁 359 超时获取锁 360 在指定的截止时间之前获取锁,如果截止时间到了仍旧无法获取锁,则返回 361 能被中断地获取锁 362 与synchronized不同,获取到所得线程能够响应中断,当获取到锁的线程被中断时,中断异常将会被抛出,同时锁会被释放 363 Lock提供了与synchronized相似的功能,但必须显示的获取锁与释放锁,虽然不及隐式操作方便,但是拥有了锁获取与释放的可操作性、可中断的锁获取与超时获取锁等多重功能。 364 队列同步器AQS 365 队列同步器AbstractQueuedSynchronizer(以下简称同步器), 是用来构建锁或者其他同步组件(继承lock)的基础框架, 366 它使用了一个int成员变量来表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作。 367 同步框架,提供通用机制来原子性管理同步状态、阻塞和唤醒线程,以及维护被阻塞线程的队列 368 基于AQS实现的同步器包括:ReentrantLock、Semaphore、ReentrantReadWriteLock、CountDownLatch和FutureTask(任务) 369 使用方式 370 继承 371 同步器的主要使用方法是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态。 372 静态内部类 373 子类被推荐定义为自定义同步组件的静态内部类,同步器自身没有实现任何同步接口,它仅仅是定义了若干同步状态获取和释放方法来供自定义同步组件使用 374 独占式共享式获取同步状态 375 同步器即可以支持独占式获取同步状态,也可以支持共享式地获取同步状态,这样方便实现不同类型的同步组件(ReentrantLock、ReentrantReadWriteLock、CountDownLatch等)。 376 锁 VS 同步器 377 联系 378 同步器是实现锁(也可以是任何同步组件)的关键:在锁中聚合同步器,利用同步器实现锁的语义。lock方法内部调用实现了AQS的内部类的require方法 379 区别 380 锁是面向使用者的,他定义了使用者与锁交互的接口(比如允许两个线程并行访问),隐藏了实现细节; 381 同步器是面向锁的实现者,它简化了锁的实现方式,屏蔽了同步管理状态、线程的排队、等待与唤醒等底层操作。 382 锁让使用者仅仅是调用其方法既可以实现同步效果、同步器让实现者通过重写抽象方法进行了队列的底层操作。他们两个是使用者和实现者关注不同的领域实现了相同的效果。 383 API 384 AbstractQueuedSynchronizer,同步器,实现JUC核心基础组件 385 同步器基于模板设计模式实现的,使用者需要继承同步器并重写指定的方法,随后将同步器组合在自定义的同步组件的实现中,并调用同步器提供的模板方法,而这些模板方法会调用使用者重写的方法。 386 自定义同步组件(extends Lock)将使用同步器 (静态内部类) 提供的模板方法来实现自己的同步语义 387 重入锁 388 ReentrantLock 389 可重入锁,是一种递归无阻塞的同步机制 390 比synchronized更强大,灵活的锁机制,可以减少死锁发生的概率 391 分为公平锁和非公平锁 392 底层采用AQS实现,通过内部Sync继承AQS 393 读写锁 394 ReentrantReadWriteLock 395 读写锁,两把锁:共享锁-读锁、排它锁-写锁 396 支持公平性、非公平性,可重入和锁降级 397 锁降级:遵循获取写锁、获取读锁在释放写锁的次序,写锁可以降级成为读锁。 398 LockSupport工具 399 阻塞和唤醒一个线程 400 Condition接口 401 Lock提供条件Condition,对线程的等待、唤醒操作更加详细和灵活,依赖于lock 402 Lock提供条件Condition,对线程的等待、唤醒操作更加详细和灵活 403 内部维护一个Condition队列,当前线程调用await()方法,将会以当前线程构造一个节点(Node),并将节点加入到该队列的尾部。 404 java 并发容器 405 ConcurrentHashMap 406 并发编程中需要用到线程安全的HashMap 407 1.8与1.7的区别 408 数据结构 409 JDK 1.7 数组+链表:Segment(ReentrantLock)+HashEntry 410 JDK 1.8 数组+链表/红黑树 Node(HashEntry) + Synchronized+CAS+红黑树 411 线程安全 412 JDK 1.7 Segment(ReentrantLock) 413 JDK 1.8 Synchronized+CAS 414 锁粒度 415 JDK1.7 锁的粒度是基于Segment的,包含多个HashEntry 416 JDK1.8 锁的粒度是基于Node的(HashEntry首节点),实现降低锁的粒度 417 查询时间复杂度 418 JDK1.7 遍历链表 O(N) 419 JDK1.8 遍历红黑树 O(log N) 420 链表转化为红黑树:定位结点的hash算法简化会带来弊端,Hash冲突加剧,因此在链表节点数量大于8时,会将链表转化为红黑树进行存储。 421 辅助类 422 JDK 1.8 例如TreeBin,Traverser等对象内部类。 423 ConcurrentLinkedQueue 424 并发编程中需要用到线程安全的队列 425 1. 使用阻塞算法 426 1个锁(入队和出队用同一把锁)和2个锁(入队和出队用不同的锁) 427 阻塞队列 428 2. 使用非阻塞算法 429 CAS 430 ConcurrentLinkedQueue 431 基于链接节点的无边界的线程安全队列,采用FIFO原则对元素进行排序,内部采用CAS算法实现 432 精妙之处:利用CAS来完成数据操作,同时允许队列的不一致性,弱一致性表现淋漓尽致。 433 ConcurrentSkipListMap 434 ConcurrentSkipListMap其内部采用SkipLis数据结构实现。 435 ConcurrentSkipListSet 436 内部采用ConcurrentSkipListMap实现 437 Java阻塞队列(7个) 438 阻塞队列是一个支持两个附加操作的队列 439 两个附加操作 440 支持阻塞的插入方法 441 当队列满时,队列会阻塞插入元素的线程,直到队列不满 442 支持阻塞的移除方法 443 当队列空时,队列会阻塞获取元素的线程,直到队列非空 444 实现原理 445 使用通知模式 446 当生产者往满的队列里添加元素时会阻塞住生产者,当消费者消费了一个队列中的元素后,会通知生产者当前队列可用 447 一个抽象类 448 AbstractQueue,改类在Queue接口中扮演着非常重要的作用,该类提供了对queue操作的骨干实现 449 一个接口 450 BlockingQueue继承java.util.Queue为阻塞队列的核心接口,提供了在多线程环境下的出列、入列操作,作为使用者,则不需要关心队列在什么时候阻塞线程,什么时候唤醒线程,所有一切均由BlockingQueue来完成。 451 七个阻塞队列 452 ArrayBlockingQueue 453 一个由数组组成的有界阻塞队列 454 LinkedBlockingQueue 455 一个由链表组成的有界阻塞队列 456 LinkedBlockingDeque 457 一个由链表组成的双向阻塞队列 458 PriorityBlockingQueue 459 一个支持优先级排序的无界阻塞队列 460 DelayQueue 461 一个使用优先级队列实现的无界阻塞队列 462 SynchronousQueue 463 一个不存储元素的阻塞队列 464 LinkedTransferQueue 465 一个由链表组成的无界阻塞队列 466 即可以像其他的BlockingQueue一样有容量又可以像SynchronousQueue一样不会锁住整个队列 467 有界 468 对读或者写都是锁上整个队列,在并发量大的时候,各种锁是比较耗资源和耗时间的 469 阻塞队列的4中处理方式 470 抛出异常 471 返回特殊值 472 一直阻塞 473 超时退出 474 注意: 如果是无界阻塞队列, 队列不可能出现满的情况,使用put或offer方法永远不会被阻塞,而且使用offer方法时,永远返回true 475 java 并发工具(4个) 476 CountDownLatch 477 等ABCD 4个人都结束了,自己才能开始,结束一个减一个 478 CountDownLatch 它允许一个或多个线程等待其他N个指定数量线程完成操作 479 CountDownLatch也可以实现join的功能,并且比join的功能更多 480 AQS(队列同步器) 共享锁 481 await() 482 CyclicBarrier 483 我和A,B,C,D 5个人互相等待,会合了再一起进电影院,到一个减一个 484 定义 485 可循环使用的同步屏障:它允许一组线程互相等待,直到达到某个公共屏障点(common barrier point) 486 通俗讲:让一组线程到达一个屏障是被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活 487 底层采用ReentrantLock + Condition 实现 488 应用场景 489 多线程结果合并的操作,用于多线程计算数据,最后合并计算结果的应用场景 490 Semaphore 491 计数器,鸡蛋篮子里只能放5个鸡蛋,缺几个,才能放几个 492 信号量 493 计数器,用来控制同时访问特定资源的线程数量,他通过协调各个线程,以保证合理的使用公共资源 494 内部采用共享锁实现 495 从概念上讲,信号量维护了一个许可集。如有必要,在许可可用前会阻塞每一个acquire(),然后再获取该许可。每个release()添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore只对许可的号码进行计数,并采取相应的行动。 496 应用场景 497 用于流量控制,特别是公用资源有限的应用场景,比如数据库连接。 498 Exchanger 499 A和B交换数据 500 定义 501 交换者,是一个用于线程间协作的工具类,用于进行线程间的数据交换,两个线程 502 它提供一个同步点,用于进行线程间成对配对及交换数据, 503 具体 504 第一个线程先执行exchange()方法,它会一只等待第二个线程也执行exchange()方法,当两个线程都到达同步点时,两个线程就可以交换数据,将本线程生产出来的数据传递给对方 505 允许在并发任务之间交换数据。具体来说,Exchanger类允许在两个线程之间定义同步点。当两个线程都到达同步点时,他们交换数据结构,因此第一个线程的数据结构进入到第二个线程中,第二个线程的数据结构进入到第一个线程中 506 应用场景 507 遗传算法 508 交叉 509 校对工作 510 AB岗录入电子银行流水 511 Exchanger 可能被视为 SynchronousQueue 的双向形式。Exchanger 可能在应用程序(比如遗传算法和管道设计)中很有用。 512 java 原子操作类Atomic(13个) 513 java.util.concurrent.atomic里的原子操作类提供了线程安全地更新一个变量的方式 514 4大类型13个原子操作类 515 基本类型类 516 AtomicBoolean 517 原子更新布尔类型 518 AtomicInteger 519 原子更新整型 520 AtomicLong 521 原子更新长整型 522 用于通过原子的方式更新基本类型 523 数组 524 AtomicIntegerArray 525 原子更新整形数组里的元素 526 AtomicLongArray 527 原子更新长整型数组里的元素 528 AtomicReferenceArray 529 原子更新引用类型数组里的元素 530 通过原子的方式更新数组里的某个元素 531 引用 532 AtomicReference 533 原子更新引用类型 534 AtomicReferenceFieldUpdater 535 原子更新引用类型里的字段 536 AtomicMarkableReference 537 原子更新带有标记位的引用类型 538 如果要原子地更新多个变量,就需要使用这个原子更新引用类型提供的类 539 属性 540 AtomicIntegerFieldUpdater 541 原子更新整型的字段的更新器 542 AtomicLongFieldUpdater 543 原子更新长整型的字段的更新器 544 AtomicStampedReference 545 原子更新带有版本号的引用类型 546 如果需要某各类的某个字段,使用原子更新字段类 547 核心底层 548 CAS 549 Unsafe只提供了3中CAS方法 550 final native boolean compareAndSwapObject() 551 final native boolean compareAndSwapInt() 552 final native boolean compareAndSwapLong() 553 java并发框架(2个) 554 Fork/Join框架 555 定义 556 一个用于并行执行任务的框架,是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架 557 核心思想 558 分治 559 fork分解任务,join收集任务 560 工作窃取算法 561 定义 562 工作窃取算法work-stealing: 某个线程从其他队列里窃取任务来执行 563 背景 564 将一个不较大的任务分割为若干个互不依赖的子任务,为了减少线程之间的竞争,把这些子任务分别放到不同的队列里,并未每个队列创建一个单独的线程来执行队列里的任务,线程和队列一一对应。 565 执行快的线程帮助执行慢的线程执行任务,提升整个任务效率 566 为了减少窃取任务线程和被窃取任务线程之间的竞争,通常会使用双端队列 567 窃取任务线程永远从双端队列的尾部拿任务执行 568 被窃取任务线程永远从双端队列的头部拿任务执行 569 优点 570 充分利用线程进行并行计算,减少了线程间的竞争 571 缺点 572 在某些情况下还是存在竞争,比如双端队列里只有一个任务时,并且该算法会消耗了更多的资源,比如创建多个线程和多个双端队列 573 核心类 574 ForkJoinTask 575 子类,用于继承 576 继承子类 RecursiveAction 577 用于没有返回结果的任务 578 继承子类 RecursiveTask 579 用于有返回结果的任务 580 方法 581 fork 582 分解任务 583 join 584 合并任务结果 585 isCompletedAbnormally() 586 检查任务是否已经抛出异常或已经被取消了 587 getException() 588 获取异常 589 ForkJoinWorkerThread 590 执行任务的工作线程 591 ForkJoinPool 592 执行任务ForkJoinTask的线程池,ForkJoinTask需要通过ForkJoinPool来执行 593 submit(task); 594 内部结构 595 ForkJoinTask数组 596 存放任务 597 ForkJoinWorkerThread数组 598 执行任务 599 Executor框架 600 Executor框架的结构 601 任务 602 包括被执行任务需要实现的接口 603 Runnable接口 604 Callable接口 605 任务的执行 606 任务执行机制的核心接口 607 Executor接口 608 继承自Executor的接口 609 ExecutorService接口 610 execute(Runnable command) 611 submit(Runnable task) 612 submit(Callable<T>task) 613 返回值是FutureTask对象 614 实现了ExecutorService接口的实现类 615 ThreadPoolExecutor 616 ScheduledThreadPoolExecutor 617 异步计算的结果 618 Future接口 619 get 620 等待任务执行完成 621 cancel 622 取消任务完成 623 实现了Future接口的实现类 624 FutureTask 625 ThreadPoolExecutor(线程池) 626 是线程池的核心实现类,用来执行被提交的任务 627 ThreadPoolExecutor 628 corePoolSize 629 线程池中核心线程的数量。当提交一个任务时,线程池会新建一个线程来执行任务,直到当前线程数等于corePoolSize。如果调用了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有基本线程。 630 maximumPoolSize 631 线程池中允许的最大线程数。线程池的阻塞队列满了之后,如果还有任务提交,如果当前的线程数小于maximumPoolSize,则会新建线程来执行任务。注意,如果使用的是无界队列,该参数也就没有什么效果了。 632 keepAliveTime 633 线程空闲的时间。线程的创建和销毁是需要代价的。线程执行完任务后不会立即销毁,而是继续存活一段时间:keepAliveTime。默认情况下,该参数只有在线程数大于corePoolSize时才会生效。 634 unit 635 keepAliveTime的单位。TimeUnit 636 workQueue 637 用来保存等待执行的任务的阻塞队列,等待的任务必须实现Runnable接口。我们可以选择如下几种: 638 分类 639 ArrayBlockingQueue:基于数组结构的有界阻塞队列,FIFO。 640 LinkedBlockingQueue:基于链表结构的有界阻塞队列,FIFO。 641 SynchronousQueue:不存储元素的阻塞队列,每个插入操作都必须等待一个移出操作,反之亦然。 642 PriorityBlockingQueue:具有优先界别的无界阻塞队列。 643 threadFactory 644 用于设置创建线程的工厂。可以通过线程工厂给每个创建出来的线程设置更有意义的名字,该对象可以通过Executors.defaultThreadFactory() 645 handler 646 RejectedExecutionHandler,线程池的拒绝策略。所谓拒绝策略,是指将任务添加到线程池中时,线程池拒绝该任务所采取的相应策略。当向线程池中提交任务时,如果此时线程池中的线程已经饱和了,而且阻塞队列也已经满了,则线程池会选择一种拒绝策略来处理该任务。 647 四种拒绝策略 648 AbortPolicy:直接抛出异常,默认策略; 649 CallerRunsPolicy:用调用者所在的线程来执行任务; 650 DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务; 651 DiscardPolicy:直接丢弃任务; 652 当然我们也可以实现自己的拒绝策略,例如记录日志、持久化存储不能处理的任务等等,实现RejectedExecutionHandler接口自定义即可。 653 工厂类Executors创建3种类型的ThreadPoolExecutor 654 SingleThreadExecutor 655 使用单个worker线程的Executor 656 应用场景 657 适用于需要保证顺序地执行各个任务;并且在任意时间点,不会有多个线程是活动的应用场景 658 特点 659 corePool和maximumPoolSize均被设置为1 660 使用的是相当于无界的有界阻塞队列LinkedBlockingQueue,所以带来的影响和FixedThreadPool一样。 661 FixedThreadPool 662 固定线程数的线程池 663 应用场景 664 为了满足资源管理的需求,需要限制当前线程数量的应用场景 665 适用于负载比较重的服务器 666 特点 667 corePoolSize 和 maximumPoolSize都设置为创建FixedThreadPool时指定的参数nThreads,意味着当线程池满时且阻塞队列也已经满时,如果继续提交任务,则会直接走拒绝策略 668 默认的拒绝策略,即AbortPolicy,则直接抛出异常。 669 keepAliveTime设置为0L,表示空闲的线程会立刻终止。 670 workQueue则是使用LinkedBlockingQueue,但是没有设置范围,那么则是最大值(Integer.MAX_VALUE),这基本就相当于一个无界队列了。 671 无界队列对线程池的影响 672 1. 当线程池中的线程数量等于corePoolSize 时,如果继续提交任务,该任务会被添加到无界阻塞队列workQueue中,因此线程中的线程数不会超过corePoolSize 673 2. 由于1,使用无界队列时的 maximumPoolSize是一个无效参数 674 3. 由于1和2,使用无界队列时的 keepAliveTime 是一个无效参数 675 4. 不会拒绝任务 676 CachedThreadPool 677 根据需要创建新线程,是大小无界的线程池 678 应用场景 679 适用于执行很多的短期异步任务的小程序 680 适用于负载较轻的服务器 681 特点 682 corePool为0,maximumPoolSize为Integer.MAX_VALUE,这就意味着所有的任务一提交就会加入到阻塞队列中。 683 keepAliveTime这是为60L,unit设置为TimeUnit.SECONDS,意味着空闲线程等待新任务的最长时间为60秒,空闲线程超过60秒后将会被终止。 684 阻塞队列采用的SynchronousQueue,每个插入操作都必须等待另一个线程对应的移除操作,此处把主线程提交的任务传递给空闲线程去执行。 685 SynchronousQueue是一个没有元素的阻塞队列,加上corePool = 0 ,maximumPoolSize = Integer.MAX_VALUE,这样就会存在一个问题,如果主线程提交任务的速度远远大于CachedThreadPool的处理速度,则CachedThreadPool会不断地创建新线程来执行任务,这样有可能会导致系统耗尽CPU和内存资源,所以在使用该线程池是,一定要注意控制并发的任务数,否则创建大量的线程可能导致严重的性能问题。 686 重要操作 687 offer 688 主线程执行offer操作与空闲线程执行的poll操作配对成功后,主线程把任务交给空闲线程执行 689 execute 690 执行任务 691 poll 692 让空闲线程在SynchronousQueue中等待60s,如果等待到新任务则执行,否则,空闲线程将终止 693 ScheduledThreadPoolExecutor 694 可以在给定的延迟后运行命令,或者定期执行命令,与Timer类似,但比其功能更强大,更灵活 695 FutureTask 696 实现了Future接口和Runnable接口,代表异步计算的结果, 697 应用场景 698 当一个线程需要等待另一个线程把某个任务执行完后它才能继续执行
11. 参考网址:
- 参考来源:http://cmsblogs.com/wp-content/resources/img/sike-juc.png
- 《Java并发编程的艺术》_方腾飞PDF 提取码:o9vr
- http://ifeve.com/the-art-of-java-concurrency-program-1/
- Java并发学习系列-绪论
- Java并发编程实战
- 死磕 Java 并发精品合集