├── .nojekyll ├── 02~线程安全 ├── 线程通信 │ ├── 共享内存 │ │ └── README.md │ ├── 数据传递 │ │ ├── 阻塞队列.md │ │ └── Exchanger.md │ └── 线程协作工具 │ │ ├── CountDownLatch.md │ │ ├── Semaphore.md │ │ └── Exchanger.md ├── 锁 │ ├── README.md │ ├── AQS │ │ ├── 99~参考资料 │ │ │ └── .gitkeep │ │ └── README.md │ ├── StampedLock │ │ └── README.md │ ├── synchronized │ │ ├── 99~参考资料 │ │ │ └── 2018~死磕 Synchronized 底层实现.md │ │ ├── README.md │ │ ├── 锁的升级.md │ │ └── 实现原理.md │ ├── 99~参考资料 │ │ └── 2021~互斥锁(mutex)的底层原理是什么? 操作系统具体是怎么实现的??.md │ └── ReentrantLock │ │ ├── LockSupport.md │ │ └── ReentrantLock.md ├── 并发容器 │ ├── BlockingQueue │ │ └── README.md │ ├── ConcurrentSkipListMap │ │ └── ConcurrentSkipListMap.md │ ├── CopyOnWrite │ │ └── CopyOnWrite.md │ ├── ConcurrentHashMap │ │ ├── README.md │ │ ├── 不安全的 HashMap.md │ │ └── 语法使用.md │ └── 快速失败和安全失败.md ├── 01~内存模型 │ └── JMM 内存模型 │ │ └── README.md └── README.md ├── 03~并发框架 ├── Quartz │ ├── README.md │ └── codes │ │ └── examples │ │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ ├── org │ │ │ │ └── quartz │ │ │ │ │ └── examples │ │ │ │ │ ├── example14 │ │ │ │ │ └── quartz_priority.properties │ │ │ │ │ ├── example12 │ │ │ │ │ ├── client.properties │ │ │ │ │ └── server.properties │ │ │ │ │ ├── example11 │ │ │ │ │ └── quartz.properties │ │ │ │ │ ├── example10 │ │ │ │ │ └── quartz.properties │ │ │ │ │ └── example13 │ │ │ │ │ ├── instance1.properties │ │ │ │ │ └── instance2.properties │ │ │ └── log4j.xml │ │ │ └── java │ │ │ └── org │ │ │ └── quartz │ │ │ └── examples │ │ │ ├── example13 │ │ │ ├── SimpleRecoveryStatefulJob.java │ │ │ └── SimpleRecoveryJob.java │ │ │ ├── example14 │ │ │ └── TriggerEchoJob.java │ │ │ ├── example2 │ │ │ └── SimpleJob.java │ │ │ ├── example8 │ │ │ └── SimpleJob.java │ │ │ ├── example9 │ │ │ ├── SimpleJob1.java │ │ │ ├── SimpleJob2.java │ │ │ └── Job1Listener.java │ │ │ ├── example1 │ │ │ └── HelloJob.java │ │ │ ├── example3 │ │ │ └── SimpleJob.java │ │ │ ├── example12 │ │ │ └── SimpleJob.java │ │ │ ├── example11 │ │ │ └── SimpleJob.java │ │ │ ├── example10 │ │ │ ├── SimpleJob.java │ │ │ └── PlugInExample.java │ │ │ └── example6 │ │ │ ├── BadJob2.java │ │ │ └── BadJob1.java │ │ └── examples_guide.txt ├── RxJava │ ├── 操作符 │ │ ├── 创建操作.md │ │ ├── 聚合操作.md │ │ ├── 过滤操作.md │ │ ├── 错误处理.md │ │ ├── 条件和布尔操作.md │ │ ├── 结合操作.md │ │ └── 辅助操作.md │ ├── 反应式编程 │ │ └── README.md │ ├── 工程实践 │ │ ├── 线程调度.md │ │ └── 应用案例.md │ └── 基础使用 │ │ ├── Completable.md │ │ └── Observable.md ├── Akka │ ├── WebSocket.md │ ├── 并发控制与事务.md │ ├── README.md │ ├── 异常处理与持久化.md │ └── 邮箱与路由.md └── Disq │ └── codes │ └── disq │ ├── src │ ├── main │ │ └── java │ │ │ └── net │ │ │ └── intelie │ │ │ └── disq │ │ │ ├── Processor.java │ │ │ ├── SerializerFactory.java │ │ │ ├── dson │ │ │ ├── StringView.java │ │ │ ├── DsonType.java │ │ │ ├── Latin1View.java │ │ │ ├── UnicodeView.java │ │ │ ├── StringCache.java │ │ │ ├── DsonBinaryRead.java │ │ │ └── DsonBinaryWrite.java │ │ │ ├── Serializer.java │ │ │ ├── RawQueue.java │ │ │ ├── NamedThreadFactory.java │ │ │ ├── DefaultSerializer.java │ │ │ ├── DeleteFileVisitor.java │ │ │ ├── DataFileWriter.java │ │ │ ├── Lenient.java │ │ │ ├── DataFileReader.java │ │ │ ├── DiskQueueReader.java │ │ │ ├── ObjectPool.java │ │ │ ├── PersistentQueue.java │ │ │ └── SerializerPool.java │ └── test │ │ └── java │ │ └── net │ │ └── intelie │ │ └── disq │ │ ├── SuppressForbidden.java │ │ ├── BsonSerializer.java │ │ ├── StringSerializer.java │ │ ├── NamedThreadFactoryTest.java │ │ ├── dson │ │ ├── StringCacheTest.java │ │ ├── DsonToBsonAllocationsTest.java │ │ └── DsonSerializerTest.java │ │ ├── GsonSerializer.java │ │ ├── FstSerializer.java │ │ ├── LenientTest.java │ │ ├── ObjectPoolTest.java │ │ ├── DiskQueueReaderTest.java │ │ └── DefaultSerializerTest.java │ ├── .gitignore │ └── README.md ├── INTRODUCTION.md ├── 01~线程与线程池 ├── 01~线程基础 │ ├── 线程控制 │ │ ├── 线程中断.md │ │ ├── 线程休眠.md │ │ └── 线程合并.md │ ├── ThreadLocal │ │ ├── ThreadLocalRandom.md │ │ └── 99~参考资料 │ │ │ └── README.md │ ├── 线程创建 │ │ └── Thread 与 Runnable.md │ ├── README.md │ └── 线程概念 │ │ ├── 守护线程.md │ │ └── 生命周期.md ├── 02~线程池 │ ├── 线程池原理 │ │ └── README.md │ ├── 线程池类型 │ │ ├── ScheduledThreadPoolExecutor.md │ │ └── 04~ForkJoin │ │ │ ├── ForkJoin 内部实现.md │ │ │ └── README.md │ ├── 自定义线程池 │ │ └── 自定义线程池.md │ └── 03~Executors │ │ ├── ExecutorService │ │ └── README.md │ │ └── README.md ├── 异步编程 │ ├── 响应式编程 │ │ └── README.md │ ├── Future │ │ ├── ListenableFuture.md │ │ └── Callable 与 Future.md │ └── 05~定时器 │ │ └── 定时器.md ├── 99~参考资料 │ └── 2020-致远-Java 线程池实现原理及其在美团业务中的实践.md ├── README.md └── 10~协程 │ └── Loom │ └── README.md ├── 05~并发模式 ├── 任务队列 │ └── README.md ├── 计数器 │ └── 计数器.md └── 单例模式 │ └── 99~参考资料 │ └── 2016~高并发下线程安全的单例模式.md ├── 09~并发性能优化 ├── 常见并发问题 │ ├── CPU 飙高 │ │ └── README.md │ ├── 内存泄漏 │ │ └── README.md │ ├── 性能瓶颈 │ │ └── README.md │ └── 死锁问题 │ │ └── README.md ├── 线程池调优 │ └── 线程调优.md └── 锁优化 │ ├── 03~锁分离.md │ ├── 01~减小锁的持有时间.md │ ├── 02~锁粒度的优化.md │ ├── 04~无锁.md │ └── README.md ├── 04~并发网络 IO ├── NIO │ ├── HTTP.md │ ├── Selector.md │ ├── Buffers.md │ └── Channel.md ├── 网络编程 │ ├── Socket │ │ └── README.md │ └── SPI │ │ ├── README.md │ │ ├── SPI 使用.md │ │ └── HSF SPI.md ├── README.md └── Netty │ ├── README.md │ └── HTTP.md ├── 99~参考资料 ├── 2020~慕课网~《性能优化,打造亿级秒杀系统》 │ └── README.md └── 极客时间~《Java 并发编程实战》 │ ├── README.md │ └── 05~一不小心就死锁了,怎么办?.md ├── .gitattributes ├── README.md └── .gitignore /.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /02~线程安全/线程通信/共享内存/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /02~线程安全/线程通信/数据传递/阻塞队列.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /02~线程安全/锁/README.md: -------------------------------------------------------------------------------- 1 | # 锁 2 | -------------------------------------------------------------------------------- /03~并发框架/Quartz/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /03~并发框架/RxJava/操作符/创建操作.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /03~并发框架/RxJava/操作符/聚合操作.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /03~并发框架/RxJava/操作符/过滤操作.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /03~并发框架/RxJava/操作符/错误处理.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /INTRODUCTION.md: -------------------------------------------------------------------------------- 1 | # 本篇导读 2 | -------------------------------------------------------------------------------- /01~线程与线程池/01~线程基础/线程控制/线程中断.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /01~线程与线程池/01~线程基础/线程控制/线程休眠.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /01~线程与线程池/01~线程基础/线程控制/线程合并.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /01~线程与线程池/02~线程池/线程池原理/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /01~线程与线程池/异步编程/响应式编程/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /02~线程安全/线程通信/数据传递/Exchanger.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /02~线程安全/锁/AQS/99~参考资料/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /02~线程安全/锁/StampedLock/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /03~并发框架/RxJava/操作符/条件和布尔操作.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /05~并发模式/任务队列/README.md: -------------------------------------------------------------------------------- 1 | # 任务队列 2 | -------------------------------------------------------------------------------- /09~并发性能优化/常见并发问题/CPU 飙高/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /09~并发性能优化/常见并发问题/内存泄漏/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /09~并发性能优化/常见并发问题/性能瓶颈/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /02~线程安全/并发容器/BlockingQueue/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /03~并发框架/RxJava/反应式编程/README.md: -------------------------------------------------------------------------------- 1 | # 反应式编程 2 | -------------------------------------------------------------------------------- /01~线程与线程池/02~线程池/线程池类型/ScheduledThreadPoolExecutor.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /04~并发网络 IO/NIO/HTTP.md: -------------------------------------------------------------------------------- 1 | # 基于 NIO 的 HTTP 处理 2 | 3 | # Links 4 | 5 | - http://ju.outofmemory.cn/entry/349839 6 | -------------------------------------------------------------------------------- /05~并发模式/计数器/计数器.md: -------------------------------------------------------------------------------- 1 | # Java 中计数器的实现 2 | 3 | # Links 4 | 5 | - https://mp.weixin.qq.com/s/1k_5-4YC9V69ORTIjJM2hA 6 | -------------------------------------------------------------------------------- /02~线程安全/锁/synchronized/99~参考资料/2018~死磕 Synchronized 底层实现.md: -------------------------------------------------------------------------------- 1 | > [原文地址](https://github.com/farmerjohngit/myblog/issues/12) 2 | -------------------------------------------------------------------------------- /99~参考资料/2020~慕课网~《性能优化,打造亿级秒杀系统》/README.md: -------------------------------------------------------------------------------- 1 | > [原文地址](https://github.com/MaJesTySA/miaosha_Shop) 2 | 3 | # 性能优化,打造亿级秒杀系统 4 | -------------------------------------------------------------------------------- /01~线程与线程池/02~线程池/自定义线程池/自定义线程池.md: -------------------------------------------------------------------------------- 1 | # 自定义线程池 2 | 3 | # Links 4 | 5 | - https://mp.weixin.qq.com/s/1u5DFbkKHsn_-I_DcGPmXw 6 | -------------------------------------------------------------------------------- /03~并发框架/Akka/WebSocket.md: -------------------------------------------------------------------------------- 1 | # HTTP 请求处理 2 | 3 | ## WebSocket 4 | 5 | # Spring Boot 与 Akka 集成 6 | 7 | ```java 8 | 9 | ``` 10 | -------------------------------------------------------------------------------- /01~线程与线程池/01~线程基础/ThreadLocal/ThreadLocalRandom.md: -------------------------------------------------------------------------------- 1 | # ThreadLocalRandom 2 | 3 | # Links 4 | 5 | - https://cubox.pro/c/BHwSJw 一文读懂ThreadLocalRandom -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.xmind filter=lfs diff=lfs merge=lfs -text 2 | *.zip filter=lfs diff=lfs merge=lfs -text 3 | *.pdf filter=lfs diff=lfs merge=lfs -text 4 | -------------------------------------------------------------------------------- /01~线程与线程池/99~参考资料/2020-致远-Java 线程池实现原理及其在美团业务中的实践.md: -------------------------------------------------------------------------------- 1 | # Java 线程池实现原理及其在美团业务中的实践 2 | 3 | # Links 4 | 5 | - https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html 6 | -------------------------------------------------------------------------------- /02~线程安全/锁/99~参考资料/2021~互斥锁(mutex)的底层原理是什么? 操作系统具体是怎么实现的??.md: -------------------------------------------------------------------------------- 1 | > [原文地址](https://www.zhihu.com/question/332113890/answer/2071397624) 2 | 3 | # 互斥锁(mutex)的底层原理是什么? 操作系统具体是怎么实现的?? 4 | -------------------------------------------------------------------------------- /03~并发框架/Disq/codes/disq/src/main/java/net/intelie/disq/Processor.java: -------------------------------------------------------------------------------- 1 | package net.intelie.disq; 2 | 3 | public interface Processor { 4 | void process(T obj) throws Exception; 5 | } 6 | -------------------------------------------------------------------------------- /03~并发框架/Disq/codes/disq/src/main/java/net/intelie/disq/SerializerFactory.java: -------------------------------------------------------------------------------- 1 | package net.intelie.disq; 2 | 3 | public interface SerializerFactory { 4 | Serializer create(); 5 | } 6 | -------------------------------------------------------------------------------- /03~并发框架/Disq/codes/disq/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.ipr 3 | *.iws 4 | *.log 5 | *.pyc 6 | *.versionsBackup 7 | .ideas 8 | .idea 9 | .DS_Store 10 | /target 11 | /*/target 12 | .jhw-cache 13 | ldapdata 14 | -------------------------------------------------------------------------------- /01~线程与线程池/02~线程池/03~Executors/ExecutorService/README.md: -------------------------------------------------------------------------------- 1 | # ExecutorService 2 | 3 | ExecutorService 是 Java java.util.concurrent 包的重要组成部分,是 Java JDK 提供的框架,用于简化异步模式下任务的执行。一般来说,ExecutorService 会自动提供一个线程池和相关 API,用于为其分配任务。 4 | -------------------------------------------------------------------------------- /03~并发框架/Disq/codes/disq/src/main/java/net/intelie/disq/dson/StringView.java: -------------------------------------------------------------------------------- 1 | package net.intelie.disq.dson; 2 | 3 | public interface StringView extends CharSequence { 4 | void clear(); 5 | 6 | void set(byte[] buf, int start, int length); 7 | } 8 | -------------------------------------------------------------------------------- /01~线程与线程池/01~线程基础/ThreadLocal/99~参考资料/README.md: -------------------------------------------------------------------------------- 1 | > [原文地址](https://exceting.github.io/2019/02/15/ThreadLocal%E7%B3%BB%E5%88%97%EF%BC%88%E4%B8%80%EF%BC%89-ThreadLocal%E7%9A%84%E4%BD%BF%E7%94%A8%E5%8F%8A%E5%8E%9F%E7%90%86%E8%A7%A3%E6%9E%90/) 2 | 3 | # ThreadLocal 系列 4 | -------------------------------------------------------------------------------- /02~线程安全/锁/ReentrantLock/LockSupport.md: -------------------------------------------------------------------------------- 1 | # LockSupport 2 | 3 | LockSupport 是用来创建锁和其他同步类的基本线程阻塞原语。LockSupport 中的 park() 和 unpark() 的作用分别是阻塞线程和解除阻塞线程,而且 park()和 unpark()不会遇到 Thread.suspend 和 Thread.resume 所可能引发的死锁问题。因为 park() 和 unpark()有许可的存在;调用 park() 的线程和另一个试图将其 unpark() 的线程之间的竞争将保持活性。 4 | -------------------------------------------------------------------------------- /04~并发网络 IO/网络编程/Socket/README.md: -------------------------------------------------------------------------------- 1 | # Java Socket 2 | 3 | 网络编程是指编写运行在多个设备(计算机)的程序,这些设备都通过网络连接起来。java.net 包中 J2SE 的 API 包含有类和接口,它们提供低层次的通信细节。你可以直接使用这些类和接口,来专注于解决问题,而不用关注通信细节。 4 | 5 | java.net 包中提供了两种常见的网络协议的支持: 6 | 7 | - TCP:TCP 是传输控制协议的缩写,它保障了两个应用程序之间的可靠通信。通常用于互联网协议,被称 TCP / IP。 8 | 9 | - UDP:UDP 是用户数据报协议的缩写,一个无连接的协议。提供了应用程序之间要发送的数据的数据包。 10 | -------------------------------------------------------------------------------- /02~线程安全/并发容器/ConcurrentSkipListMap/ConcurrentSkipListMap.md: -------------------------------------------------------------------------------- 1 | ConcurrentSkipListMap 其内部采用 SkipLis 数据结构实现。为了实现 SkipList,ConcurrentSkipListMap 提供了三个内部类来构建这样的链表结构:Node、Index、HeadIndex。其中 Node 表示最底层的单链表有序节点、Index 表示为基于 Node 的索引层,HeadIndex 用来维护索引层次。到这里我们可以这样说 ConcurrentSkipListMap 是通过 HeadIndex 维护索引层次,通过 Index 从最上层开始往下层查找,一步一步缩小查询范围,最后到达最底层 Node 时,就只需要比较很小一部分数据了。 2 | -------------------------------------------------------------------------------- /03~并发框架/Disq/codes/disq/src/main/java/net/intelie/disq/Serializer.java: -------------------------------------------------------------------------------- 1 | package net.intelie.disq; 2 | 3 | import java.io.IOException; 4 | 5 | public interface Serializer { 6 | void serialize(Buffer buffer, T obj) throws IOException; 7 | 8 | T deserialize(Buffer buffer) throws IOException; 9 | 10 | default void clear() { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /09~并发性能优化/常见并发问题/死锁问题/README.md: -------------------------------------------------------------------------------- 1 | # 死锁 2 | 3 | - 死锁:是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。由于资源占用是互斥的,当某个进程提出申请资源后,使得有关进程在无外力协助下,永远分配不到必需的资源而无法继续运行,这就产生了一种特殊现象:死锁。 4 | 5 | - 活锁:指事物 1 可以使用资源,但它让其他事物先使用资源;事物 2 可以使用资源,但它也让其他事物先使用资源,于是两者一直谦让,都无法使用资源。避免活锁的简单方法是采用先来先服务的策略。当多个事务请求封锁同一数据对象时,封锁子系统按请求封锁的先后次序对事务排队,数据对象上的锁一旦释放就批准申请队列中第一个事务获得锁。 6 | -------------------------------------------------------------------------------- /03~并发框架/Quartz/codes/examples/src/main/resources/org/quartz/examples/example14/quartz_priority.properties: -------------------------------------------------------------------------------- 1 | org.quartz.scheduler.instanceName: PriorityExampleScheduler 2 | 3 | # Set thread count to 1 to force Triggers scheduled for the same time to 4 | # to be ordered by priority. 5 | org.quartz.threadPool.threadCount: 1 6 | org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool 7 | 8 | org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore 9 | -------------------------------------------------------------------------------- /09~并发性能优化/线程池调优/线程调优.md: -------------------------------------------------------------------------------- 1 | # Java 线程调优 2 | 3 | 在讨论 Java 线程调优时,我们需要认识到系统性能是由多个因素共同决定的。这些因素包括: 4 | 5 | - 系统的吞吐量 6 | - 响应时间 7 | - 外部网络环境 8 | - 分布式架构设计 9 | - 各级缓存策略 10 | - 数据冗余处理 11 | 12 | 虽然这些因素都很重要,但在本文中,我们将重点关注单个节点的性能优化。这是因为整体系统的优化离不开每个组成部分的调优,就像一个干净的房子需要从每个角落开始打扫一样。从用户体验的角度来看,当系统负载较轻时,我们可以通过各种缓存优化策略来提高响应速度。然而,随着用户数量的增加和请求频率的上升,我们的服务需要更强大的并发处理能力来支撑。 13 | 14 | 需要注意的是,每个节点的并发处理能力都有其极限。一旦超过这个极限,响应时间就会显著增加。因此,我们的目标是找到最佳的并发处理水平,在保证最高并发数的同时,将响应时间控制在理想范围内。 15 | -------------------------------------------------------------------------------- /03~并发框架/Disq/codes/disq/src/test/java/net/intelie/disq/SuppressForbidden.java: -------------------------------------------------------------------------------- 1 | package net.intelie.disq; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.CLASS) 9 | @Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) 10 | public @interface SuppressForbidden { 11 | } 12 | -------------------------------------------------------------------------------- /03~并发框架/Disq/codes/disq/src/test/java/net/intelie/disq/BsonSerializer.java: -------------------------------------------------------------------------------- 1 | package net.intelie.disq; 2 | 3 | import org.bson.BsonBinaryWriter; 4 | 5 | import java.io.IOException; 6 | 7 | public class BsonSerializer implements Serializer { 8 | @Override 9 | public void serialize(Buffer buffer, Object obj) throws IOException { 10 | } 11 | 12 | @Override 13 | public Object deserialize(Buffer buffer) throws IOException { 14 | return null; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /02~线程安全/并发容器/CopyOnWrite/CopyOnWrite.md: -------------------------------------------------------------------------------- 1 | # CopyOnWrite 2 | 3 | Copy-On-Write 简称 COW,是一种用于程序设计中的优化策略。其基本思路是,从一开始大家都在共享同一个内容,当某个人想要修改这个内容的时候,才会真正把内容 Copy 出去形成一个新的内容然后再改,这是一种延时懒惰策略。从 JDK1.5 开始 Java 并发包里提供了两个使用 CopyOnWrite 机制实现的并发容器,它们是 CopyOnWriteArrayList 和 CopyOnWriteArraySet。 4 | 5 | CopyOnWrite 容器即写时复制的容器,当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行 Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。它是典型的读写分离思想的实践,我们可以对 CopyOnWrite 容器进行并发的读,而不需要加锁。 6 | 7 | # Links 8 | 9 | - https://mp.weixin.qq.com/s/Xv8c9A4E_DOSkI1jBhr-rg 10 | -------------------------------------------------------------------------------- /03~并发框架/Quartz/codes/examples/src/main/resources/org/quartz/examples/example12/client.properties: -------------------------------------------------------------------------------- 1 | # Properties file for use by StdSchedulerFactory 2 | # to create a Quartz Scheduler Instance. 3 | # 4 | 5 | # Configure Main Scheduler Properties ====================================== 6 | 7 | org.quartz.scheduler.instanceName: Sched1 8 | org.quartz.scheduler.logger: schedLogger 9 | org.quartz.scheduler.rmi.proxy: true 10 | org.quartz.scheduler.rmi.registryHost: localhost 11 | org.quartz.scheduler.rmi.registryPort: 1099 12 | -------------------------------------------------------------------------------- /04~并发网络 IO/NIO/Selector.md: -------------------------------------------------------------------------------- 1 | # Selector 2 | 3 | 多路复用器的核心就是通过 Selector 来轮询其上的 Channel,当发现某个或者多个 Channel 处于就绪状态后,从阻塞状态返回就绪的 Channel 的选择键集合,进行 IO 操作。 4 | 5 | Java 的 NIO 为 Reactor 模式提供了实现的基础机制,它的 Selector 当发现某个 Channel 有数据时,会通过 SelectorKey 来告知我们,在此我们实现事件和 Handler 的绑定。 6 | 7 | - Reactor 负责响应 IO 事件,一旦发生,广播发送给相应的 Handler 去处理,这类似于 AWT 的 thread 8 | - Handler 是负责非堵塞行为,类似于 AWT ActionListeners;同时负责将 handlers 与 event 事件绑定,类似于 AWT addActionListener 9 | 10 | ![](https://ngte-superbed.oss-cn-beijing.aliyuncs.com/item/20230417213458.png) 11 | 12 | # Links 13 | 14 | - https://www.baeldung.com/java-nio-selector 15 | -------------------------------------------------------------------------------- /03~并发框架/Disq/codes/disq/src/test/java/net/intelie/disq/StringSerializer.java: -------------------------------------------------------------------------------- 1 | package net.intelie.disq; 2 | 3 | import java.io.IOException; 4 | import java.nio.charset.StandardCharsets; 5 | 6 | public class StringSerializer implements Serializer { 7 | @Override 8 | public void serialize(Buffer buffer, String obj) throws IOException { 9 | buffer.write().write(obj.getBytes(StandardCharsets.UTF_8)); 10 | } 11 | 12 | @Override 13 | public String deserialize(Buffer buffer) throws IOException { 14 | return new String(buffer.buf(), 0, buffer.count(), StandardCharsets.UTF_8); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /03~并发框架/Disq/codes/disq/src/test/java/net/intelie/disq/NamedThreadFactoryTest.java: -------------------------------------------------------------------------------- 1 | package net.intelie.disq; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.assertj.core.api.Assertions.assertThat; 6 | 7 | public class NamedThreadFactoryTest { 8 | @Test 9 | public void testCreateNamed() throws Exception { 10 | NamedThreadFactory factory = new NamedThreadFactory("abc-%d"); 11 | assertThat(factory.newThread(null).getName()).isEqualTo("abc-0"); 12 | assertThat(factory.newThread(null).getName()).isEqualTo("abc-1"); 13 | assertThat(factory.newThread(null).getName()).isEqualTo("abc-2"); 14 | } 15 | } -------------------------------------------------------------------------------- /04~并发网络 IO/README.md: -------------------------------------------------------------------------------- 1 | # NIO 2 | 3 | Java NIO,被称为新 IO(New IO),是 Java 1.4 引入的,用来替代 IO API 的,它是基于 IO 复用技术的非阻塞 IO,不是异步 IO。在早期的 JDK1.4 和 1.5update10 版本之前,JDK 的 Selector 基于 select/poll 模型实现;在 JDK1.5 update10 和 Linux2 .6 以上版本,Sun 优化了 Selector 的实现,它在底层使用了 epoll 替换了 select/poll。在 JDK1.7 提供的 AIO 新增了异步的套接字通道,它是真正的异步 IO,在异步 IO 操作的时候可以传递信号变量,当操作完成之后会回调相关的方法,异步 IO 也称为 AIO。 4 | 5 | 对于网络 IO 相关的基础知识可以参考 [Linux 网络 IO](https://ng-tech.icu/books/DistributedSystem-Notes/#/?q=Linux网络IO) 以及[并发 IO](https://ng-tech.icu/books/DistributedSystem-Notes/#/?q=并发IO) 的相关章节。 6 | 7 | # Links 8 | 9 | - https://parg.co/kmG 10 | - https://mp.weixin.qq.com/s/uOPio2rBwcIK7R8O6FNCkA 11 | -------------------------------------------------------------------------------- /09~并发性能优化/锁优化/03~锁分离.md: -------------------------------------------------------------------------------- 1 | # 锁分离 2 | 3 | 根据实际的操作来选择加上不同的锁也是提升性能的重要方式之一。 4 | 5 | ## 读写分离锁替代独占锁 6 | 7 | ReadWriteLock 使用读写分离锁来替代独占锁,它也是减小锁的粒度的一种方式,上面讲的是对数据结构层面的减小锁持有时间的,这里是根据业务来划分锁的持有,在读多写少的场景使用读写分离锁会大大提高系统的并发性能。 8 | 9 | ## 重入锁和内部锁 10 | 11 | 重入锁的使用相较于内部锁更加复杂,重入锁必须手动显示释放锁,内部锁则可以自动释放,重入锁提供了一套提高性能的功能和 Condition 机制,重入锁可以设置锁的等待时间 boolean tryLock(long time),锁中断 lockInterruptibly() 和快速锁轮询 tryLock() 等可以有效的避免死锁的产生。内部锁则是通过 wait() 和 notfiy() 实现锁的控制。 12 | 13 | ## 自旋锁 14 | 15 | 自旋锁是 JVM 为了解决对多线程并发时频繁的挂起和恢复线程的操作问题的锁,当访问共享资源的时候,锁的等待时间可能很短,可能会比线程的挂起和恢复时间还要短,因此在这段时间里做线程的切换时不值得的。自旋锁可以使线程没有取得锁时不被挂起,而去执行一个空的循环,当线程获取了锁就会继续执行代码。 16 | 17 | 但是自旋锁只适用于线程竞争相对小、锁占用时间短的代码,对于锁竞争激烈的系统中不仅浪费了 CPU 资源,也免不了被挂起。JVM 可以设置自旋锁的开启和等待次数,防止一直执行空循环。 18 | -------------------------------------------------------------------------------- /03~并发框架/Disq/codes/disq/src/main/java/net/intelie/disq/RawQueue.java: -------------------------------------------------------------------------------- 1 | package net.intelie.disq; 2 | 3 | import java.io.Closeable; 4 | import java.io.IOException; 5 | 6 | public interface RawQueue extends Closeable { 7 | void reopen(); 8 | 9 | long bytes(); 10 | 11 | long count(); 12 | 13 | long remainingBytes(); 14 | 15 | long remainingCount(); 16 | 17 | void touch() throws IOException; 18 | 19 | void clear() throws IOException; 20 | 21 | boolean pop(Buffer buffer) throws IOException; 22 | 23 | boolean peek(Buffer buffer) throws IOException; 24 | 25 | void push(Buffer buffer) throws IOException; 26 | 27 | void flush() throws IOException; 28 | 29 | void close(); 30 | } 31 | -------------------------------------------------------------------------------- /01~线程与线程池/02~线程池/线程池类型/04~ForkJoin/ForkJoin 内部实现.md: -------------------------------------------------------------------------------- 1 | # ForkJoin 内部实现 2 | 3 | 我们已经很清楚 Fork/Join 框架的需求了,那么我们可以思考一下,如果让我们来设计一个 Fork/Join 框架,该如何设计?这个思考有助于你理解 Fork/Join 框架的设计。 4 | 5 | - 第一步分割任务。首先我们需要有一个 fork 类来把大任务分割成子任务,有可能子任务还是很大,所以还需要不停的分割,直到分割出的子任务足够小。 6 | - 第二步执行任务并合并结果。分割的子任务分别放在双端队列里,然后几个启动线程分别从双端队列里获取任务执行。子任务执行完的结果都统一放在一个队列里,启动一个线程从队列里拿数据,然后合并这些数据。 7 | 8 | Fork/Join 使用两个类来完成以上两件事情: 9 | 10 | - ForkJoinTask:我们要使用 ForkJoin 框架,必须首先创建一个 ForkJoin 任务。它提供在任务中执行 fork()和 join()操作的机制,通常情况下我们不需要直接继承 ForkJoinTask 类,而只需要继承它的子类,Fork/Join 框架提供了以下两个子类: 11 | 12 | - RecursiveAction:用于没有返回结果的任务。 13 | - RecursiveTask:用于有返回结果的任务。 14 | 15 | - ForkJoinPool:ForkJoinTask 需要通过 ForkJoinPool 来执行,任务分割出的子任务会添加到当前工作线程所维护的双端队列中,进入队列的头部。当一个工作线程的队列里暂时没有任务时,它会随机从其他工作线程的队列的尾部获取一个任务。 16 | -------------------------------------------------------------------------------- /09~并发性能优化/锁优化/01~减小锁的持有时间.md: -------------------------------------------------------------------------------- 1 | # 减小锁的持有时间 2 | 3 | 减小锁的持有时间是为了降低锁的冲突的可能性,提高体系的并发能力。 4 | 5 | ## 只在必要时进行同步加锁操作 6 | 7 | 例如下的代码:在加锁时先判断是否满足同步代码逻辑的要求,以达到减小锁的占有几率的目的。 8 | 9 | ```java 10 | // 使用条件判断减少锁持有时间提高效率。 11 | public void matcher(Char input) { 12 | if (!compiled) { 13 | synchronized(this) { 14 | if (!compiled) { 15 | compile(); 16 | } 17 | } 18 | } 19 | } 20 | ``` 21 | 22 | ## 只在必须加锁的代码段加锁 23 | 24 | 下面的代码的执行只针对必须要加锁的代码段进行加锁操作,减少锁的占有的时间。 25 | 26 | ```java 27 | public synchronized void syncMethod() { 28 | method1(); 29 | method2(); 30 | method3(); 31 | } 32 | 33 | public void syncMethod() { 34 | method1(); 35 | synchronized(this) { 36 | method2(); 37 | } 38 | method3(); 39 | } 40 | ``` 41 | -------------------------------------------------------------------------------- /03~并发框架/Quartz/codes/examples/src/main/resources/log4j.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /09~并发性能优化/锁优化/02~锁粒度的优化.md: -------------------------------------------------------------------------------- 1 | # 锁粒度的优化 2 | 3 | 优化锁的粒度是根据实际的代码逻辑来进行判断,分为锁粒度的细化和锁粒度的粗化 2 种优化方式。 4 | 5 | ## 锁粒度的细化 6 | 7 | 举个简单的例子,JDK 自带的工具类 ConcurrentHashMap 就是一个典型的实现场景,它对锁的拆分方式提高了大大提高了它的吞吐量,ConcurrentHashMap 将自身分成若干个段,每一段都是一个子 HashMap。当需要新增一个的时候,并不是对整个对象进行加锁,而是先根据 hashcode 计算该数据应该被加入到哪个段中,然后对该段加锁,默认情况下 ConcurrentHashMap 有 16 个段,因此运气足够好的时候可以接受 16 个线程同时插入,大大提高了吞吐量。 8 | 9 | 但是减小锁的粒度也带来了新的问题,当锁粒度过于小的时候,获取全局锁消耗的资源也相应增加,以 ConcurrentHashMap 为例,如果它需要获取当前的 size 就需要对每一个段都加锁。 10 | 11 | ## 锁粒度的粗化 12 | 13 | 在一般情况下,为了保证多线程之间的高效并发,会要求线程持有锁的时间尽量短,但是过度的细化会产生大量的申请和释放锁的操作,这对性能的影响也是非常大的。 14 | 15 | ```js 16 | for(int i = 0; i < 10000; i++) { 17 | synchronized(this) { 18 | todo(); 19 | } 20 | } 21 | synchronized(this) { 22 | for(int i = 0; i < 10000; i++) { 23 | todo(); 24 | } 25 | } 26 | ``` 27 | -------------------------------------------------------------------------------- /03~并发框架/Disq/codes/disq/src/main/java/net/intelie/disq/NamedThreadFactory.java: -------------------------------------------------------------------------------- 1 | package net.intelie.disq; 2 | 3 | import java.util.Locale; 4 | import java.util.concurrent.ThreadFactory; 5 | import java.util.concurrent.atomic.AtomicLong; 6 | 7 | public class NamedThreadFactory implements ThreadFactory { 8 | private final String nameFormat; 9 | private final AtomicLong count = new AtomicLong(0); 10 | 11 | public NamedThreadFactory(String nameFormat) { 12 | this.nameFormat = nameFormat; 13 | } 14 | 15 | @Override 16 | public Thread newThread(Runnable r) { 17 | long number = count.getAndIncrement(); 18 | Thread thread = new Thread(r); 19 | thread.setName(String.format((Locale) null, nameFormat, number)); 20 | return thread; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /02~线程安全/并发容器/ConcurrentHashMap/README.md: -------------------------------------------------------------------------------- 1 | # ConcurrentHashMap 2 | 3 | ConcurrentHashMap 是 J.U.C(java.util.concurrent 包)的重要成员,它是 HashMap 的一个线程安全的、支持高效并发的版本。HashMap 不是线程安全的。也就是说,在多线程环境下,操作 HashMap 会导致各种各样的线程安全问题,比如在 HashMap 扩容重哈希时出现的死循环问题,脏读问题等。HashMap 的这一缺点往往会造成诸多不便,虽然在并发场景下 HashTable 和由同步包装器包装的 `HashMap(Collections.synchronizedMap(Map m))` 可以代替 HashMap,但是它们都是通过使用一个全局的锁来同步不同线程间的并发访问,因此会带来不可忽视的性能问题。 4 | 5 | 庆幸的是,JDK 为我们解决了这个问题,它为 HashMap 提供了一个线程安全的高效版本:ConcurrentHashMap。在 ConcurrentHashMap 中,无论是读操作还是写操作都能保证很高的性能:在进行读操作时(几乎)不需要加锁,而在写操作时通过锁分段技术只对所操作的段加锁而不影响客户端对其它段的访问。特别地,在理想状态下,ConcurrentHashMap 可以支持 16 个线程执行并发写操作(如果并发级别设为 16),及任意数量线程的读操作。 6 | 7 | 从 JDK6 到 JDK8,实现线程安全的思想也已经完全变了,它摒弃了 Segment(锁段)的概念,而是启用了一种全新的方式实现,利用 CAS 算法。它沿用了与它同时期的 HashMap 版本的思想,底层依然由“数组”+链表+红黑树的方式思想,但是为了做到并发,又增加了很多辅助的类,例如 TreeBin,Traverser 等对象内部类。 8 | -------------------------------------------------------------------------------- /01~线程与线程池/异步编程/Future/ListenableFuture.md: -------------------------------------------------------------------------------- 1 | # ListenableFuture 2 | 3 | ListenableFuture 是 Guava 对原有 Future 的增强,可以用于监听 Future 任务的执行状况,是执行成功还是执行失败,并提供响应的接口用于对不同结果的处理。 4 | 5 | ```java 6 | ListenableFuture listenable = service.submit(...); 7 | 8 | Futures.addCallback(listenable, new FutureCallback() { 9 | @Override 10 | public void onSuccess(Object o) {} 11 | 12 | @Override 13 | public void onFailure(Throwable throwable) {} 14 | }) 15 | ``` 16 | 17 | ListenableFuture 适用场景: 18 | 19 | - 如果一个主任务开始执行,然后需要执行各个小任务,并且需要等待返回结果,统一返回给前端,此时 Future 和 ListenableFuture 作用几乎差不多,都是通过 get()方法阻塞等待每个任务执行完毕返回。 20 | - 如果一个主任务开始执行,然后执行各个小任务,主任务不需要等待每个小任务执行完,不需要每个小任务的结果,此时用 ListenableFuture 非常合适,它提供的 FutureCallBack 接口可以对每个任务的成功或失败单独做出响应。 21 | - 如果我们希望各个小任务一旦计算完成就拿到结果展示给用户(push 出去)或者做另外的计算,就必须使用另一个线程不断的查询计算状态。这样做,代码复杂,而且效率低下。 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > 在阅读本篇之前,建议先阅读[《并发编程导论](https://ng-tech.icu/books/Concurrent-Notes/#/)》。 2 | 3 | # Java 并发编程 4 | 5 | 建议您首先阅读 [J.U.C 概览](./J.U.C%20概览) 以对 Java 并发编程相关概念有所认知。 6 | 7 | ![Java 并发编程思维脑图](https://s2.ax1x.com/2019/09/02/nCLmb4.png) 8 | 9 | > 更多 Java 应用程序性能调优参阅《[JVM 与性能优化](../JVM%20与性能优化)》 10 | 11 | # Links 12 | 13 | - https://mp.weixin.qq.com/s/w-C9QkMQhgnAChnRrsrXIw 14 | 15 | - https://www.jianshu.com/p/3f6b26ee51ce 16 | 17 | - [concurrency-torture-testing-your-code-within-the-java-memory-model](http://zeroturnaround.com/rebellabs/concurrency-torture-testing-your-code-within-the-java-memory-model/) 18 | 19 | - https://zhuanlan.zhihu.com/p/91788985 20 | 21 | - https://mp.weixin.qq.com/s/1jhBZrAb7bnvkgN1TgAUpw Java 并发六十问,图文详解,快来看看你会多少道! 22 | 23 | - https://github.com/crisxuan/bestJavaer/tree/master/java-concurrent 24 | 25 | - https://github.com/MaJesTySA/JVM-JUC-Core/tree/master 26 | -------------------------------------------------------------------------------- /09~并发性能优化/锁优化/04~无锁.md: -------------------------------------------------------------------------------- 1 | # 无锁 2 | 3 | 锁是一种对操作的同步手段,但是也不是唯一的手段,例如使用空间换时间的思路同样可以解决问题,非阻塞的同步方式也可以达到并发的目的。 4 | 5 | 最简单的一种非阻塞的同步就是 ThreadLocal 了,每个线程有各自独立的 ThreadLocalMap,在并行计算时无需相互等待。另一种更为乐观的方式是使用 CAS 算法,它有 3 个参数(V,E,N),它总是认为自己的操作可以成功,因此只有在 V 的值等于 E 时,把 V 的值设置成 N;当 V 的值不等于 E,就返回 V 的当前值,然后什么也不做,当多个线程同时使用 CAS 时,只有一个线程会执行成功。 6 | 7 | 在 java.util.concurrent.atomic 包中有很多支持原子操作的类,都是基于无锁算法实现的,它的性能远远超过普通的有锁操作,例如使用 CAS 算法实现原子操作中的 getAndSet() 方法: 8 | 9 | ```java 10 | public final int getAndSet(int newValue) { 11 | for (;;) { // 不停循环直到成功 12 | int current = get(); // 获取当前的值 13 | if (compareAndSet(current, newValue)) { // 若当前的值未受其他线程影响,则设置为新值 14 | return current; // 返回新值 15 | } 16 | } 17 | } 18 | ``` 19 | 20 | 以时间换空间、以空间换时间都是实现代码的常用思路,在不同的地方应该使用不同的方式去达到业务需求。 21 | -------------------------------------------------------------------------------- /02~线程安全/并发容器/快速失败和安全失败.md: -------------------------------------------------------------------------------- 1 | # 快速失败和安全失败 2 | 3 | ## 快速失败(fail—fast) 4 | 5 | 在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出 Concurrent Modification Exception。 6 | 7 | 原理:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变 modCount 的值。每当迭代器使用 hashNext()/next() 遍历下一个元素之前,都会检测 modCount 变量是否为 expectedmodCount 值,是的话就返回遍历;否则抛出异常,终止遍历。 8 | 9 | 注意:这里异常的抛出条件是检测到 modCount != expectedmodCount 这个条件。如果集合发生变化时修改 modCount 值刚好又设置为了 expectedmodCount 值,则异常不会抛出。因此,不能依赖于这个异常是否抛出而进行并发操作的编程,这个异常只建议用于检测并发修改的 bug。 10 | 11 | 场景:java.util 包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)。 12 | 13 | ## 安全失败(fail—safe) 14 | 15 | 采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。 16 | 17 | 原理:由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发 Concurrent Modification Exception。 18 | 19 | 缺点:基于拷贝内容的优点是避免了 Concurrent Modification Exception,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。 20 | 21 | 场景:java.util.concurrent 包下的容器都是安全失败,可以在多线程下并发使用,并发修改。 22 | -------------------------------------------------------------------------------- /03~并发框架/Disq/codes/disq/src/main/java/net/intelie/disq/dson/DsonType.java: -------------------------------------------------------------------------------- 1 | package net.intelie.disq.dson; 2 | 3 | import java.util.Arrays; 4 | 5 | public enum DsonType { 6 | UNKNOWN(0x00), 7 | NULL(0x01), 8 | OBJECT(0x02), 9 | ARRAY(0x03), 10 | DOUBLE(0x04), 11 | BOOLEAN(0x05), 12 | STRING(0x06), 13 | STRING_LATIN1(0x07); 14 | 15 | private static final DsonType[] LOOKUP_TABLE = new DsonType[256]; 16 | private final int value; 17 | 18 | static { 19 | Arrays.fill(LOOKUP_TABLE, UNKNOWN); 20 | for (final DsonType cur : DsonType.values()) { 21 | LOOKUP_TABLE[cur.getValue()] = cur; 22 | } 23 | } 24 | 25 | DsonType(final int value) { 26 | this.value = value; 27 | } 28 | 29 | public int getValue() { 30 | return value; 31 | } 32 | 33 | public static DsonType findByValue(final int value) { 34 | return LOOKUP_TABLE[value & 0xFF]; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /01~线程与线程池/README.md: -------------------------------------------------------------------------------- 1 | # 线程池 2 | 3 | 线程多起来的话就需要管理,不然就会乱成一锅。我们知道线程在物理上对应的就是栈里面的一段内存,存放着局部变量的空间和待执行指令集。如果每次执行都要从头初始化这段内存,然后再交给 CPU 执行,效率就有点低了。假如我们知道该段栈内存会被经常用到,那我们就不要回收,创建完就让它在栈里面呆着,要用的时候取出来,用完换回去,是不是就省了初始化线程空间的时间,这样是我们搞出线程池的初衷。 4 | 5 | 其实线程池很简单,就是搞了个池子放了一堆线程。既然我们搞线程池是为了提高效率,那就要考虑线程池放多少个线程比较合适,太多了或者太少了有什么问题,怎么拒绝多余的请求,除了异常怎么处理。首先我们来看跟线程池有关的一张类图。 6 | 7 | # ThreadFactory 8 | 9 | 在 Java 中并不鼓励直接创建线程,线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。 10 | 11 | ```java 12 | ThreadFactory namedThreadFactory = new ThreadFactoryBuilder() 13 | .setNameFormat("demo-pool-%d").build(); 14 | 15 | ExecutorService singleThreadPool = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy()); 16 | 17 | singleThreadPool.execute(()-> System.out.println(Thread.currentThread().getName())); 18 | singleThreadPool.shutdown(); 19 | ``` 20 | -------------------------------------------------------------------------------- /03~并发框架/Disq/codes/disq/README.md: -------------------------------------------------------------------------------- 1 | # Disq 2 | 3 | Simple, fast, disk-backed queue and task executor for Java 8. 4 | 5 | **Features:** 6 | * `PersistentQueue`: a disk-backed blocking queue; 7 | * `Disq`: a disk-backed task executor. 8 | 9 | ## Usage 10 | 11 | Disq is available through Maven Central repository, just add the following 12 | dependency to your `pom.xml` file: 13 | 14 | ```xml 15 | 16 | net.intelie.disq 17 | disq 18 | 0.12 19 | 20 | ``` 21 | 22 | Then, you can use it like that: 23 | 24 | ```java 25 | Processor processor = x -> { 26 | System.out.println(x); 27 | }; 28 | 29 | Disq disq = Disq.builder(processor) 30 | .setDirectory("my_queue") 31 | .setThreadCount(8) 32 | .setMaxSize(1024 * 1024 * 1024) //1GB 33 | .build(); 34 | 35 | disq.submit("some item"); 36 | disq.submit("another item"); 37 | ``` 38 | -------------------------------------------------------------------------------- /04~并发网络 IO/网络编程/SPI/README.md: -------------------------------------------------------------------------------- 1 | # SPI 2 | 3 | SPI:Service Provider Interface,是 JDK 内置的一种服务提供机制。许多开发框架都使用了 Java 的 SPI 机制,如 java.sql.Driver 的 SPI 实现(mysql 驱动、oracle 驱动等)、common-logging 的日志接口实现、dubbo 的扩展实现等等。 4 | 5 | 面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。在实际过程中,API 的实现是封装在 jar 包中的,所以当需要更换一种实现时,要生成新的 jar 包来替换以前的类。为了实现在模块装配的时候不用在程序里动态指明,这就需要一种服务发现机制。java spi 就是提供这样的一个机制:为某个接口寻找服务实现的机制。通过它就可以实现,不修改原来 jar 的情况下,为 api 新增一种实现。这有点类似 IOC 的思想,将装配的控制权移到了程序之外。 6 | 7 | # SPI 缺陷 8 | 9 | ServiceLoader 缺少一些有用的特性: 10 | 11 | - 缺少实例的维护 ServiceLoader 每次 load 后,都会生成一份实例,也就是我们理解的 prototype; 12 | 13 | - 无法获取指定的实例,在 Spring 中可以通过 beanFactory.getBean("id") 获取一个实例,但是 ServiceLoader 不支持,只能一次获取所有的接口实例; 14 | 15 | - 不支持排序 ServiceLoader 返回的接口实例没有进行排序,随着新的实例加入,会出现排序不稳定的情况; 16 | 17 | - 无法获的所有实现的类型 无法通过接口获取所有的接口实例类型; 18 | 19 | - 作用域缺失,没有定义 singleton 和 prototype 的定义,不利于用户进行自由定制。 20 | 21 | # Links 22 | 23 | - https://cxis.me/2017/04/17/Java%E4%B8%ADSPI%E6%9C%BA%E5%88%B6%E6%B7%B1%E5%85%A5%E5%8F%8A%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/ 24 | -------------------------------------------------------------------------------- /03~并发框架/Quartz/codes/examples/src/main/resources/org/quartz/examples/example11/quartz.properties: -------------------------------------------------------------------------------- 1 | 2 | #============================================================================ 3 | # Configure Main Scheduler Properties 4 | #============================================================================ 5 | 6 | org.quartz.scheduler.instanceName: TestScheduler 7 | org.quartz.scheduler.instanceId: AUTO 8 | 9 | #============================================================================ 10 | # Configure ThreadPool 11 | #============================================================================ 12 | 13 | org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool 14 | org.quartz.threadPool.threadCount: 15 15 | org.quartz.threadPool.threadPriority: 5 16 | 17 | #============================================================================ 18 | # Configure JobStore 19 | #============================================================================ 20 | 21 | org.quartz.jobStore.misfireThreshold: 60000 22 | 23 | org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore 24 | -------------------------------------------------------------------------------- /03~并发框架/Disq/codes/disq/src/test/java/net/intelie/disq/dson/StringCacheTest.java: -------------------------------------------------------------------------------- 1 | package net.intelie.disq.dson; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.assertj.core.api.Assertions.assertThat; 6 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 7 | 8 | public class StringCacheTest { 9 | @Test 10 | public void testMustBePowerOfTwo() { 11 | assertThatThrownBy(() -> new StringCache(123, 1024)) 12 | .isInstanceOf(IllegalArgumentException.class) 13 | .hasMessageContaining("bucketCount must be a power of two"); 14 | } 15 | 16 | @Test 17 | public void testCacheHit() { 18 | StringCache cache = new StringCache(); 19 | StringBuilder original = new StringBuilder("abcde"); 20 | String cached1 = cache.get(original); 21 | String cached2 = cache.get(original); 22 | 23 | assertThat(original.toString()).isEqualTo(cached1).isNotSameAs(cached1); 24 | assertThat(original.toString()).isEqualTo(cached2).isNotSameAs(cached2); 25 | 26 | assertThat(cached1).isSameAs(cached2); 27 | } 28 | } -------------------------------------------------------------------------------- /09~并发性能优化/锁优化/README.md: -------------------------------------------------------------------------------- 1 | # 锁优化 2 | 3 | 在多核时代,多线程对系统的性能提升有着巨大的作用,但是多线程对系统也相应的产生了额外的开销,线程本身的数据、线程的调度、线程的申请释放等都对系统的产生额外的负担,锁是在高并发的环境下为了保证数据的正确性而产生的同步手段,为了进一步提升性能,我们就需要对锁进行优化。 4 | 5 | 锁的优化思路有:从减小锁的持有时间、锁的粒度的优化、锁分离、锁消除、无锁等等。 6 | 7 | ## 锁的代价 8 | 9 | 现实编程过程中,加锁通常会严重地影响性能。线程会因为竞争不到锁而被挂起,等锁被释放的时候,线程又会被恢复,这个过程中存在着很大的开销,并且通常会有较长时间的中断,因为当一个线程正在等待锁时,它不能做任何其他事情。如果一个线程在持有锁的情况下被延迟执行,例如发生了缺页错误、调度延迟或者其它类似情况,那么所有需要这个锁的线程都无法执行下去。如果被阻塞线程的优先级较高,而持有锁的线程优先级较低,就会发生优先级反转。 10 | 11 | Disruptor 论文中讲述了一个实验:这个测试程序调用了一个函数,该函数会对一个 64 位的计数器循环自增 5 亿次;机器环境:2.4G 6 核,运算 64 位的计数器累加 5 亿次。 12 | 13 | | Method | Time (ms) | 14 | | --------------------------------- | --------- | 15 | | Single thread | 300 | 16 | | Single thread with CAS | 5,700 | 17 | | Single thread with lock | 10,000 | 18 | | Single thread with volatile write | 4,700 | 19 | | Two threads with CAS | 30,000 | 20 | | Two threads with lock | 224,000 | 21 | 22 | CAS 操作比单线程无锁慢了 1 个数量级;有锁且多线程并发的情况下,速度比单线程无锁慢 3 个数量级。可见无锁速度最快。单线程情况下,不加锁的性能 > CAS 操作的性能 > 加锁的性能。在多线程情况下,为了保证线程安全,必须使用 CAS 或锁,这种情况下,CAS 的性能超过锁的性能,前者大约是后者的 8 倍。 23 | -------------------------------------------------------------------------------- /02~线程安全/锁/synchronized/README.md: -------------------------------------------------------------------------------- 1 | # synchronized 2 | 3 | synchronized 同步,该关键字确保代码块同一时间只被一个线程执行,在这个前提下再设计符合线程安全的逻辑: 4 | 5 | - 对象:对象加锁,进入同步代码块之前获取对象锁 6 | - 实例方法:对象加锁,执行实例方法前获取对象实例锁 7 | - 类方法:类加锁,执行类方法前获取类锁 8 | 9 | 在多线程并发编程中 synchronized 一直是元老级角色,很多人都会称呼它为重量级锁。但是,随着 Java SE 1.6 对 synchronized 进行了各种优化之后,有些情况下它就并不那么重了;引入了偏向锁和轻量级锁,对锁的存储结构和升级过程,有效减少获得锁和释放锁带来的性能消耗。synchronized 关键字,同时解决了原子性、可见性、有序性问题: 10 | 11 | - 可见性:按照 JMM 规范,对一个变量解锁之前,必须先把此变量同步回主存中,这样解锁后,后续线程就可以访问到被修改后的值。所以被 synchronized 锁住的对象,其值具有可见性。 12 | - 原子性:通过监视器锁,可以保证 synchronized 修饰的代码在同一时间,只能被一个线程访问,在锁未释放之前其它线程无法进入该方法或代码块,保证了操作的原子性。 13 | - 有序性:synchronized 关键字并不禁止指令重排,但是由于程序是以单线程的方式执行的,所以执行的结果是确定的,不会受指令重排的干扰,有序性不再是个问题。 14 | 15 | 需要注意的是,当我们使用 synchronized 关键字,管理某个状态时,必须对访问这个对象的所有操作,都加上 synchronized 关键字,否则仍然会有并发安全性问题。 16 | 17 | # 同步使用 18 | 19 | - 对于,普通同步方法,锁是当前实例对象。`public synchronized void test(){...}` 20 | - 对于静态同步方法,锁是当前类的 Class 对象。`public static synchronized void test(...){}` 21 | - 对于对于同步方法块,锁是 synchronized 括号中里配置的对象。`synchronized(instance){...}` 22 | 23 | # Links 24 | 25 | - https://blog.csdn.net/significantfrank/article/details/80399179 Synchronized 和 Lock 该如何选择 26 | 27 | - https://mp.weixin.qq.com/s/w5K8kmNwAcIxB5lb1N93pg synchronized 连环问 28 | -------------------------------------------------------------------------------- /04~并发网络 IO/NIO/Buffers.md: -------------------------------------------------------------------------------- 1 | # Buffers 2 | 3 | 在 NIO 库中,所有数据都是通过缓冲处理的,在读取数据时,它是直接读到缓冲区中的;在写入数据时,写入到缓冲区中。任何时候访问 NIO 中的数据,都是通过缓冲区进行的。缓冲区实际上是一个容器对象,更直接的说,其实就是一个数组,在 NIO 库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的;在写入数据时,它也是写入到缓冲区中的;任何时候访问 NIO 中的数据,都是将它放到缓冲区中。而在面向流 IO 系统中,所有数据都是直接写入或者直接将数据读取到 Stream 对象中。 4 | 5 | 在 NIO 中,所有的缓冲区类型都继承于抽象类 Buffer,最常用的就是 ByteBuffer,对于 Java 中的基本类型,基本都有一个具体 Buffer 类型与之相对应,它们之间的继承关系如下图所示: 6 | 7 | ![](http://hi.csdn.net/attachment/201107/17/0_131088834611J5.gif) 8 | 9 | - ByteBuffer 10 | - CharBuffer 11 | - DoubleBuffer 12 | - FloatBuffer 13 | - IntBuffer 14 | - LongBuffer 15 | - ShortBuffer 16 | - MappedByteBuffer 17 | 18 | # IntBuffer 19 | 20 | ```java 21 | // 分配新的 int 缓冲区,参数为缓冲区容量 22 | // 新缓冲区的当前位置将为零,其界限(限制位置)将为其容量。它将具有一个底层实现数组,其数组偏移量将为零。 23 | IntBuffer buffer = IntBuffer.allocate(8); 24 | 25 | for (int i = 0; i < buffer.capacity(); ++i) { 26 | int j = 2 * (i + 1); 27 | // 将给定整数写入此缓冲区的当前位置,当前位置递增 28 | buffer.put(j); 29 | } 30 | 31 | // 重设此缓冲区,将限制设置为当前位置,然后将当前位置设置为 0 32 | buffer.flip(); 33 | 34 | // 查看在当前位置和限制位置之间是否有元素 35 | while (buffer.hasRemaining()) { 36 | // 读取此缓冲区当前位置的整数,然后当前位置递增 37 | int j = buffer.get(); 38 | System.out.print(j + " "); 39 | } 40 | ``` 41 | -------------------------------------------------------------------------------- /04~并发网络 IO/Netty/README.md: -------------------------------------------------------------------------------- 1 | # Netty 2 | 3 | Netty 是一个异步网络库,使 Java NIO 的功能更好用。在《[Concurrent-Notes/并发 IO](https://github.com/wx-chevalier/Concurrent-Notes?q=)》中我们讨论了 Reactor 模型,Netty 主要基于主从 Reactor 多线程模型发展出来的: 4 | 5 | ![Netty 模型](https://ngte-superbed.oss-cn-beijing.aliyuncs.com/superbed/2021/07/29/61022c2a5132923bf8d96dbf.jpg) 6 | 7 | # Netty 网络分层架构 8 | 9 | Nettty 逻辑架构为典型网络分层架构设计,从下到上分别为网络通信层、事件调度层、服务编排层。 10 | 11 | ![Netty 分层架构](https://ngte-superbed.oss-cn-beijing.aliyuncs.com/superbed/2021/07/29/61022c4a5132923bf8d9c41d.jpg) 12 | 13 | - 网络通信层:它执行网络 I/O 操作,核心组件包含 BootStrap、ServerBootStrap、Channel。 14 | 15 | - Channel 通道,提供了基础的 API 用于操作网络 IO,比如 bind、connect、read、write、flush 等等。它以 JDK NIO Channel 为基础,提供了更高层次的抽象,同时屏蔽了底层 Socket 的复杂性。Channel 有多种状态,比如连接建立、数据读写、连接断开。随着状态的变化,Channel 处于不同的生命周期,背后绑定相应的事件回调函数。 16 | 17 | - 事件调度层:它的核心组件包含 EventLoopGroup、EventLoop。 18 | 19 | - EventLoop 本质是一个线程池,主要负责接收 Socket I/O 请求,并分配事件循环器来处理连接生命周期中所发生的各种事件。 20 | 21 | - 服务编排层:它的职责实现网络事件的动态编排和有序传播。 22 | - ChannelPipeline 基于责任链模式,方便业务逻辑的拦截和扩展;本质上它是一个双向链表将不同的 ChannelHandler 链接在一块,当 I/O 读写事件发生时, 会依次调用 ChannelHandler 对 Channel(Socket) 读取的数据进行处理。 23 | 24 | # Links 25 | 26 | - https://juejin.im/post/5df35adb6fb9a0163a4830e7 27 | -------------------------------------------------------------------------------- /01~线程与线程池/10~协程/Loom/README.md: -------------------------------------------------------------------------------- 1 | # Loom 2 | 3 | 内核线程始终是一种稀缺资源。如果你真的把它归结为,这里真正的基础资源是硬件提供的物理线程,根据定义,物理线程是有限的。即使在抽象塔上,内核线程在操作系统本身和 JVM 中都是相对重要的。一般来说,一个进程很难拥有超过几千个线程,即使仔细调整也是如此,而拥有更少的线程则是更理想的。Loom 所做的是和 Cats Effect 这样的框架玩同样的把戏,也就是说,它在底层内核线程(它称之为 "载体线程")之上创建一个抽象。这个抽象是非常轻量级的,而且严格来说(有点......)是非阻塞的,这使得在一个进程中拥有数百万个线程而不产生问题成为可能。也许令人困惑的是,Loom 将这个抽象定义为 Thread 本身,并将其直接集成到 JVM 中,这意味着任何在 JVM 上编写的代码都能够利用它(而不是像 Cats Effect 这样的框架,在那里你需要明确选择加入 IO 或 Future 等东西)。 4 | 5 | 因此,这里发生的事情是,Thread 被重新定义为一个更轻量级的抽象,位于底层载体线程之上,而底层载体线程与以往一样稀缺且重量级。这样做的好处是,你需要非常小心地处理那些硬性阻断底层载体线程的事情。Loom 试图通过与 JVM 和 Java 标准库的紧密结合来解决这个问题,这样一来,通常会阻断载体线程的机制反而会取消虚拟线程的时间安排,允许其他线程访问。更简单地说,它将 Unsafe.park 转换为一个回调,在运行时恢复线程的延续性。 6 | 7 | 这是一个聪明的技巧,特别是集成到 JVM 中,但它并不完美。正如你所指出的,本地代码中的任何阻塞都完全超出了 Loom 所能保护的范围,而且这种阻塞比你想象的要普遍得多。例如,Netty 在本地代码中非常积极地进行阻塞,因为它实现了自己的操作系统特定的异步 IO 层接口(如 epoll 和 io_uring)。即使没有第三方框架,本机阻塞的例子也比比皆是。`new URL("https://www.google.com").hashCode()` 就是一个例子,因为它委托给了本机操作系统的 DNS 客户端,而后者在所有主要操作系统上都是阻塞的。另一个例子是文件 IO,它在 NTFS 上是无阻塞的,在支持 io_uring 的 Linux 版本上也可以是无阻塞的,但在 APFS 和 HFS+上根本上是阻塞的。 8 | 9 | 换句话说,Loom 是一个典型的泄漏性抽象:它承诺了一些它无法实现的东西,并在这样做时邀请你编写代码,而这些假设在许多常见情况下是不成立的。这就是它与 Cats Effect 或 Vert.x 等框架的真正不同之处,后者非常直接地指出阻塞是不好的,并促使你(用户)努力声明你的阻塞,以便可以用不太危险的方式管理它(特别是通过分流策略,如 OP 中描述的)。 10 | -------------------------------------------------------------------------------- /03~并发框架/Quartz/codes/examples/src/main/resources/org/quartz/examples/example12/server.properties: -------------------------------------------------------------------------------- 1 | #============================================================================ 2 | # Configure Main Scheduler Properties 3 | #============================================================================ 4 | 5 | org.quartz.scheduler.instanceName: Sched1 6 | org.quartz.scheduler.rmi.export: true 7 | org.quartz.scheduler.rmi.registryHost: localhost 8 | org.quartz.scheduler.rmi.registryPort: 1099 9 | org.quartz.scheduler.rmi.createRegistry: true 10 | 11 | #============================================================================ 12 | # Configure ThreadPool 13 | #============================================================================ 14 | 15 | org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool 16 | org.quartz.threadPool.threadCount: 10 17 | org.quartz.threadPool.threadPriority: 5 18 | 19 | #============================================================================ 20 | # Configure JobStore 21 | #============================================================================ 22 | 23 | org.quartz.jobStore.misfireThreshold: 60000 24 | 25 | org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore 26 | -------------------------------------------------------------------------------- /03~并发框架/Akka/并发控制与事务.md: -------------------------------------------------------------------------------- 1 | # Transaction | 并发控制与事务 2 | 3 | # Memory Model | 内存模型 4 | 5 | With the Actors implementation in Akka, there are two ways multiple threads can execute actions on shared memory: 6 | 7 | if a message is sent to an actor (e.g. by another actor). In most cases messages are immutable, but if that message is not a properly constructed immutable object, without a “happens before” rule, it would be possible for the receiver to see partially initialized data structures and possibly even values out of thin air (longs/doubles). 8 | if an actor makes changes to its internal state while processing a message, and accesses that state while processing another message moments later. It is important to realize that with the actor model you don’t get any guarantee that the same thread will be executing the same actor for different messages. 9 | To prevent visibility and reordering problems on actors, Akka guarantees the following two “happens before” rules: 10 | 11 | The actor send rule: the send of the message to an actor happens before the receive of that message by the same actor. 12 | The actor subsequent processing rule: processing of one message happens before processing of the next message by the same actor. 13 | 14 | # Dispatcher 15 | 16 | # STM 软件事务内存 17 | -------------------------------------------------------------------------------- /03~并发框架/Disq/codes/disq/src/main/java/net/intelie/disq/DefaultSerializer.java: -------------------------------------------------------------------------------- 1 | package net.intelie.disq; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.io.IOException; 7 | import java.io.ObjectInputStream; 8 | import java.io.ObjectOutputStream; 9 | 10 | public class DefaultSerializer implements Serializer, SerializerFactory { 11 | private static final Logger LOGGER = LoggerFactory.getLogger(DefaultSerializer.class); 12 | 13 | @Override 14 | public void serialize(Buffer buffer, T obj) throws IOException { 15 | try (ObjectOutputStream oos = new ObjectOutputStream(buffer.write())) { 16 | oos.writeObject(obj); 17 | } 18 | } 19 | 20 | @Override 21 | public T deserialize(Buffer buffer) throws IOException { 22 | try (ObjectInputStream ois = new ObjectInputStream(buffer.read())) { 23 | try { 24 | return (T) ois.readObject(); 25 | } catch (ClassNotFoundException e) { 26 | LOGGER.info("Exception on default deserializer", e); 27 | throw new IOException(e); 28 | } 29 | } 30 | } 31 | 32 | @Override 33 | public Serializer create() { 34 | return this; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /03~并发框架/Disq/codes/disq/src/main/java/net/intelie/disq/DeleteFileVisitor.java: -------------------------------------------------------------------------------- 1 | package net.intelie.disq; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.io.IOException; 7 | import java.nio.file.FileVisitResult; 8 | import java.nio.file.Files; 9 | import java.nio.file.Path; 10 | import java.nio.file.SimpleFileVisitor; 11 | import java.nio.file.attribute.BasicFileAttributes; 12 | 13 | public class DeleteFileVisitor extends SimpleFileVisitor { 14 | private static final Logger LOGGER = LoggerFactory.getLogger(DeleteFileVisitor.class); 15 | 16 | @Override 17 | public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { 18 | tryDelete(file); 19 | return FileVisitResult.CONTINUE; 20 | } 21 | 22 | @Override 23 | public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { 24 | tryDelete(dir); 25 | return FileVisitResult.CONTINUE; 26 | } 27 | 28 | private void tryDelete(Path dir) { 29 | try { 30 | Files.delete(dir); 31 | } catch (Exception e) { 32 | LOGGER.info("Could not delete {}: {}", dir, e.getMessage()); 33 | LOGGER.debug("Stacktrace", e); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /01~线程与线程池/异步编程/05~定时器/定时器.md: -------------------------------------------------------------------------------- 1 | # Timer 2 | 3 | Timer 计时器具备使任务延迟执行以及周期性执行的功能,但是 Timer 天生存在一些缺陷,所以从 JDK 1.5 开始就推荐使用 ScheduledThreadPoolExecutor(ScheduledExecutorService 实现类)作为其替代工具。 4 | 5 | 首先 Timer 对提交的任务调度是基于绝对时间而不是相对时间的,所以通过其提交的任务对系统时钟的改变是敏感的(譬如提交延迟任务后修改了系统时间会影响其执行);而 ScheduledThreadExecutor 只支持相对时间,对系统时间不敏感。 6 | 7 | 接着 Timer 的另一个问题是如果 TimerTask 抛出未检查异常则 Timer 将会产生无法预料的行为,因为 Timer 线程并不捕获异常,所以 TimerTask 抛出的未检查异常会使 Timer 线程终止,所以后续提交的任务得不到执行;而 ScheduledThreadPoolExecutor 不存在此问题。 8 | 9 | 所有的现代操作系统都通过进程和线程来支持并发。进程是通常彼此独立运行的程序的实例,比如,如果你启动了一个 Java 程序,操作系统产生一个新的进程,与其他程序一起并行执行。在这些进程的内部,我们使用线程并发执行代码,因此,我们可以最大限度的利用 CPU 可用的核心(core)。Java 从 JDK1.0 开始执行线程。在开始一个新的线程之前,你必须指定由这个线程执行的代码,通常称为 task。这可以通过实现 Runnable——一个定义了一个无返回值无参数的 run()方法的函数接口,如下面的代码所示: 10 | 11 | ```java 12 | Runnable task = () -> { 13 | String threadName = Thread.currentThread().getName(); 14 | System.out.println("Hello " + threadName); 15 | }; 16 | 17 | task.run(); 18 | 19 | Thread thread = new Thread(task); 20 | thread.start(); 21 | 22 | System.out.println("Done!"); 23 | ``` 24 | 25 | 因为 Runnable 是一个函数接口,所以我们利用 lambda 表达式将当前的线程名打印到控制台。首先,在开始一个线程前我们在主线程中直接运行 runnable。控制台输出的结果可能像下面这样: 26 | 27 | ``` 28 | Hello main 29 | Hello Thread-0 30 | Done! 31 | ``` 32 | 33 | 或者这样: 34 | 35 | ``` 36 | Hello main 37 | Done! 38 | Hello Thread-0 39 | ``` 40 | -------------------------------------------------------------------------------- /01~线程与线程池/01~线程基础/线程创建/Thread 与 Runnable.md: -------------------------------------------------------------------------------- 1 | # Thread 与 Runnable 2 | 3 | - 新建线程:new Thread(),新建一个线程对象,内存为线程在栈上分配好内存空间 4 | - 启动线程:start(),告诉系统系统准备就绪,只要资源允许随时可以执行我栈里面的指令了 5 | - 执行线程:run(),分配了 CPU 等计算资源,正在执行栈里面的指令集 6 | - 停止线程(过时):stop(),把 CPU 和内存资源回收,线程消亡,由于太过粗暴,已经被标记为过时 7 | - 线程中断: 8 | - interrupt(),中断是对线程打上了中断标签,可供 run()里面的方法体接收中断信号,至于线程要不要中断,全靠业务逻辑设计,而不是简单粗暴的把线程直接停掉 9 | - isInterrupt(),主要是 run()方法体来判断当前线程是否被置为中断 10 | - interrupted(),静态方法,也是用户判断线程是否被置为中断状态,同时判断完将线程中断状态复位 11 | - 线程休眠:sleep(),静态方法,线程休眠指定时间段,此间让出 CPU 资源给其他线程,但是线程依然持有对象锁,其他线程无法进入同步块,休眠完成后也未必立刻执行,需要等到资源允许才能执行 12 | - 线程等待(对象方法):wait() 是 Object 的方法,也即是对象的内置方法,在同步块中线程执行到该方法时,也即让出了该对象的锁,所以无法继续执行 13 | - 线程通知(对象方法):notify()、notifyAll(),此时该对象持有一个或者多个线程的 wait,调用 notify() 随机的让一个线程恢复对象的锁,调用 notifyAll() 则让所有线程恢复对象锁 14 | - 线程挂起(过时):suspend(),线程挂起并没有释放资源,而是只能等到 resume()才能继续执行 15 | - 线程恢复(过时):resume(),由于指令重排可能导致 resume()先于 suspend()执行,导致线程永远挂起,所以该方法被标为过时 16 | - 线程加入:join(),在一个线程调用另外一个线程的 join()方法表明当前线程阻塞知道被调用线程执行结束再进行,也即是被调用线程织入进来 17 | - 线程让步:yield(),暂停当前线程进而执行别的线程,当前线程等待下一轮资源允许再进行,防止该线程一直霸占资源,而其他线程饿死 18 | - 线程等待:park(),基于线程对象的操作,较对象锁更为精准 19 | - 线程恢复:unpark(Thread thread),对应 park()解锁,为不可重入锁 20 | 21 | # 线程分组 22 | 23 | 为了管理线程,于是有了线程组的概念,业务上把类似的线程放在一个 ThreadGroup 里面统一管理。线程组表示一组线程,此外,线程组还可以包括其他线程组。线程组形成一个树,其中除了初始线程组以外的每个线程组都有一个父线程。线程被允许访问它自己的线程组信息,但不能访问线程组的父线程组或任何其他线程组的信息。 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore all 2 | * 3 | 4 | # Unignore all with extensions 5 | !*.* 6 | 7 | # Unignore all dirs 8 | !*/ 9 | 10 | .DS_Store 11 | 12 | # Logs 13 | logs 14 | *.log 15 | npm-debug.log* 16 | yarn-debug.log* 17 | yarn-error.log* 18 | 19 | # Runtime data 20 | pids 21 | *.pid 22 | *.seed 23 | *.pid.lock 24 | 25 | # Directory for instrumented libs generated by jscoverage/JSCover 26 | lib-cov 27 | 28 | # Coverage directory used by tools like istanbul 29 | coverage 30 | 31 | # nyc test coverage 32 | .nyc_output 33 | 34 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 35 | .grunt 36 | 37 | # Bower dependency directory (https://bower.io/) 38 | bower_components 39 | 40 | # node-waf configuration 41 | .lock-wscript 42 | 43 | # Compiled binary addons (https://nodejs.org/api/addons.html) 44 | build/Release 45 | 46 | # Dependency directories 47 | node_modules/ 48 | jspm_packages/ 49 | 50 | # TypeScript v1 declaration files 51 | typings/ 52 | 53 | # Optional npm cache directory 54 | .npm 55 | 56 | # Optional eslint cache 57 | .eslintcache 58 | 59 | # Optional REPL history 60 | .node_repl_history 61 | 62 | # Output of 'npm pack' 63 | *.tgz 64 | 65 | # Yarn Integrity file 66 | .yarn-integrity 67 | 68 | # dotenv environment variables file 69 | .env 70 | 71 | # next.js build output 72 | .next 73 | -------------------------------------------------------------------------------- /01~线程与线程池/01~线程基础/README.md: -------------------------------------------------------------------------------- 1 | # 线程 2 | 3 | 操作系统中的多任务即在同一时刻运行多个程序的能力。操作系统将 CPU 的时间片分配给每一个进程,给人并行处理的感觉。多线程程序在较低的层次上扩展了多任务的概念:一个程序同时执行多个任务。通常,每一个任务称为一个线程。可以同时运行一个以上线程的程序称为是多线程程序。 4 | 5 | 多进程与多线程本质的区别在于每个进程拥有自己的一整套变量,而线程则共享数据。共享变量使线程之间的通信比进程之间的通信更有效、更容易。在有些操作系统中,与进程相比,线程更“轻量级”,创建、撤销一个线程比启动新进程的开销要小得多。 6 | 7 | # Java 线程与操作系统线程 8 | 9 | Java 线程在 JDK1.2 之前,是基于称为绿色线程(Green Threads)的用户线程实现的,而到了 JDK1.2 及以后,JVM 选择了更加稳健且方便使用的操作系统原生的线程模型,通过系统调用,将程序的线程交给了操作系统内核进行调度。因此,在目前的 JDK 版本中,操作系统支持怎样的线程模型,在很大程度上决定了 Java 虚拟机的线程是怎样映射的,这点在不同的平台上没有办法达成一致,虚拟机规范中也并未限定 Java 线程需要使用哪种线程模型来实现。线程模型只对线程的并发规模和操作成本产生影响,对 Java 程序的编码和运行过程来说,这些差异都是透明的。 10 | 11 | 对于 Sun JDK 来说,它的 Windows 版与 Linux 版都是使用一对一的线程模型实现的,一条 Java 线程就映射到一条轻量级进程之中,因为 Windows 和 Linux 系统提供的线程模型就是一对一的。也就是说,现在的 Java 中线程的本质,其实就是操作系统中的线程,Linux 下是基于 pthread 库实现的轻量级进程,Windows 下是原生的系统 Win32 API 提供系统调用从而实现多线程。 12 | 13 | ![Java 线程状态的变化](https://ngte-superbed.oss-cn-beijing.aliyuncs.com/item/20230417211943.png) 14 | 15 | 在现在的操作系统中,因为线程依旧被视为轻量级进程,所以操作系统中线程的状态实际上和进程状态是一致的模型。从实际意义上来讲,操作系统中的线程除去 new 和 terminated 状态,一个线程真实存在的状态,只有: 16 | 17 | - `ready`:表示线程已经被创建,正在等待系统调度分配 CPU 使用权。 18 | - `running`:表示线程获得了 CPU 使用权,正在进行运算。 19 | - `waiting`:表示线程等待(或者说挂起),让出 CPU 资源给其他线程使用。 20 | 21 | 对于 Java 中的线程状态:无论是 Timed Waiting,Waiting 还是 Blocked,对应的都是操作系统线程的 waiting(等待)状态。而 Runnable 状态,则对应了操作系统中的 ready 和 running 状态。Java 线程和操作系统线程,实际上同根同源,但又相差甚远。 22 | -------------------------------------------------------------------------------- /03~并发框架/Disq/codes/disq/src/main/java/net/intelie/disq/DataFileWriter.java: -------------------------------------------------------------------------------- 1 | package net.intelie.disq; 2 | 3 | import java.io.*; 4 | import java.nio.file.Path; 5 | 6 | public class DataFileWriter implements Closeable { 7 | public static final int OVERHEAD = 4; 8 | private final DataOutputStream stream; 9 | private final FileOutputStream fos; 10 | private final File file; 11 | 12 | public DataFileWriter(Path file, long position) throws IOException { 13 | this.file = file.toFile(); 14 | setLength(this.file, position); 15 | fos = new FileOutputStream(this.file, true); 16 | stream = new DataOutputStream(new BufferedOutputStream(fos, 1024 * 1024)); 17 | } 18 | 19 | private void setLength(File file, long size) throws IOException { 20 | RandomAccessFile rand = new RandomAccessFile(file, "rws"); 21 | rand.setLength(size); 22 | rand.close(); 23 | } 24 | 25 | public int write(Buffer buffer) throws IOException { 26 | stream.writeInt(buffer.count()); 27 | stream.write(buffer.buf(), 0, buffer.count()); 28 | //stream.flush(); 29 | return buffer.count() + OVERHEAD; 30 | } 31 | 32 | public void flush() throws IOException { 33 | stream.flush(); 34 | } 35 | 36 | @Override 37 | public void close() throws IOException { 38 | stream.close(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /02~线程安全/01~内存模型/JMM 内存模型/README.md: -------------------------------------------------------------------------------- 1 | # Java 内存模型(Java Memory Model, JMM) 2 | 3 | ## 概述 4 | 5 | Java 内存模型(JMM)是一个规范,它定义了 Java 程序中各个变量(线程共享变量)的访问规则,以及在 JVM 中将变量存储到内存和从内存中读取变量这样的底层细节。 6 | 7 | ![Java 内存模型](https://ngte-superbed.oss-cn-beijing.aliyuncs.com/item/20230417213538.png) 8 | 9 | ## 为什么需要内存模型 10 | 11 | 现代计算机系统中,CPU 处理器的运算速度和内存的访问速度差距较大,通常有数个数量级的差异。为了解决这个问题,计算机系统在 CPU 和主内存之间引入了高速缓存: 12 | 13 | ![Java 工作内存与 CPU 高速缓存](https://s3.ax1x.com/2021/01/28/y9e6JI.png) 14 | 15 | ## JMM 的核心概念 16 | 17 | Java 内存模型规定: 18 | 19 | - 所有变量都存储在主内存(Main Memory)中 20 | - 每个线程都有自己的工作内存(Working Memory) 21 | - 线程不能直接操作主内存中的变量,而是必须先将变量从主内存加载到工作内存中 22 | - 线程对变量的所有操作都必须在工作内存中进行 23 | 24 | ## happens-before 原则 25 | 26 | happens-before 是 JMM 中非常重要的概念,它定义了操作之间的内存可见性。如果一个操作 A happens-before 另一个操作 B,那么 A 操作的结果对 B 操作是可见的。主要规则包括: 27 | 28 | 1. **程序次序规则**:单线程内,按照程序代码顺序执行 29 | 2. **管程锁定规则**:解锁操作 happens-before 后续的加锁操作 30 | 3. **volatile 变量规则**:volatile 变量的写操作 happens-before 后续的读操作 31 | 4. **线程启动规则**:线程的 start() 方法 happens-before 该线程的所有操作 32 | 5. **线程终止规则**:线程的所有操作 happens-before 线程的终止检测 33 | 6. **对象终结规则**:对象的构造函数执行结束 happens-before finalize() 方法 34 | 35 | 需要注意的是,虽然 JVM 可能会对指令进行重排序,但是它会保证在单线程中执行结果的正确性。重排序只会针对不存在数据依赖性的指令进行。 36 | 37 | ## JMM 的发展 38 | 39 | Java 内存模型在 1995 年首次提出,主要目标是解决不同硬件平台和操作系统上的线程同步问题。在 Java 5 之前,JMM 存在诸多问题,例如: 40 | 41 | - 线程间的可见性问题 42 | - 指令重排序导致的异常行为 43 | 44 | Java 5 对 JMM 进行了重大修订,通过更严格的规范和新的同步机制(如 volatile 的增强语义),显著改善了并发编程的可靠性。 45 | -------------------------------------------------------------------------------- /01~线程与线程池/02~线程池/线程池类型/04~ForkJoin/README.md: -------------------------------------------------------------------------------- 1 | # ForkJoin 2 | 3 | Fork/Join 框架是 Java7 提供了的一个用于并行执行任务的框架,是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。我们再通过 Fork 和 Join 这两个单词来理解下 Fork/Join 框架,Fork 就是把一个大任务切分为若干子任务并行的执行,Join 就是合并 这些子任务的执行结果,最后得到这个大任务的结果。比如计算 1+2+。。+ 10000,可以分割成 10 个子任务,每个子任务分别对 1000 个数进行求和,最终汇总这 10 个子任务的结果。Fork/Join 的运行流程图如下: 4 | 5 | ![](http://cdn3.infoqstatic.com/statics_s1_20160405-0343u1/resource/articles/fork-join-introduction/zh/resources/21.png) 6 | 7 | # 工作窃取算法 8 | 9 | 工作窃取(work-stealing)算法是指某个线程从其他队列里窃取任务来执行。工作窃取的运行流程图如下: 10 | 11 | ![工作窃取算法](https://ngte-superbed.oss-cn-beijing.aliyuncs.com/superbed/2021/07/16/60f197135132923bf88e6373.jpg) 12 | 13 | 假如我们需要做一个比较大的任务,我们可以把这个任务分割为若干互不依赖的子任务,为了减少线程间的竞争,于是把这些 子任务分别放到不同的队列里,并为每个队列创建一个单独的线程来执行队列里的任务,线程和队列一一对应,比如 A 线程负责处理 A 队列里的任务。但是有的线程 会先把自己队列里的任务干完,而其他线程对应的队列里还有任务等待处理。 14 | 15 | 干完活的线程与其等着,不如去帮其他线程干活,于是它就去其他线程的队列里窃取一个任务来执行。而在这时它们会访问同一个队列,所以为了减少窃取任务线程和被窃取任务线程之间的竞争,通常会使用双端队列,被窃取任务线程永远从双端队列 的头部拿任务执行,而窃取任务的线程永远从双端队列的尾部拿任务执行。 16 | 17 | 工作窃取算法的优点是充分利用线程进行并行计算,并减少了线程间的竞争,其缺点是在某些情况下还是存在竞争,比如双端队列里只有一个任务时。并且消耗了更多的系统资源,比如创建多个线程和多个双端队列。 18 | 19 | # Links 20 | 21 | - [concurrency-torture-testing-your-code-within-the-java-memory-model](http://zeroturnaround.com/rebellabs/concurrency-torture-testing-your-code-within-the-java-memory-model/) 22 | - https://www.baeldung.com/java-fork-join 23 | - [聊聊并发(八)——Fork/Join 框架介绍](http://www.infoq.com/cn/articles/fork-join-introduction) 24 | -------------------------------------------------------------------------------- /03~并发框架/RxJava/工程实践/线程调度.md: -------------------------------------------------------------------------------- 1 | # RxJava 线程调度 2 | 3 | # Scheduler 4 | 5 | `Scheduler`是`RxJava`的线程调度器,可以指定代码执行的线程。RxJava 内置了几种线程: 6 | 7 | - `AndroidSchedulers.mainThread()` 主线程 8 | - `Schedulers.immediate()` 当前线程,即默认`Scheduler` 9 | - `Schedulers.newThread()` 启用新线程 10 | - `Schedulers.io()` IO 线程,内部是一个数量无上限的线程池,可以进行文件、数据库和网络操作。 11 | - `Schedulers.computation()` CPU 计算用的线程,内部是一个数目固定为 CPU 核数的线程池,适合于 CPU 密集型计算,不能操作文件、数据库和网络。 12 | 13 | `subscribeOn()`和`observeOn()`可以用来控制代码的执行线程。 14 | 15 | ```java 16 | Observable.create(new Observable.OnSubscribe() { 17 | @Override 18 | public void call(Subscriber subscriber) { 19 | Log.d(TAG, "OnSubscribe.call Thread -> " + Thread.currentThread().getName()); 20 | subscriber.onNext("message"); 21 | } 22 | }).subscribeOn(Schedulers.io()) 23 | .observeOn(AndroidSchedulers.mainThread()) //这一句在J2EE中无法执行 24 | .subscribe(new Subscriber() { 25 | @Override 26 | public void onCompleted() { 27 | 28 | } 29 | 30 | @Override 31 | public void onError(Throwable e) { 32 | 33 | } 34 | 35 | @Override 36 | public void onNext(String s) { 37 | Log.d(TAG, "Subscriber.onNext Thread -> " + Thread.currentThread().getName()); 38 | } 39 | }); 40 | ``` 41 | 42 | 根据打印出的 Log 可以得出结论: 43 | subscribeOn()指定 OnSubscribe.call()的执行线程,即 Observable 通知 Subscriber 的线程; 44 | observeOn()指定 Subscriber 回调的执行线程,即事件消费的线程。 45 | 46 | # Links 47 | -------------------------------------------------------------------------------- /03~并发框架/Disq/codes/disq/src/main/java/net/intelie/disq/dson/Latin1View.java: -------------------------------------------------------------------------------- 1 | package net.intelie.disq.dson; 2 | 3 | public class Latin1View implements StringView { 4 | private StringBuilder builder; 5 | private byte[] buf; 6 | private int start; 7 | private int length; 8 | 9 | @Override 10 | public void clear() { 11 | buf = null; 12 | } 13 | 14 | @Override 15 | public void set(byte[] buf, int start, int length) { 16 | this.buf = buf; 17 | this.start = start; 18 | this.length = length; 19 | } 20 | 21 | @Override 22 | public int length() { 23 | return length; 24 | } 25 | 26 | @Override 27 | public char charAt(int i) { 28 | return (char) (buf[start + i] & 0xFF); 29 | } 30 | 31 | @Override 32 | public Latin1View subSequence(int startIndex, int endIndex) { 33 | Latin1View view = new Latin1View(); 34 | subSequence(startIndex, endIndex, view); 35 | return view; 36 | } 37 | 38 | public void subSequence(int startIndex, int endIndex, Latin1View target) { 39 | target.set(buf, start + startIndex, endIndex - startIndex); 40 | } 41 | 42 | @Override 43 | public String toString() { 44 | if (builder == null) { 45 | builder = new StringBuilder(length); 46 | } else { 47 | builder.ensureCapacity(length); 48 | builder.setLength(0); 49 | } 50 | return builder.append(this).toString(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /03~并发框架/Disq/codes/disq/src/test/java/net/intelie/disq/GsonSerializer.java: -------------------------------------------------------------------------------- 1 | package net.intelie.disq; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | 6 | import java.io.IOException; 7 | import java.io.InputStreamReader; 8 | import java.io.OutputStreamWriter; 9 | import java.nio.charset.StandardCharsets; 10 | 11 | public class GsonSerializer implements SerializerFactory { 12 | private final Gson gson = new GsonBuilder().serializeNulls().create(); 13 | private final Class clazz; 14 | 15 | public GsonSerializer(Class clazz) { 16 | this.clazz = clazz; 17 | } 18 | 19 | @Override 20 | public Serializer create() { 21 | return new Serializer() { 22 | @Override 23 | public void serialize(Buffer buffer, T obj) throws IOException { 24 | try (OutputStreamWriter writer = new OutputStreamWriter(buffer.write(), StandardCharsets.UTF_8)) { 25 | gson.toJson(obj, writer); 26 | } 27 | } 28 | 29 | @Override 30 | public T deserialize(Buffer buffer) throws IOException { 31 | try (InputStreamReader reader = new InputStreamReader(buffer.read(), StandardCharsets.UTF_8)) { 32 | return gson.fromJson(reader, clazz); 33 | } 34 | } 35 | }; 36 | } 37 | 38 | public static GsonSerializer make() { 39 | return new GsonSerializer<>(Object.class); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /03~并发框架/Quartz/codes/examples/src/main/java/org/quartz/examples/example13/SimpleRecoveryStatefulJob.java: -------------------------------------------------------------------------------- 1 | /* 2 | * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | * 16 | */ 17 | 18 | package org.quartz.examples.example13; 19 | 20 | import org.quartz.DisallowConcurrentExecution; 21 | import org.quartz.PersistJobDataAfterExecution; 22 | 23 | /** 24 | * This job has the same functionality of SimpleRecoveryJob except that this job implements is 'stateful', in that it 25 | * will have it's data (JobDataMap) automatically re-persisted after each execution, and only one instance of the 26 | * JobDetail can be executed at a time. 27 | * 28 | * @author Bill Kratzer 29 | */ 30 | @PersistJobDataAfterExecution 31 | @DisallowConcurrentExecution 32 | public class SimpleRecoveryStatefulJob extends SimpleRecoveryJob { 33 | 34 | public SimpleRecoveryStatefulJob() { 35 | super(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /02~线程安全/锁/AQS/README.md: -------------------------------------------------------------------------------- 1 | # AQS 2 | 3 | Java 中的大部分同步类(Lock、Semaphore、ReentrantLock 等)都是基于 AbstractQueuedSynchronizer(简称为 AQS)实现的。AQS 是一种提供了原子式管理同步状态、阻塞和唤醒线程功能以及队列模型的简单框架。通过上文我们已经了解,ReentrantLock 支持公平锁和非公平锁,并且 ReentrantLock 的底层就是由 AQS 来实现的。那么 ReentrantLock 是如何通过公平锁和非公平锁与 AQS 关联起来呢? 我们着重从这两者的加锁过程来理解一下它们与 AQS 之间的关系。 4 | 5 | 非公平锁源码中的加锁流程如下: 6 | 7 | ```java 8 | // java.util.concurrent.locks.ReentrantLock#NonfairSync 9 | 10 | // 非公平锁 11 | static final class NonfairSync extends Sync { 12 | ... 13 | final void lock() { 14 | if (compareAndSetState(0, 1)) 15 | setExclusiveOwnerThread(Thread.currentThread()); 16 | else 17 | acquire(1); 18 | } 19 | ... 20 | } 21 | ``` 22 | 23 | 这块代码的含义为: 24 | 25 | - 若通过 CAS 设置变量 State(同步状态)成功,也就是获取锁成功,则将当前线程设置为独占线程。 26 | - 若通过 CAS 设置变量 State(同步状态)失败,也就是获取锁失败,则进入 Acquire 方法进行后续处理。 27 | 28 | 第一步很好理解,但第二步获取锁失败后,后续的处理策略是怎么样的呢?这块可能会有以下思考: 29 | 30 | - 某个线程获取锁失败的后续流程是什么呢?有以下两种可能: 31 | 32 | - 将当前线程获锁结果设置为失败,获取锁流程结束。这种设计会极大降低系统的并发度,并不满足我们实际的需求。所以就需要下面这种流程,也就是 AQS 框架的处理流程。 33 | - 存在某种排队等候机制,线程继续等待,仍然保留获取锁的可能,获取锁流程仍在继续。 34 | 35 | - 对于问题 1 的第二种情况,既然说到了排队等候机制,那么就一定会有某种队列形成,这样的队列是什么数据结构呢? 36 | - 处于排队等候机制中的线程,什么时候可以有机会获取锁呢? 37 | - 如果处于排队等候机制中的线程一直无法获取锁,还是需要一直等待吗,还是有别的策略来解决这一问题? 38 | 39 | 带着非公平锁的这些问题,再看下公平锁源码中获锁的方式: 40 | 41 | ```java 42 | // java.util.concurrent.locks.ReentrantLock#FairSync 43 | 44 | static final class FairSync extends Sync { 45 | ... 46 | final void lock() { 47 | acquire(1); 48 | } 49 | ... 50 | } 51 | ``` 52 | 53 | 结合公平锁和非公平锁的加锁流程,虽然流程上有一定的不同,但是都调用了 Acquire 方法,而 Acquire 方法是 FairSync 和 UnfairSync 的父类 AQS 中的核心方法。 54 | -------------------------------------------------------------------------------- /03~并发框架/Disq/codes/disq/src/main/java/net/intelie/disq/dson/UnicodeView.java: -------------------------------------------------------------------------------- 1 | package net.intelie.disq.dson; 2 | 3 | public class UnicodeView implements StringView { 4 | private StringBuilder builder; 5 | private byte[] buf; 6 | private int start; 7 | private int length; 8 | 9 | 10 | @Override 11 | public void clear() { 12 | buf = null; 13 | } 14 | 15 | @Override 16 | public void set(byte[] buf, int start, int length) { 17 | this.buf = buf; 18 | this.start = start; 19 | this.length = length; 20 | } 21 | 22 | @Override 23 | public int length() { 24 | return length; 25 | } 26 | 27 | @Override 28 | public char charAt(int i) { 29 | return (char) ((buf[start + i * 2] & 0xFF) | (buf[start + i * 2 + 1] << 8)); 30 | } 31 | 32 | @Override 33 | public UnicodeView subSequence(int startIndex, int endIndex) { 34 | UnicodeView view = new UnicodeView(); 35 | subSequence(startIndex, endIndex, view); 36 | return view; 37 | } 38 | 39 | public void subSequence(int startIndex, int endIndex, UnicodeView target) { 40 | target.set(buf, start + startIndex * 2, endIndex - startIndex); 41 | } 42 | 43 | @Override 44 | public String toString() { 45 | if (builder == null) { 46 | builder = new StringBuilder(length); 47 | } else { 48 | builder.ensureCapacity(length); 49 | builder.setLength(0); 50 | } 51 | return builder.append(this).toString(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /03~并发框架/Disq/codes/disq/src/main/java/net/intelie/disq/dson/StringCache.java: -------------------------------------------------------------------------------- 1 | package net.intelie.disq.dson; 2 | 3 | public class StringCache { 4 | public static final String EMPTY = ""; 5 | private final int bucketCount; 6 | private final int maxStringLength; 7 | private final String[] cache; 8 | 9 | public StringCache() { 10 | this(8192, 1024); 11 | } 12 | 13 | public StringCache(int bucketCount, int maxStringLength) { 14 | if (Integer.bitCount(bucketCount) != 1) 15 | throw new IllegalArgumentException("bucketCount must be a power of two"); 16 | this.bucketCount = bucketCount; 17 | this.maxStringLength = maxStringLength; 18 | this.cache = new String[bucketCount]; 19 | } 20 | 21 | public String get(CharSequence cs) { 22 | if (cs == null) return null; 23 | int length = cs.length(); 24 | if (length == 0) return EMPTY; 25 | if (length > maxStringLength) return cs.toString(); 26 | 27 | int hash = hash(cs, length); 28 | int n = hash & (bucketCount - 1); 29 | String cached = cache[n]; 30 | if (eq(cached, cs, hash)) 31 | return cached; 32 | return cache[n] = cs.toString(); 33 | } 34 | 35 | private int hash(CharSequence cs, int length) { 36 | int hash = 0; 37 | for (int i = 0; i < length; i++) 38 | hash = 31 * hash + cs.charAt(i); 39 | return hash; 40 | } 41 | 42 | 43 | private static boolean eq(String cached, CharSequence cs, int hash) { 44 | if (cached == null || cached.hashCode() != hash) 45 | return false; 46 | return cached.contentEquals(cs); 47 | } 48 | } -------------------------------------------------------------------------------- /03~并发框架/Quartz/codes/examples/src/main/resources/org/quartz/examples/example10/quartz.properties: -------------------------------------------------------------------------------- 1 | 2 | #============================================================================ 3 | # Configure Main Scheduler Properties 4 | #============================================================================ 5 | 6 | org.quartz.scheduler.instanceName: Example10Scheduler 7 | org.quartz.scheduler.instanceId: AUTO 8 | 9 | #============================================================================ 10 | # Configure ThreadPool 11 | #============================================================================ 12 | 13 | org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool 14 | org.quartz.threadPool.threadCount: 3 15 | org.quartz.threadPool.threadPriority: 5 16 | 17 | #============================================================================ 18 | # Configure JobStore 19 | #============================================================================ 20 | 21 | org.quartz.jobStore.misfireThreshold: 60000 22 | 23 | org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore 24 | 25 | #============================================================================ 26 | # Configure Plugins 27 | #============================================================================ 28 | 29 | org.quartz.plugin.triggHistory.class: org.quartz.plugins.history.LoggingJobHistoryPlugin 30 | 31 | org.quartz.plugin.jobInitializer.class: org.quartz.plugins.xml.XMLSchedulingDataProcessorPlugin 32 | org.quartz.plugin.jobInitializer.fileNames: org/quartz/examples/example10/quartz_data.xml 33 | org.quartz.plugin.jobInitializer.failOnFileNotFound: true 34 | org.quartz.plugin.jobInitializer.scanInterval: 120 35 | org.quartz.plugin.jobInitializer.wrapInUserTransaction: false -------------------------------------------------------------------------------- /02~线程安全/并发容器/ConcurrentHashMap/不安全的 HashMap.md: -------------------------------------------------------------------------------- 1 | # 不安全的 HashMap 2 | 3 | 在多线程使用场景中,应该尽量避免使用线程不安全的 HashMap,而使用线程安全的 ConcurrentHashMap。那么为什么说 HashMap 是 线程不安全的,下面举例子说明在并发的多线程使用场景中使用 HashMap 可能造成死循环。 4 | 5 | ```java 6 | public class HashMapInfiniteLoop { 7 | 8 | private static HashMap map = new HashMap(2,0.75f); 9 | public static void main(String[] args) { 10 | map.put(5,"C"); 11 | 12 | new Thread("Thread1") { 13 | public void run() { 14 | map.put(7, "B"); 15 | System.out.println(map); 16 | }; 17 | }.start(); 18 | 19 | new Thread("Thread2") { 20 | public void run() { 21 | map.put(3, "A); 22 | System.out.println(map); 23 | }; 24 | }.start(); 25 | } 26 | } 27 | ``` 28 | 29 | 其中,map 初始化为一个长度为 2 的数组,loadFactor=0.75,`threshold=2*0.75=1`,也就是说当 put 第二个 key 的时候,map 就需要进行 resize。通过设置断点让线程 1 和线程 2 同时 debug 到 transfer 方法的首行。注意此时两个线程已经成功添加数据。放开 thread1 的断点至 transfer 方法的“Entry next = e.next;” 这一行;然后放开线程 2 的的断点,让线程 2 进行 resize。结果如下图。 30 | 31 | ![断点调试图](https://ngte-superbed.oss-cn-beijing.aliyuncs.com/superbed/2021/07/16/60f18e4d5132923bf8417c48.jpg) 32 | 33 | 注意,Thread1 的 e 指向了 key(3),而 next 指向了 key(7),其在线程二 rehash 后,指向了线程二重组后的链表。线程一被调度回来执行,先是执行 newTalbe[i] = e,然后是 e = next,导致了 e 指向了 key(7),而下一次循环的 next = e.next 导致了 next 指向了 key(3)。 34 | 35 | ![断点调试图](https://ngte-superbed.oss-cn-beijing.aliyuncs.com/superbed/2021/07/16/60f18e855132923bf843320f.jpg) 36 | 37 | e.next = newTable[i] 导致 key(3).next 指向了 key(7)。注意:此时的 key(7).next 已经指向了 key(3),环形链表就这样出现了。 38 | 39 | ![循环示意图](https://ngte-superbed.oss-cn-beijing.aliyuncs.com/superbed/2021/07/16/60f18ea95132923bf8445220.jpg) 40 | 41 | 于是,当我们用线程一调用 map.get(11)时,悲剧就出现了:Infinite Loop。 42 | -------------------------------------------------------------------------------- /03~并发框架/Disq/codes/disq/src/test/java/net/intelie/disq/dson/DsonToBsonAllocationsTest.java: -------------------------------------------------------------------------------- 1 | package net.intelie.disq.dson; 2 | 3 | import net.intelie.disq.Buffer; 4 | import net.intelie.disq.SuppressForbidden; 5 | import net.intelie.introspective.ThreadResources; 6 | import org.junit.Test; 7 | 8 | import java.util.Arrays; 9 | import java.util.LinkedHashMap; 10 | import java.util.Map; 11 | 12 | import static org.assertj.core.api.Assertions.assertThat; 13 | 14 | public class DsonToBsonAllocationsTest { 15 | 16 | @Test 17 | public void testSimple() { 18 | int warmup = 10000, realTest = 10000; 19 | 20 | Map map = new LinkedHashMap<>(); 21 | map.put("aaa", 123.0); 22 | map.put("bbb", true); 23 | map.put(123.0, Arrays.asList(123, "(╯°□°)╯︵ ┻━┻\uD800\uDF48")); 24 | 25 | Buffer in = new Buffer(); 26 | Buffer out = new Buffer(); 27 | 28 | DsonSerializer.Instance dson = new DsonSerializer().create(); 29 | 30 | DsonToBsonConverter converter = new DsonToBsonConverter(); 31 | 32 | for (int i = 0; i < warmup; i++) { 33 | dson.serialize(in, map); 34 | converter.convert(in.read(), out.write()); 35 | } 36 | 37 | long start = ThreadResources.allocatedBytes(Thread.currentThread()); 38 | for (int i = 0; i < realTest; i++) { 39 | dson.serialize(in, map); 40 | converter.convert(in.read(), out.write()); 41 | } 42 | long result = ThreadResources.allocatedBytes(Thread.currentThread()) - start; 43 | 44 | assertThat(result / (double) realTest).isLessThan(1); 45 | 46 | printStats(result); 47 | } 48 | 49 | @SuppressForbidden 50 | private static void printStats(long result) { 51 | System.out.println("ALLOCATIONS: " + result); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /03~并发框架/Disq/codes/disq/src/test/java/net/intelie/disq/FstSerializer.java: -------------------------------------------------------------------------------- 1 | package net.intelie.disq; 2 | 3 | import org.nustaq.serialization.FSTConfiguration; 4 | import org.nustaq.serialization.FSTObjectInput; 5 | import org.nustaq.serialization.FSTObjectOutput; 6 | 7 | import java.io.IOException; 8 | 9 | public class FstSerializer implements SerializerFactory { 10 | @Override 11 | public Serializer create() { 12 | return new Serializer() { 13 | private final FSTConfiguration conf = newFSTConfiguration(); 14 | private final FSTObjectOutput output = new FSTObjectOutput(conf); 15 | private final FSTObjectInput input = new FSTObjectInput(conf); 16 | 17 | @Override 18 | public void serialize(Buffer buffer, Object obj) throws IOException { 19 | try (Buffer.OutStream stream = buffer.write()) { 20 | output.resetForReUse(stream); 21 | 22 | output.writeObject(obj); 23 | output.flush(); 24 | 25 | output.resetForReUse(); 26 | } 27 | } 28 | 29 | @Override 30 | public Object deserialize(Buffer buffer) throws IOException { 31 | input.resetForReuseUseArray(buffer.buf(), buffer.count()); 32 | try { 33 | return input.readObject(); 34 | } catch (ClassNotFoundException e) { 35 | throw new IOException(e); 36 | } 37 | 38 | } 39 | }; 40 | } 41 | 42 | private static FSTConfiguration newFSTConfiguration() { 43 | FSTConfiguration fstConfiguration = FSTConfiguration.createDefaultConfiguration(); 44 | fstConfiguration.setShareReferences(false); 45 | return fstConfiguration; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /03~并发框架/Quartz/codes/examples/src/main/resources/org/quartz/examples/example13/instance1.properties: -------------------------------------------------------------------------------- 1 | 2 | #============================================================================ 3 | # Configure Main Scheduler Properties 4 | #============================================================================ 5 | 6 | org.quartz.scheduler.instanceName: TestScheduler 7 | org.quartz.scheduler.instanceId: instance_one 8 | 9 | #============================================================================ 10 | # Configure ThreadPool 11 | #============================================================================ 12 | 13 | org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool 14 | org.quartz.threadPool.threadCount: 5 15 | org.quartz.threadPool.threadPriority: 5 16 | 17 | #============================================================================ 18 | # Configure JobStore 19 | #============================================================================ 20 | 21 | org.quartz.jobStore.misfireThreshold: 60000 22 | 23 | org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX 24 | org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.PostgreSQLDelegate 25 | org.quartz.jobStore.useProperties=false 26 | org.quartz.jobStore.dataSource=myDS 27 | org.quartz.jobStore.tablePrefix=QRTZ_ 28 | org.quartz.jobStore.isClustered=true 29 | 30 | #============================================================================ 31 | # Configure Datasources 32 | #============================================================================ 33 | 34 | org.quartz.dataSource.myDS.driver: org.postgresql.Driver 35 | org.quartz.dataSource.myDS.URL: jdbc:postgresql://localhost:5432/quartz 36 | org.quartz.dataSource.myDS.user: quartz 37 | org.quartz.dataSource.myDS.password: quartz 38 | org.quartz.dataSource.myDS.maxConnections: 5 39 | org.quartz.dataSource.myDS.validationQuery: select 0 40 | -------------------------------------------------------------------------------- /03~并发框架/Quartz/codes/examples/src/main/resources/org/quartz/examples/example13/instance2.properties: -------------------------------------------------------------------------------- 1 | 2 | #============================================================================ 3 | # Configure Main Scheduler Properties 4 | #============================================================================ 5 | 6 | org.quartz.scheduler.instanceName: TestScheduler 7 | org.quartz.scheduler.instanceId: instance_two 8 | 9 | #============================================================================ 10 | # Configure ThreadPool 11 | #============================================================================ 12 | 13 | org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool 14 | org.quartz.threadPool.threadCount: 5 15 | org.quartz.threadPool.threadPriority: 5 16 | 17 | #============================================================================ 18 | # Configure JobStore 19 | #============================================================================ 20 | 21 | org.quartz.jobStore.misfireThreshold: 60000 22 | 23 | org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX 24 | org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.PostgreSQLDelegate 25 | org.quartz.jobStore.useProperties=false 26 | org.quartz.jobStore.dataSource=myDS 27 | org.quartz.jobStore.tablePrefix=QRTZ_ 28 | org.quartz.jobStore.isClustered=true 29 | 30 | #============================================================================ 31 | # Configure Datasources 32 | #============================================================================ 33 | 34 | org.quartz.dataSource.myDS.driver: org.postgresql.Driver 35 | org.quartz.dataSource.myDS.URL: jdbc:postgresql://localhost:5432/quartz 36 | org.quartz.dataSource.myDS.user: quartz 37 | org.quartz.dataSource.myDS.password: quartz 38 | org.quartz.dataSource.myDS.maxConnections: 5 39 | org.quartz.dataSource.myDS.validationQuery: select 0 40 | 41 | -------------------------------------------------------------------------------- /01~线程与线程池/异步编程/Future/Callable 与 Future.md: -------------------------------------------------------------------------------- 1 | # Callable & Future 2 | 3 | Executors 本身提供了一种对于多线程的封装,而 Executor 还支持另一种类型的任务:Callable。Callables 也是类似于 Runnable 的函数接口,不同之处在于,Callable 返回一个值 Future,用来描述一个异步计算的结果。Callable 接口本身是一个 Lambda 表达式(函数式接口): 4 | 5 | ```java 6 | Callable task = () -> { 7 | try { 8 | TimeUnit.SECONDS.sleep(1); 9 | return 123; 10 | } 11 | catch (InterruptedException e) 12 | throw new IllegalStateException("task interrupted", e); 13 | } 14 | }; 15 | ``` 16 | 17 | Callbale 也可以像 Runnable 一样提交给 executor services。但是 submit()不会等待任务完成,executor service 不能直接返回 callable 的结果。不过,executor 可以返回一个 Future 类型的结果,它可以用来在稍后某个时间取出实际的结果。 18 | 19 | ```java 20 | ExecutorService executor = Executors.newFixedThreadPool(1); 21 | Future future = executor.submit(task); 22 | 23 | System.out.println("future done? " + future.isDone()); 24 | 25 | Integer result = future.get(); 26 | 27 | System.out.println("future done? " + future.isDone()); 28 | System.out.print("result: " + result); 29 | ``` 30 | 31 | Future 与底层的 ExecutorService 紧密的结合在一起。记住,如果你关闭 executor,所有的未中止的 future 都会抛出异常。 32 | 33 | ```java 34 | executor.shutdownNow(); 35 | future.get(); 36 | ``` 37 | 38 | 任何 future.get()调用都会阻塞,然后等待直到 callable 中止。在最糟糕的情况下,一个 callable 持续运行——因此使你的程序将没有响应。我们可以简单的传入一个时长来避免这种情况。 39 | 40 | ```java 41 | ExecutorService executor = Executors.newFixedThreadPool(1); 42 | 43 | Future future = executor.submit(() -> { 44 | try { 45 | TimeUnit.SECONDS.sleep(2); 46 | return 123; 47 | } 48 | catch (InterruptedException e) { 49 | throw new IllegalStateException("task interrupted", e); 50 | } 51 | }); 52 | 53 | future.get(1, TimeUnit.SECONDS); 54 | ``` 55 | 56 | 运行上面的代码将会产生一个 TimeoutException: 57 | 58 | ``` 59 | Exception in thread "main" java.util.concurrent.TimeoutException 60 | at java.util.concurrent.FutureTask.get(FutureTask.java:205) 61 | ``` 62 | -------------------------------------------------------------------------------- /03~并发框架/Disq/codes/disq/src/main/java/net/intelie/disq/Lenient.java: -------------------------------------------------------------------------------- 1 | package net.intelie.disq; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.io.IOException; 7 | import java.nio.file.Files; 8 | import java.nio.file.Path; 9 | 10 | public abstract class Lenient { 11 | private static final Logger LOGGER = LoggerFactory.getLogger(Lenient.class); 12 | 13 | public static long perform(RawQueue queue, Buffer buffer, Op supplier) throws IOException { 14 | try { 15 | return supplier.call(buffer); 16 | } catch (Throwable e) { 17 | LOGGER.info("First try queue operation error", e); 18 | queue.reopen(); 19 | try { 20 | return supplier.call(buffer); 21 | } catch (Throwable e2) { 22 | LOGGER.info("Second try queue operation error", e2); 23 | queue.reopen(); 24 | throw e2; 25 | } 26 | } 27 | } 28 | 29 | public static long performSafe(RawQueue queue, Buffer buffer, Op supplier, long defaultValue) { 30 | try { 31 | return perform(queue, buffer, supplier); 32 | } catch (Throwable e) { 33 | return defaultValue; 34 | } 35 | } 36 | 37 | 38 | public static void safeClose(AutoCloseable closeable) { 39 | try { 40 | if (closeable != null) 41 | closeable.close(); 42 | } catch (Throwable e) { 43 | LOGGER.info("Error closing closeable", e); 44 | } 45 | } 46 | 47 | public static void safeDelete(Path directory) { 48 | try { 49 | Files.walkFileTree(directory, new DeleteFileVisitor()); 50 | } catch (Throwable e) { 51 | LOGGER.info("Error deleting directory", e); 52 | } 53 | } 54 | 55 | public interface Op { 56 | long call(Buffer buffer) throws IOException; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /03~并发框架/Disq/codes/disq/src/main/java/net/intelie/disq/DataFileReader.java: -------------------------------------------------------------------------------- 1 | package net.intelie.disq; 2 | 3 | import java.io.*; 4 | import java.nio.file.Path; 5 | 6 | public class DataFileReader implements Closeable { 7 | private final DataInputStream stream; 8 | private final FileInputStream fis; 9 | 10 | public DataFileReader(Path file, long position) throws IOException { 11 | fis = new FileInputStream(file.toFile()); 12 | skipToPosition(position); 13 | stream = new DataInputStream(new BufferedInputStream(fis, 1024 * 1024)); 14 | } 15 | 16 | private void skipToPosition(long position) throws IOException { 17 | while (position > 0) 18 | position -= fis.skip(position); 19 | } 20 | 21 | public long size() throws IOException { 22 | return fis.getChannel().size(); 23 | } 24 | 25 | public int read(Buffer buffer) throws IOException { 26 | return internalRead(buffer, false); 27 | } 28 | 29 | private int internalRead(Buffer buffer, boolean peek) throws IOException { 30 | if (peek) stream.mark(4); 31 | int size = stream.readInt(); 32 | if (peek) stream.reset(); 33 | 34 | stream.mark(4 + size); 35 | int total = DataFileWriter.OVERHEAD; 36 | 37 | buffer.setCount(size, false); 38 | 39 | int offset = 0; 40 | 41 | if (peek) stream.readInt(); 42 | while (size > 0) { 43 | int read = stream.read(buffer.buf(), offset, size); 44 | size -= read; 45 | offset += read; 46 | total += read; 47 | } 48 | 49 | if (peek) 50 | stream.reset(); 51 | 52 | return total; 53 | } 54 | 55 | public int peek(Buffer buffer) throws IOException { 56 | return internalRead(buffer, true); 57 | } 58 | 59 | @Override 60 | public void close() throws IOException { 61 | stream.close(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /99~参考资料/极客时间~《Java 并发编程实战》/README.md: -------------------------------------------------------------------------------- 1 | > DocId: LSbY4Fi8sLE 2 | 3 | # Java 并发编程实战 4 | 5 | 并发编程可以总结为三个核⼼问题:分⼯、同步、互斥。所谓分⼯指的是如何⾼效地拆解任务并分配给线程,⽽同步指的是线程之间如何协作,互斥则是保证同⼀时刻只允许⼀个线程访问共享资源。Java SDK 并发包很⼤部分内容都是按照这三个维度组织的,例如 Fork/Join 框架就是⼀种分⼯模式,CountDownLatch 就是⼀种典型的同步⽅式,⽽可重⼊锁则是⼀种互斥⼿段。 6 | 7 | ![](https://ngte-superbed.oss-cn-beijing.aliyuncs.com/uPic/VhIeYqdDiu86.png) 8 | 9 | ## 分工 10 | 11 | 所谓分⼯,类似于现实中⼀个组织完成⼀个项⽬,项⽬经理要拆分任务,安排合适的成员去完成。在并发编程领域这⽅⾯的成果还是很丰硕的。Java SDK 并发包⾥的 Executor、Fork/Join、Future 本质上都是⼀种分⼯⽅法。除此之外,并发编程领域还总结了⼀些设计模式,基本上都是和分⼯⽅法相关的,例如⽣产者 - 消费者、Thread-Per-Message、Worker Thread 模式等都是⽤来指导你如何分⼯的。 12 | 13 | 学习这部分内容,最佳的⽅式就是和现实世界做对⽐。例如⽣产者 - 消费者模式,可以类⽐⼀下餐馆⾥的⼤厨和服务员,⼤厨就是⽣产者,负责做菜,做完放到出菜⼝,⽽服务员就是消费者,把做好的菜给你端过来。不过,我们经常会发现,出菜⼝有时候⼀下⼦出了好⼏个菜,服务员是可以把这⼀批菜同时端给你的。其实这就是⽣产者 - 消费者模式的⼀个优点,⽣产者⼀个⼀个地⽣产数据,⽽消费者可以批处理,这样就提⾼了性能。 14 | 15 | ## 同步 16 | 17 | 在并发编程领域⾥的同步,主要指的就是线程间的协作,本质上和现实⽣活中的协作没区别,不过是⼀个线程执⾏完了⼀个任务,如何通知执⾏后续任务的线程开⼯⽽已。协作⼀般是和分⼯相关的。Java SDK 并发包⾥的 Executor、Fork/Join、Future 本质上都是分⼯⽅法,但同时也能解决线程协作的问题。例如,⽤ Future 可以发起⼀个异步调⽤,当主线程通过 get() ⽅法取结果时,主线程就会等待,当异步执⾏的结果返回时,get() ⽅法就⾃动返回了。主线程和异步线程之间的协作,Future ⼯具类已经帮我们解决了。除此之外,Java SDK ⾥提供的 CountDownLatch、CyclicBarrier、Phaser、Exchanger 也都是⽤来解决线程协作问题的。 18 | 19 | 工作中遇到的线程协作问题,基本上都可以描述为这样的一个问题:当某个条件不满足时,线程需要等待,当某个条件满足时,线程需要被唤醒执行。例如,在生产者 - 消费者模型里,也有类似的描述,“当队列满时,生产者线程等待,当队列不满时,生产者线程需要被唤醒执行;当队列空时,消费者线程等待,当队列不空时,消费者线程需要被唤醒执行。“在 Java 并发编程领域,解决协作问题的核心技术是管程,上面提到的所有线程协作技术底层都是利用管程解决的。管程是一种解决并发问题的通用模型,除了能解决线程协作问题,还能解决下面我们将要介绍的互斥问题。可以这么说,管程是解决并发问题的万能钥匙。” 20 | 21 | ## 互斥 22 | 23 | 分工、同步主要强调的是性能,但并发程序里还有一部分是关于正确性的,用专业术语叫“线程安全”。并发程序里,当多个线程同时访问同一个共享变量的时候,结果是不确定的。不确定,则意味着可能正确,也可能错误,事先是不知道的。而导致不确定的主要源头是可见性问题、有序性问题和原子性问题,为了解决这三个问题,Java 语言引入了内存模型,内存模型提供了一系列的规则,利用这些规则,我们可以避免可见性问题、有序性问题,但是还不足以完全解决线程安全问题。解决线程安全问题的核心方案还是互斥。 24 | 25 | 所谓互斥,指的是同一时刻,只允许一个线程访问共享变量。实现互斥的核心技术就是锁,Java 语言里 synchronized、SDK 里的各种 Lock 都能解决互斥问题。虽说锁解决了安全性问题,但同时也带来了性能问题。Java SDK 里提供的 ReadWriteLock、StampedLock 就可以优化读多写少场景下锁的性能。还可以使用无锁的数据结构,例如 Java SDK 里提供的原子类都是基于无锁技术实现的。除此之外,还有一些其他的方案,原理是不共享变量或者变量只允许读。这方面,Java 提供了 Thread Local 和 final 关键字,还有一种 Copy-on-write 的模式。使用锁除了要注意性能问题外,还需要注意死锁问题。 26 | -------------------------------------------------------------------------------- /03~并发框架/Akka/README.md: -------------------------------------------------------------------------------- 1 | # Akka 2 | 3 | In today's world, computer hardware is becoming cheaper and more powerful, as we have multiple cores on a single CPU chip. As cores keep on increasing, with the increasing power of hardware, we need a state of the art software framework which can use these cores efficiently. 4 | 5 | Akka is such a framework, or you can say, a toolkit, which utilizes the hardware cores efficiently and lets you write performant applications. 6 | 7 | Thus, Akka provides a basic unit of abstraction of transparent distribution called actors, which form the basis for writing resilient, elastic, event-driven, and responsive systems. 8 | 9 | Let's see what is meant by these properties: 10 | 11 | Resilient: Applications that can heal themselves, which means they can recover from failure, and will always be responsive, even in case of failure like if we get errors or exceptions 12 | 13 | Elastic: A system which is responsive under varying amount of workload, that is, the system always remains responsive, irrespective of increasing or decreasing traffic, by increasing or decreasing the resources allocated to service this workload 14 | 15 | Message Driven: A system whose components are loosely coupled with each other and communicate using asynchronous message passing, and which reacts to those messages by taking an action 16 | 17 | Responsive: A system that satisfies the preceding three properties is called responsive 18 | 19 | Akka 是一个建立在 Actors 概念和可组合 Futures 之上的并发框架, Akka 设计灵感来源于 Erlang;Erlang 是基于 Actor 模型构建的。它通常被用来取代阻塞锁如同步、读写锁及类似的更高级别的异步抽象。Netty 是一个异步网络库,使 Java NIO 的功能更好用。Akka 针对 IO 操作有一个抽象,这和 netty 是一样的。使用 Akka 可以用来创建计算集群,Actor 在不同的机器之间传递消息。从这个角度来看,Akka 相对于 Netty 来说,是一个更高层次的抽象 20 | 21 | Akka 是一种高度可扩展的软件,这不仅仅表现在性能方面,也表现在它所适用的应用的大小。Akka 的核心,Akka-actor 是非常小的,可以非常方便地放进你的应用中,提供你需要的异步无锁并行功能,不会有任何困扰。你可以任意选择 Akka 的某些部分集成到你的应用中,也可以使用完整的包——Akka 微内核,它是一个独立的容器,可以直接部署你的 Akka 应用。随着 CPU 核数越来越多,即使你只使用一台电脑,Akka 也可作为一种提供卓越性能的选择。Akka 还同时提供多种并发范型,允许用户选择正确的工具来完成工作。 22 | 23 | # Links 24 | 25 | - https://doc.akka.io/docs/akka/current/actors.html#become-unbecome 26 | -------------------------------------------------------------------------------- /04~并发网络 IO/Netty/HTTP.md: -------------------------------------------------------------------------------- 1 | # Web 框架 2 | 3 | First you need to handle HTTP data with a Netty ChannelPipeline using a pipeline factory: 4 | 5 | ```java 6 | public class DefaultNettyServletPipelineFactory implements ChannelPipelineFactory { 7 | ... 8 | public ChannelPipeline getPipeline() throws Exception { 9 | ChannelPipeline pipeline = pipeline(); 10 | 11 | pipeline.addLast("decoder", new HttpRequestDecoder()); 12 | pipeline.addLast("encoder", new HttpResponseEncoder()); 13 | pipeline.addLast("deflater", new HttpContentCompressor()); 14 | pipeline.addLast("handler", servletHandler); // will convert http request to servlet request 15 | return pipeline; 16 | } 17 | ``` 18 | 19 | Then you will need a NettyServletHandler that converts Netty HttpRequest to a Servlet request: 20 | 21 | ```java 22 | public class NettyServletHandler extends SimpleChannelUpstreamHandler { 23 | ... 24 | @Override 25 | public void messageReceived(ChannelHandlerContext context, MessageEvent event) throws Exception { 26 | HttpRequest request = (HttpRequest) event.getMessage(); 27 | // Then get URL, method, headers, ... and pass the values to the Servlet container. 28 | } 29 | ``` 30 | 31 | You will also need a method to start the server: 32 | 33 | ```java 34 | public void startServer(int port, String pipelineFactory) throws Exception { 35 | ServerBootstrap server = new ServerBootstrap( 36 | new NioServerSocketChannelFactory(Executors.newCachedThreadPool(), Executors.newCachedThreadPool())); 37 | 38 | if (pipelineFactory == null) { // If the user doesn't have a specific pipeline use the default one 39 | pipelineFactory = "org.xins.common.servlet.container.DefaultNettyServletPipelineFactory"; 40 | } 41 | DefaultNettyServletPipelineFactory pipelineFactoryClass = (DefaultNettyServletPipelineFactory) Class.forName(pipelineFactory).newInstance(); 42 | pipelineFactoryClass.setServletHandler(this); 43 | 44 | server.setPipelineFactory(pipelineFactoryClass); 45 | 46 | server.bind(new InetSocketAddress(port)); 47 | } 48 | ``` 49 | -------------------------------------------------------------------------------- /01~线程与线程池/01~线程基础/线程概念/守护线程.md: -------------------------------------------------------------------------------- 1 | # 守护线程 2 | 3 | 通常情况下,线程运行到最后一条指令后则完成生命周期,结束线程,然后系统回收资源。或者单遇到异常或者 return 提前返回,但是如果我们想让线程常驻内存的话,比如一些监控类线程,需要 24 小时值班的,于是我们又创造了守护线程的概念。 4 | 5 | setDaemon()传入 true 则会把线程一直保持在内存里面,除非 JVM 宕机否则不会退出。 6 | 7 | ## 守护线程和用户线程的区别 8 | 9 | Java 提供了两种类型的线程:守护线程 和 用户线程 10 | 11 | - 守护线程 是高优先级线程。JVM 会在终止之前等待任何用户线程完成其任务。 12 | - 用户线程 是低优先级线程。其唯一作用是为用户线程提供服务。 13 | 14 | 由于守护线程的作用是为用户线程提供服务,并且仅在用户线程运行时才需要,因此一旦所有用户线程完成执行,JVM 就会终止。也就是说 守护线程不会阻止 JVM 退出。这也是为什么通常存在于守护线程中的无限循环不会导致问题,因为任何代码(包括 finally 块 )都不会在所有用户线程完成执行后执行。 15 | 16 | 这也是为什么我们并不推荐 在守护线程中执行 I/O 任务 。因为可能导致无法正确关闭资源。但是,守护线程并不是 100% 不能阻止 JVM 退出的。守护线程中设计不良的代码可能会阻止 JVM 退出。例如,在正在运行的守护线程上调用 Thread.join() 可以阻止应用程序的关闭。 17 | 18 | # 如何创建守护线程 19 | 20 | 守护线程也是一个线程,因此它的创建和启动其实和普通线程没什么区别?要将普通线程设置为守护线程,方法很简单,只需要调用 Thread.setDaemon() 方法即可。例如下面这段代码,假设我们继承 Thread 类创建了一个新类 NewThread 。那么我们就可以创建这个类的实例并设置为守护线程 21 | 22 | ```java 23 | NewThread daemonThread = new NewThread(); 24 | daemonThread.setDaemon(true); 25 | daemonThread.start(); 26 | ``` 27 | 28 | 在 Java 语言中,线程的状态是自动继承的。任何线程都会继承创建它的线程的守护程序状态。怎么理解呢? 29 | 30 | 1. 如果一个线程是普通线程(用户线程) ,那么它创建的子线程默认也是普通线程(用户线程 )。 31 | 2. 如果一个线程是守护线程,那么它创建的子线程默认也是守护线程。 32 | 33 | 因此,我们可以推演出: 由于主线程是用户线程,因此在 `main()` 方法内创建的任何线程默认为用户线程。 34 | 35 | 需要注意的是调用 `setDaemon()` 方法的时机,该方法只能在创建 Thread 对象并且在启动线程前调用。在线程运行时尝试调用 `setDaemon()` 将抛出 IllegalThreadStateException 异常。 36 | 37 | ``` 38 | @Test(expected = IllegalThreadStateException.class) 39 | public void whenSetDaemonWhileRunning_thenIllegalThreadStateException() { 40 | NewThread daemonThread = new NewThread(); 41 | daemonThread.start(); 42 | daemonThread.setDaemon(true); 43 | } 44 | ``` 45 | 46 | # 如何检查一个线程是守护线程还是用户线程? 47 | 48 | 检查一个线程是否是守护线程,可以简单地调用方法 isDaemon() ,如下代码所示: 49 | 50 | ```java 51 | @Test 52 | public void whenCallIsDaemon_thenCorrect() { 53 | NewThread daemonThread = new NewThread(); 54 | NewThread userThread = new NewThread(); 55 | daemonThread.setDaemon(true); 56 | daemonThread.start(); 57 | userThread.start(); 58 | 59 | assertTrue(daemonThread.isDaemon()); 60 | assertFalse(userThread.isDaemon()); 61 | } 62 | ``` 63 | -------------------------------------------------------------------------------- /02~线程安全/线程通信/线程协作工具/CountDownLatch.md: -------------------------------------------------------------------------------- 1 | # CountDownLatch 2 | 3 | 一个线程(或者多个),等待另外 N 个线程完成某个事情之后才能执行。在一些应用场合中,需要等待某个条件达到要求后才能做后面的事情;同时当线程都完成后也会触发事件,以便进行后面的操作。这个时候就可以使用 CountDownLatch。CountDownLatch 是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。用给定的计数初始化 CountDownLatch。CountDownLatch 最重要的方法是 countDown()和 await(),前者主要是倒数一次,后者是等待倒数到 0,如果没有到达 0,就只有阻塞等待了。 4 | 5 | ```java 6 | public void countDown() 7 | ``` 8 | 9 | 递减锁存器的计数,如果计数到达零,则释放所有等待的线程。如果当前计数大于零,则将计数减少。如果新的计数为零,出于线程调度目的,将重新启用所有的等待线程。如果当前计数等于零,则不发生任何操作。 10 | 11 | ```java 12 | public boolean await(long timeout, 13 | TimeUnit unit) 14 | throws InterruptedException 15 | ``` 16 | 17 | 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。如果当前计数为零,则此方法立刻返回 true 值。如果当前计数大于零,则出于线程调度目的,将禁用当前线程,且在发生以下三种情况之一前,该线程将一直处于休眠状态: 18 | 19 | - 由于调用 countDown() 方法,计数到达零; 20 | - 或者其他某个线程中断当前线程; 21 | - 或者已超出指定的等待时间。 22 | 23 | 如果计数到达零,则该方法返回 true 值。如果当前线程在进入此方法时已经设置了该线程的中断状态;或者在等待时被中断,则抛出 InterruptedException,并且清除当前线程的已中断状态。如果超出了指定的等待时间,则返回值为 false。如果该时间小于等于零,则此方法根本不会等待。 24 | 25 | # 案例:赛跑 26 | 27 | ```java 28 | // 开始的倒数锁 29 | final CountDownLatch begin = new CountDownLatch(1); 30 | 31 | // 结束的倒数锁 32 | final CountDownLatch end = new CountDownLatch(10); 33 | 34 | // 十名选手 35 | final ExecutorService exec = Executors.newFixedThreadPool(10); 36 | 37 | for (int index = 0; index < 10; index++) { 38 | final int NO = index + 1; 39 | Runnable run = new Runnable() { 40 | public void run() { 41 | try { 42 | // 如果当前计数为零,则此方法立即返回。 43 | // 等待 44 | begin.await(); 45 | Thread.sleep((long) (Math.random() * 10000)); 46 | System.out.println("No." + NO + " arrived"); 47 | } catch (InterruptedException e) { 48 | } finally { 49 | // 每个选手到达终点时,end 就减一 50 | end.countDown(); 51 | } 52 | } 53 | }; 54 | exec.submit(run); 55 | } 56 | 57 | // begin 减一,开始游戏 58 | begin.countDown(); 59 | // 等待 end 变为 0,即所有选手到达终点 60 | end.await(); 61 | System.out.println("Game Over"); 62 | exec.shutdown(); 63 | ``` 64 | -------------------------------------------------------------------------------- /03~并发框架/Disq/codes/disq/src/main/java/net/intelie/disq/DiskQueueReader.java: -------------------------------------------------------------------------------- 1 | package net.intelie.disq; 2 | 3 | import java.io.Closeable; 4 | import java.io.IOException; 5 | import java.nio.file.Files; 6 | import java.nio.file.Path; 7 | import java.util.Locale; 8 | 9 | public class DiskQueueReader implements Closeable { 10 | private final StateFile state; 11 | private final Path directory; 12 | private DataFileReader reader; 13 | 14 | public DiskQueueReader(Path directory) throws IOException { 15 | this.directory = directory; 16 | this.state = new StateFile(this.directory.resolve("state"), true); 17 | this.reader = openReader(); 18 | } 19 | 20 | private DataFileReader openReader() throws IOException { 21 | Path file = makeDataPath(state.getReadFile()); 22 | if (!Files.exists(file)) return null; 23 | return new DataFileReader(file, state.getReadPosition()); 24 | } 25 | 26 | public long bytes() { 27 | return state.getBytes(); 28 | } 29 | 30 | public long count() { 31 | return state.getCount(); 32 | } 33 | 34 | public boolean moveNext(Buffer buffer) throws IOException { 35 | if (checkReadEOF()) 36 | return false; 37 | int read = reader.read(buffer); 38 | state.addReadCount(read); 39 | return true; 40 | } 41 | 42 | private Path makeDataPath(int state) { 43 | return directory.resolve(String.format((Locale) null, "data%02x", state)); 44 | } 45 | 46 | private boolean checkReadEOF() throws IOException { 47 | while (state.readFileEof()) 48 | if (!maybeAdvanceFile()) 49 | break; 50 | return state.getCount() == 0; 51 | } 52 | 53 | private boolean maybeAdvanceFile() throws IOException { 54 | state.advanceReadFile(reader.size()); 55 | reader.close(); 56 | reader = openReader(); 57 | return reader != null; 58 | } 59 | 60 | @Override 61 | public void close() throws IOException { 62 | if (reader != null) 63 | reader.close(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /03~并发框架/Quartz/codes/examples/src/main/java/org/quartz/examples/example14/TriggerEchoJob.java: -------------------------------------------------------------------------------- 1 | /* 2 | * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | * 16 | */ 17 | package org.quartz.examples.example14; 18 | 19 | import org.slf4j.Logger; 20 | import org.slf4j.LoggerFactory; 21 | import org.quartz.Job; 22 | import org.quartz.JobExecutionContext; 23 | import org.quartz.JobExecutionException; 24 | 25 | /** 26 | * This is just a simple job that echos the name of the Trigger 27 | * that fired it. 28 | */ 29 | public class TriggerEchoJob implements Job { 30 | 31 | private static final Logger LOG = LoggerFactory.getLogger(TriggerEchoJob.class); 32 | 33 | /** 34 | * Empty constructor for job initilization 35 | * 36 | *

37 | * Quartz requires a public empty constructor so that the 38 | * scheduler can instantiate the class whenever it needs. 39 | *

40 | */ 41 | public TriggerEchoJob() { 42 | } 43 | 44 | /** 45 | *

46 | * Called by the {@link org.quartz.Scheduler} when a 47 | * {@link org.quartz.Trigger} fires that is associated with 48 | * the Job. 49 | *

50 | * 51 | * @throws JobExecutionException 52 | * if there is an exception while executing the job. 53 | */ 54 | public void execute(JobExecutionContext context) 55 | throws JobExecutionException { 56 | LOG.info("TRIGGER: " + context.getTrigger().getKey()); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /03~并发框架/Quartz/codes/examples/examples_guide.txt: -------------------------------------------------------------------------------- 1 | Welcome 2 | ======= 3 | 4 | Welcome to the Quartz Examples directory. 5 | 6 | This directory contains 15 examples that show you how to use various 7 | features of Quartz. Each example is located in its own subdirectory. 8 | Every example can be run using Windows .bat files or Linux/UNIX .sh files. 9 | 10 | Additionally, each example directory contains a readme.txt file. Please 11 | read this file first, as it will contain useful information for running 12 | the examples. 13 | 14 | 15 | Examples Listing 16 | ================ 17 | 18 | example1 - Your first Quartz Program 19 | example2 - Using Simple Triggers 20 | example3 - Using Cron Triggers 21 | example4 - Job State and Job Parameters 22 | example5 - Job Misfires 23 | example6 - Handling Job Exceptions 24 | example7 - Interrupting Jobs 25 | example8 - How to use Quartz Calendars 26 | example9 - Using Job Listeners 27 | example10 - Using Quartz Plug-Ins 28 | example11 - Loading Up Quartz with Many Jobs 29 | example12 - Remoting with Quartz using RMI 30 | example13 - Clustering Quartz and JDBC Job Stores 31 | example14 - Quartz Trigger Priorities 32 | example15 - Running a native (shell) command from a Job 33 | 34 | 35 | Further description of each example and possible configuration changes to them 36 | is documented on the example class javadoc themselves, e.g. within 37 | examples/src/main/java/org/quartz/examples/example11/LoadExample.java 38 | 39 | Running the Examples 40 | ==================== 41 | 42 | The examples can be run from the examples folder of the repository, such as this: 43 | 44 | ../gradlew :examples:runExample1 45 | 46 | or 47 | 48 | ../gradlew :examples:runExample4 49 | 50 | or etc. 51 | 52 | 53 | Note that examples 12 and 13 require running two process (this will need to be 54 | done in separate terminals, or on separate machines): 55 | 56 | For Example 12 you need to first run the "server" portion, and then run the 57 | "client" portion of the example: 58 | 59 | ../gradlew :examples:runExample12Server 60 | ../gradlew :examples:runExample12Client 61 | 62 | For Example 13 you need to run two instances (two cluster members): 63 | 64 | ../gradlew :examples:runExample13Instance1 65 | ../gradlew :examples:runExample13Instance2 66 | 67 | -------------------------------------------------------------------------------- /02~线程安全/锁/synchronized/锁的升级.md: -------------------------------------------------------------------------------- 1 | # 锁的升级 2 | 3 | Java SE 1.6 为了减少获得锁和释放锁带来的性能消耗,引入了偏向锁和轻量级锁,在 Java SE 1.6 中,锁一共有 4 种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率。 4 | 5 | - 偏向锁只会在第一次请求时采用 CAS 操作,在锁对象的标记字段中记录下当前线程的地址。在之后的运行过程中,持有该偏向锁的线程的加锁操作将直接返回。它针对的是锁仅会被同一线程持有的情况。 6 | 7 | - 轻量级锁采用 CAS 操作,将锁对象的标记字段替换为一个指针,指向当前线程栈上的一块空间,存储着锁对象原本的标记字段。它针对的是多个线程在不同时间段申请同一把锁的情况。 8 | 9 | - 重量级锁会阻塞、唤醒请求加锁的线程。它针对的是多个线程同时竞争同一把锁的情况。Java 虚拟机采取了自适应自旋,来避免线程在面对非常小的 synchronized 代码块时,仍会被阻塞、唤醒的情况。 10 | 11 | 不同级别锁的优缺点对比如下: 12 | 13 | | | 优点 | 缺点 | 适用场景 | 14 | | -------- | ---------------------------------------------------------------- | ---------------------------------------------- | -------------------------------- | 15 | | 偏向锁 | 加锁和解锁不需要额外的消耗,和执行非同步方法比仅存在纳秒级的差距 | 如果线程间存在锁竞争,会带来额外的锁撤销的消耗 | 适用于只有一个线程访问同步块场景 | 16 | | 轻量级锁 | 竞争的线程不会阻塞,提高了程序的响应速度 | 如果始终得不到锁竞争的线程使用自旋会消耗 CPU | 追求响应时间,锁占用时间很短 | 17 | | 重量级锁 | 线程竞争不使用自旋,不会消耗 CPU | 线程阻塞,响应时间缓慢 | 追求吞吐量,锁占用时间较长 | 18 | 19 | # 偏向锁 20 | 21 | 多数情况下,锁不会存在竞争,而是同一个线程多次获得。当某个线程访问同步块代码时,会将锁对象和栈帧中的锁记里存储锁偏向的线程 ID,以后线程在进入和退出同步块时不需要进行 CAS 操作来加锁和解锁,只需简单比对一下对象头中的 MarkWord 里的线程 ID,如果一致则表示线程获得锁。若不一致,再继续测试偏向锁的标识是否为 1:如果没有设置(无锁状态),用 CAS(Compare and Swap)竞争锁;如果设置了,尝试使用 CAS 将对象头的偏向锁指向当前线程。 22 | 23 | 当有另一个线程尝试竞争锁时,持有偏向锁的线程才会释放锁。需要等待全局安全点(在这个时间点上没有字节码正在执行),它会首先暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否活着,如果线程不处于活动状态,则将对象头设置成无锁状态,如果线程仍然活着,拥有偏向锁的栈会被执行,遍历偏向对象的锁记录,栈中的锁记录和对象头的 Mark Word,要么重新偏向于其他线程,要么恢复到无锁或者标记对象不适合作为偏向锁,最后唤醒暂停的线程。 24 | 25 | Java 6,7 默认开启偏向锁,可以通过 JVM 的参数 `-XX:-UsebiasedLocking=false` 关闭。 26 | 27 | # 轻量级锁 28 | 29 | 加锁的流程中,锁记录存储在栈桢,会将对象头的 MarkWord 复制到锁记录。线程在执行同步块时,会尝试用 CAS 将对象头的 MarkWord 替换为指向锁记录的指针,若成功,获得锁;失败表示其他线程竞争锁,当前线程尝试使用自旋获取锁。解锁的流程中,类似于加锁反向操作,会将锁记录复制会对象头的 MarkWord。若成功,表示操作过程中没有竞争发生;若失败,存在竞争,锁会膨胀成重量级锁。 30 | 31 | ![](https://ngte-superbed.oss-cn-beijing.aliyuncs.com/item/20230417212000.png) 32 | 33 | 当膨胀到重量级锁时,不会再通过自选获得锁(自旋时线程处于活动状态,会消耗 CPU),而是将线程阻塞,获得锁的线程执行完后会释放重量级锁,此时唤醒因为锁阻塞的线程,进行新一轮的竞争。 34 | 35 | # 重量级锁 36 | 37 | # Links 38 | 39 | - https://www.cnblogs.com/yuhangwang/p/11295940.html 锁膨胀的源码分析 40 | -------------------------------------------------------------------------------- /03~并发框架/Quartz/codes/examples/src/main/java/org/quartz/examples/example2/SimpleJob.java: -------------------------------------------------------------------------------- 1 | /* 2 | * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | * 16 | */ 17 | 18 | package org.quartz.examples.example2; 19 | 20 | import java.util.Date; 21 | 22 | import org.slf4j.Logger; 23 | import org.slf4j.LoggerFactory; 24 | import org.quartz.Job; 25 | import org.quartz.JobExecutionContext; 26 | import org.quartz.JobExecutionException; 27 | import org.quartz.JobKey; 28 | 29 | /** 30 | *

31 | * This is just a simple job that gets fired off many times by example 1 32 | *

33 | * 34 | * @author Bill Kratzer 35 | */ 36 | public class SimpleJob implements Job { 37 | 38 | private static Logger _log = LoggerFactory.getLogger(SimpleJob.class); 39 | 40 | /** 41 | * Empty constructor for job initialization 42 | */ 43 | public SimpleJob() { 44 | } 45 | 46 | /** 47 | *

48 | * Called by the {@link org.quartz.Scheduler} when a 49 | * {@link org.quartz.Trigger} fires that is associated with 50 | * the Job. 51 | *

52 | * 53 | * @throws JobExecutionException 54 | * if there is an exception while executing the job. 55 | */ 56 | public void execute(JobExecutionContext context) 57 | throws JobExecutionException { 58 | 59 | // This job simply prints out its job name and the 60 | // date and time that it is running 61 | JobKey jobKey = context.getJobDetail().getKey(); 62 | _log.info("SimpleJob says: " + jobKey + " executing at " + new Date()); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /03~并发框架/Quartz/codes/examples/src/main/java/org/quartz/examples/example8/SimpleJob.java: -------------------------------------------------------------------------------- 1 | /* 2 | * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | * 16 | */ 17 | 18 | package org.quartz.examples.example8; 19 | 20 | import java.util.Date; 21 | 22 | import org.slf4j.Logger; 23 | import org.slf4j.LoggerFactory; 24 | import org.quartz.Job; 25 | import org.quartz.JobExecutionContext; 26 | import org.quartz.JobExecutionException; 27 | import org.quartz.JobKey; 28 | 29 | /** 30 | *

31 | * This is just a simple job that gets fired off many times by example 1 32 | *

33 | * 34 | * @author Bill Kratzer 35 | */ 36 | public class SimpleJob implements Job { 37 | 38 | private static Logger _log = LoggerFactory.getLogger(SimpleJob.class); 39 | 40 | /** 41 | * Empty constructor for job initialization 42 | */ 43 | public SimpleJob() { 44 | } 45 | 46 | /** 47 | *

48 | * Called by the {@link org.quartz.Scheduler} when a 49 | * {@link org.quartz.Trigger} fires that is associated with 50 | * the Job. 51 | *

52 | * 53 | * @throws JobExecutionException 54 | * if there is an exception while executing the job. 55 | */ 56 | public void execute(JobExecutionContext context) 57 | throws JobExecutionException { 58 | 59 | // This job simply prints out its job name and the 60 | // date and time that it is running 61 | JobKey jobKey = context.getJobDetail().getKey(); 62 | _log.info("SimpleJob says: " + jobKey + " executing at " + new Date()); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /03~并发框架/Quartz/codes/examples/src/main/java/org/quartz/examples/example9/SimpleJob1.java: -------------------------------------------------------------------------------- 1 | /* 2 | * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | * 16 | */ 17 | 18 | package org.quartz.examples.example9; 19 | 20 | import java.util.Date; 21 | 22 | import org.quartz.Job; 23 | import org.quartz.JobExecutionContext; 24 | import org.quartz.JobExecutionException; 25 | import org.quartz.JobKey; 26 | import org.slf4j.Logger; 27 | import org.slf4j.LoggerFactory; 28 | 29 | /** 30 | *

31 | * This is just a simple job that gets fired off many times by example 1 32 | *

33 | * 34 | * @author Bill Kratzer 35 | */ 36 | public class SimpleJob1 implements Job { 37 | 38 | private static Logger _log = LoggerFactory.getLogger(SimpleJob1.class); 39 | 40 | /** 41 | * Empty constructor for job initilization 42 | */ 43 | public SimpleJob1() { 44 | } 45 | 46 | /** 47 | *

48 | * Called by the {@link org.quartz.Scheduler} when a 49 | * {@link org.quartz.Trigger} fires that is associated with 50 | * the Job. 51 | *

52 | * 53 | * @throws JobExecutionException 54 | * if there is an exception while executing the job. 55 | */ 56 | public void execute(JobExecutionContext context) 57 | throws JobExecutionException { 58 | 59 | // This job simply prints out its job name and the 60 | // date and time that it is running 61 | JobKey jobKey = context.getJobDetail().getKey(); 62 | _log.info("SimpleJob1 says: " + jobKey + " executing at " + new Date()); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /03~并发框架/Quartz/codes/examples/src/main/java/org/quartz/examples/example9/SimpleJob2.java: -------------------------------------------------------------------------------- 1 | /* 2 | * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | * 16 | */ 17 | 18 | package org.quartz.examples.example9; 19 | 20 | import java.util.Date; 21 | 22 | import org.quartz.Job; 23 | import org.quartz.JobExecutionContext; 24 | import org.quartz.JobExecutionException; 25 | import org.quartz.JobKey; 26 | import org.slf4j.Logger; 27 | import org.slf4j.LoggerFactory; 28 | 29 | /** 30 | *

31 | * This is just a simple job that gets fired off many times by example 1 32 | *

33 | * 34 | * @author Bill Kratzer 35 | */ 36 | public class SimpleJob2 implements Job { 37 | 38 | private static Logger _log = LoggerFactory.getLogger(SimpleJob2.class); 39 | 40 | /** 41 | * Empty constructor for job initialization 42 | */ 43 | public SimpleJob2() { 44 | } 45 | 46 | /** 47 | *

48 | * Called by the {@link org.quartz.Scheduler} when a 49 | * {@link org.quartz.Trigger} fires that is associated with 50 | * the Job. 51 | *

52 | * 53 | * @throws JobExecutionException 54 | * if there is an exception while executing the job. 55 | */ 56 | public void execute(JobExecutionContext context) 57 | throws JobExecutionException { 58 | 59 | // This job simply prints out its job name and the 60 | // date and time that it is running 61 | JobKey jobKey = context.getJobDetail().getKey(); 62 | _log.info("SimpleJob2 says: " + jobKey + " executing at " + new Date()); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /03~并发框架/Quartz/codes/examples/src/main/java/org/quartz/examples/example1/HelloJob.java: -------------------------------------------------------------------------------- 1 | /* 2 | * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | * 16 | */ 17 | 18 | package org.quartz.examples.example1; 19 | 20 | import java.util.Date; 21 | 22 | import org.slf4j.Logger; 23 | import org.slf4j.LoggerFactory; 24 | import org.quartz.Job; 25 | import org.quartz.JobExecutionContext; 26 | import org.quartz.JobExecutionException; 27 | 28 | /** 29 | *

30 | * This is just a simple job that says "Hello" to the world. 31 | *

32 | * 33 | * @author Bill Kratzer 34 | */ 35 | public class HelloJob implements Job { 36 | 37 | private static Logger _log = LoggerFactory.getLogger(HelloJob.class); 38 | 39 | /** 40 | *

41 | * Empty constructor for job initilization 42 | *

43 | *

44 | * Quartz requires a public empty constructor so that the 45 | * scheduler can instantiate the class whenever it needs. 46 | *

47 | */ 48 | public HelloJob() { 49 | } 50 | 51 | /** 52 | *

53 | * Called by the {@link org.quartz.Scheduler} when a 54 | * {@link org.quartz.Trigger} fires that is associated with 55 | * the Job. 56 | *

57 | * 58 | * @throws JobExecutionException 59 | * if there is an exception while executing the job. 60 | */ 61 | public void execute(JobExecutionContext context) 62 | throws JobExecutionException { 63 | 64 | // Say Hello to the World and display the date/time 65 | _log.info("Hello World! - " + new Date()); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /03~并发框架/RxJava/操作符/结合操作.md: -------------------------------------------------------------------------------- 1 | # Combing(组合) 2 | 3 | ## Merge(合并) 4 | 5 | 在”异步的世界“中经常会创建这样的场景,我们有多个来源但是又只想有一个结果:多输入,单输出。RxJava 的 merge()方法将帮助你把两个甚至更多的 Observables 合并到他们发射的数据项里。下图给出了把两个序列合并在一个最终发射的 Observable。 6 | ![](http://rxjava.yuxingxin.com/images/chapter6_1.png) 7 | 正如你看到的那样,发射的数据被交叉合并到一个 Observable 里面。注意如果你同步的合并 Observable,它们将连接在一起并且不会交叉。 8 | 9 | ``` 10 | Observable observable_1 = Observable.from(new Integer[]{1, 2}); 11 | 12 | Observable observable_2 = Observable.from(new Integer[]{2, 3}); 13 | 14 | Observable observable_combined = Observable.merge(observable_1, observable_2); 15 | 16 | observable_combined.subscribe( 17 | (value) -> { 18 | 19 | System.out.println(Thread.currentThread().getName() + " Emited!"); 20 | System.out.println(value); 21 | } 22 | ); 23 | ``` 24 | 25 | 需要注意的是,在上述代码中,最终值的输出序列还是 1,2,2,3,这是因为两个 Observable 都是在 Main Thread 中执行,我们来看看如果用`subscribeOn`让每个 Observable 在不同线程中执行的效果: 26 | 27 | ```java 28 | Observable observable_1 = Observable.create(subscriber -> { 29 | try { 30 | Thread.sleep(1000l); 31 | 32 | subscriber.onNext(1); 33 | 34 | Thread.sleep(3000l); 35 | 36 | subscriber.onNext(2); 37 | 38 | } catch (InterruptedException e) { 39 | e.printStackTrace(); 40 | } 41 | }).subscribeOn(Schedulers.newThread()); 42 | 43 | Observable observable_2 = Observable.create(subscriber -> { 44 | try { 45 | 46 | Thread.sleep(2000l); 47 | 48 | subscriber.onNext(3); 49 | 50 | Thread.sleep(4000l); 51 | 52 | subscriber.onNext(4); 53 | 54 | } catch (InterruptedException e) { 55 | e.printStackTrace(); 56 | } 57 | }) 58 | .subscribeOn(Schedulers.newThread()); 59 | 60 | Observable observable_combined = Observable.merge(observable_1, observable_2); 61 | 62 | observable_combined.subscribe( 63 | (value) -> { 64 | 65 | System.out.println(Thread.currentThread().getName() + " Emited!"); 66 | 67 | System.out.println(value); 68 | } 69 | ); 70 | ``` 71 | 72 | 最终的结果如下所示: 73 | 74 | ``` 75 | RxNewThreadScheduler-1 Emited! 76 | 1 77 | RxNewThreadScheduler-2 Emited! 78 | 3 79 | RxNewThreadScheduler-1 Emited! 80 | 2 81 | RxNewThreadScheduler-2 Emited! 82 | 4 83 | ``` 84 | -------------------------------------------------------------------------------- /03~并发框架/Disq/codes/disq/src/main/java/net/intelie/disq/dson/DsonBinaryRead.java: -------------------------------------------------------------------------------- 1 | package net.intelie.disq.dson; 2 | 3 | import net.intelie.disq.Buffer; 4 | 5 | public abstract class DsonBinaryRead { 6 | public static void readUnicode(Buffer.InStream stream, StringView view) { 7 | int len = readCount(stream); 8 | view.set(stream.buf(), stream.position(), len); 9 | stream.position(stream.position() + len * 2); 10 | } 11 | 12 | public static void readLatin1(Buffer.InStream stream, StringView view) { 13 | int len = readCount(stream); 14 | view.set(stream.buf(), stream.position(), len); 15 | stream.position(stream.position() + len); 16 | } 17 | 18 | public static DsonType readType(Buffer.InStream stream) { 19 | return DsonType.findByValue(stream.read()); 20 | } 21 | 22 | public static double readNumber(Buffer.InStream stream) { 23 | return Double.longBitsToDouble(readInt64(stream)); 24 | } 25 | 26 | public static boolean readBoolean(Buffer.InStream stream) { 27 | return stream.read() > 0; 28 | } 29 | 30 | public static int readCount(Buffer.InStream stream) { 31 | int read = stream.read(); 32 | if (read < 128) 33 | return read; 34 | int read2 = stream.read(); 35 | if (read2 < 128) 36 | return (read & 0x7F) | read2 << 7; 37 | return (read & 0x7F) | 38 | (read2 & 0x7F) << 7 | 39 | stream.read() << 14 | 40 | stream.read() << 22 | 41 | stream.read() << 30; 42 | 43 | } 44 | 45 | public static int readInt32(Buffer.InStream stream) { 46 | return stream.read() | 47 | stream.read() << 8 | 48 | stream.read() << 16 | 49 | stream.read() << 24; 50 | } 51 | 52 | public static long readInt64(Buffer.InStream stream) { 53 | return (long) stream.read() | 54 | (long) stream.read() << 8 | 55 | (long) stream.read() << 16 | 56 | (long) stream.read() << 24 | 57 | (long) stream.read() << 32 | 58 | (long) stream.read() << 40 | 59 | (long) stream.read() << 48 | 60 | (long) stream.read() << 56; 61 | 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /03~并发框架/Quartz/codes/examples/src/main/java/org/quartz/examples/example3/SimpleJob.java: -------------------------------------------------------------------------------- 1 | /* 2 | * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | * 16 | */ 17 | 18 | package org.quartz.examples.example3; 19 | 20 | import java.util.Date; 21 | 22 | import org.slf4j.Logger; 23 | import org.slf4j.LoggerFactory; 24 | import org.quartz.Job; 25 | import org.quartz.JobExecutionContext; 26 | import org.quartz.JobExecutionException; 27 | import org.quartz.JobKey; 28 | 29 | /** 30 | *

31 | * This is just a simple job that gets fired off many times by example 1 32 | *

33 | * 34 | * @author Bill Kratzer 35 | */ 36 | public class SimpleJob implements Job { 37 | 38 | private static Logger _log = LoggerFactory.getLogger(SimpleJob.class); 39 | 40 | /** 41 | * Quartz requires a public empty constructor so that the 42 | * scheduler can instantiate the class whenever it needs. 43 | */ 44 | public SimpleJob() { 45 | } 46 | 47 | /** 48 | *

49 | * Called by the {@link org.quartz.Scheduler} when a 50 | * {@link org.quartz.Trigger} fires that is associated with 51 | * the Job. 52 | *

53 | * 54 | * @throws JobExecutionException 55 | * if there is an exception while executing the job. 56 | */ 57 | public void execute(JobExecutionContext context) 58 | throws JobExecutionException { 59 | 60 | // This job simply prints out its job name and the 61 | // date and time that it is running 62 | JobKey jobKey = context.getJobDetail().getKey(); 63 | _log.info("SimpleJob says: " + jobKey + " executing at " + new Date()); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /03~并发框架/Quartz/codes/examples/src/main/java/org/quartz/examples/example9/Job1Listener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | * 16 | */ 17 | 18 | package org.quartz.examples.example9; 19 | 20 | import static org.quartz.JobBuilder.newJob; 21 | import static org.quartz.TriggerBuilder.newTrigger; 22 | 23 | import org.quartz.JobDetail; 24 | import org.quartz.JobExecutionContext; 25 | import org.quartz.JobExecutionException; 26 | import org.quartz.JobListener; 27 | import org.quartz.SchedulerException; 28 | import org.quartz.Trigger; 29 | import org.slf4j.Logger; 30 | import org.slf4j.LoggerFactory; 31 | 32 | /** 33 | * @author wkratzer 34 | */ 35 | public class Job1Listener implements JobListener { 36 | 37 | private static Logger _log = LoggerFactory.getLogger(Job1Listener.class); 38 | 39 | public String getName() { 40 | return "job1_to_job2"; 41 | } 42 | 43 | public void jobToBeExecuted(JobExecutionContext inContext) { 44 | _log.info("Job1Listener says: Job Is about to be executed."); 45 | } 46 | 47 | public void jobExecutionVetoed(JobExecutionContext inContext) { 48 | _log.info("Job1Listener says: Job Execution was vetoed."); 49 | } 50 | 51 | public void jobWasExecuted(JobExecutionContext inContext, JobExecutionException inException) { 52 | _log.info("Job1Listener says: Job was executed."); 53 | 54 | // Simple job #2 55 | JobDetail job2 = newJob(SimpleJob2.class).withIdentity("job2").build(); 56 | 57 | Trigger trigger = newTrigger().withIdentity("job2Trigger").startNow().build(); 58 | 59 | try { 60 | // schedule the job to run! 61 | inContext.getScheduler().scheduleJob(job2, trigger); 62 | } catch (SchedulerException e) { 63 | _log.warn("Unable to schedule job2!"); 64 | e.printStackTrace(); 65 | } 66 | 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /03~并发框架/Disq/codes/disq/src/main/java/net/intelie/disq/ObjectPool.java: -------------------------------------------------------------------------------- 1 | package net.intelie.disq; 2 | 3 | import java.lang.ref.SoftReference; 4 | import java.util.ArrayDeque; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.function.Function; 8 | 9 | public class ObjectPool { 10 | private final ArrayDeque queue = new ArrayDeque<>(); 11 | private final Function factory; 12 | private final int maxRetries; 13 | 14 | @SuppressWarnings({"unused", "FieldCanBeLocal"}) 15 | private final List strong; 16 | //keeping a strong reference to minPoolSize elements to avoid their collection 17 | 18 | public ObjectPool(Function factory) { 19 | this(factory, 1, 5); 20 | } 21 | 22 | public ObjectPool(Function factory, int minPoolSize, int maxRetries) { 23 | this.maxRetries = maxRetries; 24 | this.factory = factory; 25 | this.strong = initMinPool(minPoolSize); 26 | } 27 | 28 | private List initMinPool(int minPoolSize) { 29 | List strong = new ArrayList<>(); 30 | for (int i = 0; i < minPoolSize; i++) { 31 | try (Ref ref = new Ref()) { 32 | strong.add(ref.obj()); 33 | } 34 | } 35 | return strong; 36 | } 37 | 38 | public Ref acquire() { 39 | Ref ref = null; 40 | 41 | for (int i = 0; i < maxRetries; i++) { 42 | synchronized (queue) { 43 | ref = queue.poll(); 44 | } 45 | 46 | //either empty pool or valid deref'd object 47 | if (ref == null || ref.materialize()) break; 48 | 49 | ref = null; 50 | } 51 | 52 | if (ref == null) 53 | ref = new Ref(); 54 | 55 | return ref; 56 | } 57 | 58 | public class Ref implements AutoCloseable { 59 | private final SoftReference ref; 60 | private T obj; 61 | 62 | private Ref() { 63 | this.ref = new SoftReference<>(this.obj = factory.apply(this)); 64 | } 65 | 66 | private boolean materialize() { 67 | this.obj = ref.get(); 68 | return this.obj != null; 69 | } 70 | 71 | public T obj() { 72 | return obj; 73 | } 74 | 75 | @Override 76 | public void close() { 77 | if (obj == null) return; 78 | obj = null; 79 | synchronized (queue) { 80 | queue.offer(this); 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /05~并发模式/单例模式/99~参考资料/2016~高并发下线程安全的单例模式.md: -------------------------------------------------------------------------------- 1 | ```md 2 | 这篇文章详细介绍了多种实现线程安全的单例模式的方法。主要内容包括: 3 | 4 | 1. 饿汉式单例:在类加载时就创建实例,线程安全但可能造成资源浪费。 5 | 6 | 2. 懒汉式单例:第一次使用时才创建实例,但存在线程安全问题。 7 | 8 | 3. 线程安全的懒汉式单例: 9 | 10 | - 使用 synchronized 关键字 11 | - 同步代码块 12 | - Double Check Locking (DCL)双重检查锁机制 13 | 14 | 4. 静态内部类实现单例:利用 JVM 类加载机制保证线程安全。 15 | 16 | 5. 序列化与反序列化的单例实现:使用 readResolve()方法保证反序列化后的单例性。 17 | 18 | 6. 使用 static 代码块实现单例。 19 | 20 | 7. 使用枚举实现单例:最安全的实现方式,可以防止反射和序列化破坏单例。 21 | 22 | 8. 完善的枚举单例实现:通过内部枚举类隐藏实现细节。 23 | 24 | 文章通过代码示例和多线程测试验证了各种实现方式的有效性,并分析了它们的优缺点。最后推荐使用枚举实现单例模式,认为这是最安全可靠的方式。 25 | 26 | [原文链接](https://blog.csdn.net/cselmu9/article/details/51366946/) 27 | ``` 28 | 29 | # [高并发下线程安全的单例模式](https://blog.csdn.net/cselmu9/article/details/51366946/) 30 | 31 | 单例模式是为确保一个类只有一个实例,并为整个系统提供一个全局访问点的一种模式方法。从概念中体现出了单例的一些特点: 32 | 33 | (1)在任何情况下,单例类永远只有一个实例存在 34 | (2)单例需要有能力为整个系统提供这一唯一实例 35 | 36 | 在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个 Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免政出多头。 37 | 38 | 正是由于这个特点,单例对象通常作为程序中的存放配置信息的载体,因为它能保证其他对象读到一致的信息。例如在某个服务器程序中,该服务器的配置信息可能存放在数据库或文件中,这些配置数据由某个单例对象统一读取,服务进程中的其他对象如果要获取这些配置信息,只需访问该单例对象即可。这种方式极大地简化了在复杂环境 下,尤其是多线程环境下的配置管理,但是随着应用场景的不同,也可能带来一些同步问题。 39 | 40 | # 1、饿汉式单例 41 | 42 | 饿汉式单例是指在方法调用前,实例就已经创建好了。下面是实现代码: 43 | 44 | ```java 45 | package org.mlinge.s01; 46 | 47 | public class MySingleton { 48 | 49 | private static MySingleton instance = new MySingleton(); 50 | 51 | private MySingleton(){} 52 | 53 | public static MySingleton getInstance() { 54 | return instance; 55 | } 56 | 57 | } 58 | ``` 59 | 60 | 以上是单例的饿汉式实现,我们来看看饿汉式在多线程下的执行情况,给出一段多线程的执行代码: 61 | 62 | ```java 63 | package org.mlinge.s01; 64 | 65 | public class MyThread extends Thread{ 66 | 67 | @Override 68 | public void run() { 69 | System.out.println(MySingleton.getInstance().hashCode()); 70 | } 71 | 72 | public static void main(String[] args) { 73 | 74 | MyThread[] mts = new MyThread[10]; 75 | for(int i = 0 ; i < mts.length ; i++){ 76 | mts[i] = new MyThread(); 77 | } 78 | 79 | for (int j = 0; j < mts.length; j++) { 80 | mts[j].start(); 81 | } 82 | } 83 | } 84 | ``` 85 | 86 | ```sh 87 | 1718900954 88 | 1718900954 89 | 1718900954 90 | 1718900954 91 | 1718900954 92 | 1718900954 93 | 1718900954 94 | 1718900954 95 | 1718900954 96 | 1718900954 97 | ``` 98 | 99 | 从运行结果可以看出实例变量额 hashCode 值一致,这说明对象是同一个,饿汉式单例实现了。 100 | 101 | # 2、懒汉式单例 102 | -------------------------------------------------------------------------------- /03~并发框架/Quartz/codes/examples/src/main/java/org/quartz/examples/example12/SimpleJob.java: -------------------------------------------------------------------------------- 1 | /* 2 | * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | * 16 | */ 17 | 18 | package org.quartz.examples.example12; 19 | 20 | import java.util.Date; 21 | 22 | import org.slf4j.Logger; 23 | import org.slf4j.LoggerFactory; 24 | import org.quartz.Job; 25 | import org.quartz.JobExecutionContext; 26 | import org.quartz.JobExecutionException; 27 | import org.quartz.JobKey; 28 | 29 | /** 30 | *

31 | * A dumb implementation of Job, for unittesting purposes. 32 | *

33 | * 34 | * @author James House 35 | */ 36 | public class SimpleJob implements Job { 37 | 38 | public static final String MESSAGE = "msg"; 39 | 40 | private static Logger _log = LoggerFactory.getLogger(SimpleJob.class); 41 | 42 | /** 43 | * Quartz requires a public empty constructor so that the 44 | * scheduler can instantiate the class whenever it needs. 45 | */ 46 | public SimpleJob() { 47 | } 48 | 49 | /** 50 | *

51 | * Called by the {@link org.quartz.Scheduler} when a 52 | * {@link org.quartz.Trigger} fires that is associated with 53 | * the Job. 54 | *

55 | * 56 | * @throws JobExecutionException 57 | * if there is an exception while executing the job. 58 | */ 59 | public void execute(JobExecutionContext context) 60 | throws JobExecutionException { 61 | 62 | // This job simply prints out its job name and the 63 | // date and time that it is running 64 | JobKey jobKey = context.getJobDetail().getKey(); 65 | 66 | String message = (String) context.getJobDetail().getJobDataMap().get(MESSAGE); 67 | 68 | _log.info("SimpleJob: " + jobKey + " executing at " + new Date()); 69 | _log.info("SimpleJob: msg: " + message); 70 | } 71 | 72 | 73 | 74 | } 75 | -------------------------------------------------------------------------------- /02~线程安全/锁/ReentrantLock/ReentrantLock.md: -------------------------------------------------------------------------------- 1 | # ReentrantLock 2 | 3 | 可重入互斥锁具有与使用 synchronized 的隐式监视器锁具有相同的行为和语义,但具有更好扩展功能。ReentrantLock 由最后成功锁定的线程拥有,而且还未解锁。当锁未被其他线程占有时,线程调用 lock()将返回并且成功获取锁。如果当前线程已拥有锁,则该方法将立即返回。这可以使用方法 isHeldByCurrentThread()和 getHoldCount()来检查。 4 | 5 | | 特性 | ReentrantLock | Synchronized | 6 | | ---------- | ------------------------------ | ---------------- | 7 | | 锁实现机制 | 依赖 AQS | 监视器模式 | 8 | | 灵活性 | 支持响应中断、超时、尝试获取锁 | 不灵活 | 9 | | 释放形式 | 必须显示调用 unlock() 释放锁 | 自动释放监视器 | 10 | | 锁类型 | 公平锁 & 非公平锁 | 非公平锁 | 11 | | 条件队列 | 可关联多个条件队列 | 关联一个条件队列 | 12 | | 可重入性 | 可重入 | 可重入 | 13 | 14 | 典型的 synchronized 与 ReentrantLock 对比如下: 15 | 16 | ```java 17 | // **************************Synchronized的使用方式************************** 18 | // 1.用于代码块 19 | synchronized (this) {} 20 | // 2.用于对象 21 | synchronized (object) {} 22 | // 3.用于方法 23 | public synchronized void test () {} 24 | // 4.可重入 25 | for (int i = 0; i < 100; i++) { 26 | synchronized (this) {} 27 | } 28 | 29 | // **************************ReentrantLock的使用方式************************** 30 | public void test () throw Exception { 31 | // 1.初始化选择公平锁、非公平锁 32 | ReentrantLock lock = new ReentrantLock(true); 33 | // 2.可用于代码块 34 | lock.lock(); 35 | try { 36 | try { 37 | // 3.支持多种加锁方式,比较灵活; 具有可重入特性 38 | if(lock.tryLock(100, TimeUnit.MILLISECONDS)){ } 39 | } finally { 40 | // 4.手动释放锁 41 | lock.unlock() 42 | } 43 | } finally { 44 | lock.unlock(); 45 | } 46 | } 47 | ``` 48 | 49 | # 使用 ReentrantLock 50 | 51 | 构造函数接受可选的 fairness 参数。当设置为 true 时,在竞争条件下,锁定有利于赋予等待时间最长线程的访问权限。否则,锁将不保证特定的访问顺序。在多线程访问的情况,使用公平锁比默认设置,有着更低的吞吐量,但是获得锁的时间比较小而且可以避免等待锁导致的饥饿。但是,锁的公平性并不能保证线程调度的公平性。因此,使用公平锁的许多线程中的一个可以连续多次获得它,而其他活动线程没有进展并且当前没有持有锁。不定时的 tryLock()方法不遵循公平性设置。即使其他线程正在等待,如果锁可用,它也会成功。 52 | 53 | ```java 54 | public class Test implements Runnable { 55 | 56 | public synchronized void get() { 57 | System.out.println(Thread.currentThread().getId()); 58 | 59 | //在子方法里又进入了锁 60 | set(); 61 | } 62 | 63 | public synchronized void set() { 64 | System.out.println(Thread.currentThread().getId()); 65 | } 66 | 67 | @Override 68 | public void run() { 69 | get(); 70 | } 71 | 72 | public static void main(String[] args) { 73 | Test ss = new Test(); 74 | new Thread(ss).start(); 75 | new Thread(ss).start(); 76 | new Thread(ss).start(); 77 | } 78 | } 79 | ``` 80 | 81 | # Links 82 | 83 | - [Java 中的 ReentrantLock 和 synchronized 两种锁定机制的对比](http://my.eoe.cn/niunaixiaoshu/archive/5227.html) 84 | -------------------------------------------------------------------------------- /03~并发框架/Quartz/codes/examples/src/main/java/org/quartz/examples/example11/SimpleJob.java: -------------------------------------------------------------------------------- 1 | /* 2 | * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | * 16 | */ 17 | 18 | package org.quartz.examples.example11; 19 | 20 | import org.quartz.Job; 21 | import org.quartz.JobExecutionContext; 22 | import org.quartz.JobExecutionException; 23 | import org.quartz.JobKey; 24 | import org.slf4j.Logger; 25 | import org.slf4j.LoggerFactory; 26 | 27 | import java.util.Date; 28 | 29 | /** 30 | *

31 | * This is just a simple job that gets fired off many times by example 1 32 | *

33 | * 34 | * @author Bill Kratzer 35 | */ 36 | public class SimpleJob implements Job { 37 | 38 | private static Logger _log = LoggerFactory.getLogger(SimpleJob.class); 39 | 40 | // job parameter 41 | public static final String DELAY_TIME = "delay time"; 42 | 43 | /** 44 | * Empty constructor for job initilization 45 | */ 46 | public SimpleJob() { 47 | } 48 | 49 | /** 50 | *

51 | * Called by the {@link org.quartz.Scheduler} when a {@link org.quartz.Trigger} fires that 52 | * is associated with the Job. 53 | *

54 | * 55 | * @throws JobExecutionException if there is an exception while executing the job. 56 | */ 57 | public void execute(JobExecutionContext context) throws JobExecutionException { 58 | 59 | // This job simply prints out its job name and the 60 | // date and time that it is running 61 | JobKey jobKey = context.getJobDetail().getKey(); 62 | _log.info("Executing job: " + jobKey + " executing at " + new Date()); 63 | 64 | // wait for a period of time 65 | long delayTime = context.getJobDetail().getJobDataMap().getLong(DELAY_TIME); 66 | try { 67 | Thread.sleep(delayTime); 68 | } catch (Exception e) { 69 | // 70 | } 71 | 72 | _log.info("Finished Executing job: " + jobKey + " at " + new Date()); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /03~并发框架/RxJava/操作符/辅助操作.md: -------------------------------------------------------------------------------- 1 | # Utility 2 | 3 | ## delay:延迟发射或者监听 4 | 5 | 顾名思义,delay 操作会延时一段时间再发射数据。有两种方式实现这个效果;一是缓存这些数据,等一段时间后再发射;或者是把 Subscriber 订阅的时间延迟。 6 | 7 | ### delay() 8 | 9 | 简单的 delay 函数只是把每个数据都延时一段时间再发射,相当于把整个数据流都往后推迟了。 10 | 11 | ```java 12 | Observable.interval(100, TimeUnit.MILLISECONDS).take(5) 13 | .delay(1, TimeUnit.SECONDS) 14 | .timeInterval() 15 | .subscribe(System.out::println); 16 | ``` 17 | 18 | 输出: 19 | 20 | ```java 21 | TimeInterval [intervalInMilliseconds=1109, value=0] 22 | TimeInterval [intervalInMilliseconds=94, value=1] 23 | TimeInterval [intervalInMilliseconds=100, value=2] 24 | TimeInterval [intervalInMilliseconds=100, value=3] 25 | TimeInterval [intervalInMilliseconds=101, value=4] 26 | ``` 27 | 28 | 可以看到,第一个数据差不多被延迟了 1s,后面每隔 100ms 左右发射下一个数据。还可以分别推迟每个数据的时间。 29 | 30 | 这个重载函数的参数为一个函数,该函数的参数为源 Observable 发射的数据返回一个 信号 Observable。当信号 Observable 发射数据的时候,也就是源 Observable 的数据发射的时候。 31 | 32 | ```java 33 | Observable.interval(100, TimeUnit.MILLISECONDS).take(5) 34 | .delay(i -> Observable.timer(i * 100, TimeUnit.MILLISECONDS)) 35 | .timeInterval() 36 | .subscribe(System.out::println); 37 | ``` 38 | 39 | 输出: 40 | 41 | ```java 42 | TimeInterval [intervalInMilliseconds=152, value=0] 43 | TimeInterval [intervalInMilliseconds=173, value=1] 44 | TimeInterval [intervalInMilliseconds=199, value=2] 45 | TimeInterval [intervalInMilliseconds=201, value=3] 46 | TimeInterval [intervalInMilliseconds=199, value=4] 47 | ``` 48 | 49 | 源 Observable 每隔 100ms 发射一个数据,而结果显示为 200ms 发射一个数据。interval 从 0 开始发射数据,i 结果为 0、1、2 等,每隔数据推迟了 `i*100ms` 再发射。所以后面每隔数据都比前一个数据多推迟了 100ms,结果就是每个数据差不多间隔 200ms 发射。 50 | 51 | ### delaySubscription 52 | 53 | 除了缓存数据,延迟发射缓冲的数据以外,还可以选择使用推迟订阅的方式。根据 Observable 是 hot 或者 cold 则会有不同的结果。后面会专门的介绍 cold 和 hot Observable 的区别。这里的示例为 cold Observable,推迟订阅到 cold Observable 和推迟整个数据流是一样的效果。但是由于推迟订阅不需要缓存发射的数据,所以更加高效。 54 | 55 | ```java 56 | Observable.interval(100, TimeUnit.MILLISECONDS).take(5) 57 | .delaySubscription(1000, TimeUnit.MILLISECONDS) 58 | .timeInterval() 59 | .subscribe(System.out::println); 60 | ``` 61 | 62 | 输出: 63 | 64 | ```java 65 | TimeInterval [intervalInMilliseconds=1114, value=0] 66 | TimeInterval [intervalInMilliseconds=92, value=1] 67 | TimeInterval [intervalInMilliseconds=101, value=2] 68 | TimeInterval [intervalInMilliseconds=100, value=3] 69 | TimeInterval [intervalInMilliseconds=99, value=4] 70 | ``` 71 | 72 | 可以看到整个数据流推迟了 1000ms。同样还有一个重载函数,可以使用另外一个 Observable 来告诉 Subscriber 何时订阅: 73 | 74 | ```java 75 | public final Observable delaySubscription(Func0> subscriptionDelay) 76 | ``` 77 | -------------------------------------------------------------------------------- /02~线程安全/线程通信/线程协作工具/Semaphore.md: -------------------------------------------------------------------------------- 1 | # Semaphore 2 | 3 | 锁和同步块同时只能允许单个线程访问共享资源,这个明显有些单调,部分场景其实可以允许多个线程访问,这个时候信号量实例就派上用场了。信号量逻辑上维持了一组许可证,线程调用 acquire()阻塞直到许可证可用后才能执行。执行 release() 意味着释放许可证,实际上信号量并没有真正的许可证,只是采用了计数功能来实现这个功能。 4 | 5 | # 案例:多资源竞争 6 | 7 | 举个例子,如下代码,十个线程竞争三个资源,一开始有三个线程可以直接运行,剩下的七个线程只能阻塞等到其它线程使用资源完毕才能执行; 8 | 9 | ```java 10 | public class SemaphoreTest { 11 | 12 | public static void print(String str){ 13 | SimpleDateFormat dfdate = new SimpleDateFormat("HH:mm:ss"); 14 | System.out.println("[" + dfdate.format(new Date()) + "]" + Thread.currentThread().getName() + str); 15 | } 16 | 17 | public static void main(String[] args) { 18 | // 线程数目 19 | int threadCount = 10; 20 | // 资源数目 21 | Semaphore semaphore = new Semaphore(3); 22 | 23 | ExecutorService es = Executors.newFixedThreadPool(threadCount); 24 | 25 | // 启动若干线程 26 | for (int i = 0; i < threadCount; i++) 27 | es.execute(new ConsumeResourceTask((i + 1) * 1000, semaphore)); 28 | } 29 | } 30 | 31 | class ConsumeResourceTask implements Runnable { 32 | private Semaphore semaphore; 33 | private int sleepTime; 34 | 35 | public ConsumeResourceTask(int sleepTime, Semaphore semaphore) { 36 | this.sleepTime = sleepTime; 37 | this.semaphore = semaphore; 38 | } 39 | 40 | public void run() { 41 | try { 42 | //获取资源 43 | semaphore.acquire(); 44 | SemaphoreTest.print(" 占用一个资源..."); 45 | TimeUnit.MILLISECONDS.sleep(sleepTime); 46 | SemaphoreTest.print(" 资源使用结束,释放资源"); 47 | //释放资源 48 | semaphore.release(); 49 | } catch (InterruptedException e) { 50 | e.printStackTrace(); 51 | } 52 | } 53 | } 54 | 55 | [10:30:11]pool-1-thread-1 占用一个资源... 56 | [10:30:11]pool-1-thread-2 占用一个资源... 57 | [10:30:11]pool-1-thread-3 占用一个资源... 58 | [10:30:12]pool-1-thread-1 资源使用结束,释放资源 59 | [10:30:12]pool-1-thread-4 占用一个资源... 60 | [10:30:13]pool-1-thread-2 资源使用结束,释放资源 61 | [10:30:13]pool-1-thread-5 占用一个资源... 62 | [10:30:14]pool-1-thread-3 资源使用结束,释放资源 63 | [10:30:14]pool-1-thread-8 占用一个资源... 64 | [10:30:16]pool-1-thread-4 资源使用结束,释放资源 65 | [10:30:16]pool-1-thread-6 占用一个资源... 66 | [10:30:18]pool-1-thread-5 资源使用结束,释放资源 67 | [10:30:18]pool-1-thread-9 占用一个资源... 68 | [10:30:22]pool-1-thread-8 资源使用结束,释放资源 69 | [10:30:22]pool-1-thread-7 占用一个资源... 70 | [10:30:22]pool-1-thread-6 资源使用结束,释放资源 71 | [10:30:22]pool-1-thread-10 占用一个资源... 72 | [10:30:27]pool-1-thread-9 资源使用结束,释放资源 73 | [10:30:29]pool-1-thread-7 资源使用结束,释放资源 74 | [10:30:32]pool-1-thread-10 资源使用结束,释放资源 75 | ``` 76 | -------------------------------------------------------------------------------- /02~线程安全/README.md: -------------------------------------------------------------------------------- 1 | > 参阅《[Concurrent-Notes](https://github.com/wx-chevalier/Concurrent-Notes?q=线程安全)》相关章节了解线程安全知识。 2 | 3 | # 线程安全 4 | 5 | 在 Java 中,保证线程安全一般会用两种方式:锁和原子变量。volatile 确保每次操作都能强制同步 CPU 缓存和主存直接的变量。而且在编译期间能阻止指令重排。读写并发情况下 volatile 也不能确保线程安全。 6 | 7 | # 锁 8 | 9 | ![通过加锁的方式实现线程安全](https://ngte-superbed.oss-cn-beijing.aliyuncs.com/superbed/2021/07/22/60f92c625132923bf8d6fd1a.jpg) 10 | 11 | 采取加锁的方式,默认线程会冲突,访问数据时,先加上锁再访问,访问之后再解锁。通过锁界定一个临界区,同时只有一个线程进入。如上图所示,Thread2 访问 Entry 的时候,加了锁,Thread1 就不能再执行访问 Entry 的代码,从而保证线程安全。下面是 ArrayBlockingQueue 通过加锁的方式实现的 offer 方法,保证线程安全。 12 | 13 | ```java 14 | public boolean offer(E e) { 15 | checkNotNull(e); 16 | final ReentrantLock lock = this.lock; 17 | lock.lock(); 18 | try { 19 | if (count == items.length) 20 | return false; 21 | else { 22 | insert(e); 23 | return true; 24 | } 25 | } finally { 26 | lock.unlock(); 27 | } 28 | } 29 | ``` 30 | 31 | # 原子变量 32 | 33 | 原子变量能够保证原子性的操作,意思是某个任务在执行过程中,要么全部成功,要么全部失败回滚,恢复到执行之前的初态,不存在初态和成功之间的中间状态。例如 CAS 操作,要么比较并交换成功,要么比较并交换失败。由 CPU 保证原子性。通过原子变量可以实现线程安全。执行某个任务的时候,先假定不会有冲突,若不发生冲突,则直接执行成功;当发生冲突的时候,则执行失败,回滚再重新操作,直到不发生冲突。 34 | 35 | ![通过原子变量 CAS 实现线程安全](https://ngte-superbed.oss-cn-beijing.aliyuncs.com/superbed/2021/07/22/60f92eb75132923bf8de26e1.jpg) 36 | 37 | 如图所示,Thread1 和 Thread2 都要把 Entry 加 1。若不加锁,也不使用 CAS,有可能 Thread1 取到了 myValue=1,Thread2 也取到了 myValue=1,然后相加,Entry 中的 value 值为 2。这与预期不相符,我们预期的是 Entry 的值经过两次相加后等于 3。CAS 会先把 Entry 现在的 value 跟线程当初读出的值相比较,若相同,则赋值;若不相同,则赋值执行失败。一般会通过 while/for 循环来重新执行,直到赋值成功。 38 | 39 | 代码示例是 AtomicInteger 的 getAndAdd 方法。CAS 是 CPU 的一个指令,由 CPU 保证原子性。 40 | 41 | ```java 42 | /** 43 | * Atomically adds the given value to the current value. 44 | * 45 | * @param delta the value to add 46 | * @return the previous value 47 | */ 48 | public final int getAndAdd(int delta) { 49 | for (;;) { 50 | int current = get(); 51 | int next = current + delta; 52 | if (compareAndSet(current, next)) 53 | return current; 54 | } 55 | } 56 | 57 | /** 58 | * Atomically sets the value to the given updated value 59 | * if the current value {@code ==} the expected value. 60 | * 61 | * @param expect the expected value 62 | * @param update the new value 63 | * @return true if successful. False return indicates that 64 | * the actual value was not equal to the expected value. 65 | */ 66 | public final boolean compareAndSet(int expect, int update) { 67 | return unsafe.compareAndSwapInt(this, valueOffset, expect, update); 68 | } 69 | ``` 70 | 71 | 在高度竞争的情况下,锁的性能将超过原子变量的性能,但是更真实的竞争情况下,原子变量的性能将超过锁的性能。同时原子变量不会有死锁等活跃性问题。 72 | -------------------------------------------------------------------------------- /03~并发框架/Akka/异常处理与持久化.md: -------------------------------------------------------------------------------- 1 | # Error Handling & Persistence | 异常处理与持久化 2 | 3 | ## Fault Tolerance | 容错机制 4 | 5 | ## Persistence | 持久化 6 | 7 | Akka 持久化可以使有状态的 actor 能够保持其内部状态,以便在启动、JVM 崩溃后重新启动、或在集群中迁移时,恢复它们的内部状态。Akka 持久性关键点在于,只有对 actor 内部状态的更改才会被持久化,而不会直接保持其当前状态(可选快照除外)。这些更改只会追加到存储,没有任何修改,这允许非常高的事务速率和高效的复制;通过加载持久化的数据 stateful actors 可以重建内部状态。 8 | 9 | Akka 持久性扩展依赖一些内置持久性插件,包括基于内存堆的日志,基于本地文件系统的快照存储和基于 LevelDB 的日志。 10 | 11 | ```xml 12 | 13 | org.iq80.leveldb 14 | leveldb 15 | 0.7 16 | 17 | ``` 18 | 19 | 然后我们还需要针对持久化策略添加相关的配置: 20 | 21 | ``` 22 | akka.persistence.journal.plugin = "akka.persistence.journal.leveldb" 23 | akka.persistence.snapshot-store.plugin = "akka.persistence.snapshot-store.local" 24 | 25 | akka.persistence.journal.leveldb.dir = "target/example/journal" 26 | akka.persistence.snapshot-store.local.dir = "target/example/snapshots" 27 | 28 | # DO NOT USE THIS IN PRODUCTION !!! 29 | # See also https://github.com/typesafehub/activator/issues/287 30 | akka.persistence.journal.leveldb.native = false 31 | ``` 32 | 33 | 当我们声明某个可持久化的 Actor 时,需要使其继承自 AbstractPersistentActor: 34 | 35 | ```java 36 | class ExamplePersistentActor extends AbstractPersistentActor {} 37 | ``` 38 | 39 | 然后复写 createReceiveRecover 与 createReceive 方法;createReceive 是正常的处理消息的方法,而 createReceiveRecover 则是用于在恢复阶段处理接收到的消息的方法。 40 | 41 | ```java 42 | @Override 43 | public Receive createReceiveRecover() { 44 | return receiveBuilder() 45 | // 恢复之前在上一个快照点之后发布的 Event 46 | .match(Evt.class, e -> state.update(e)) 47 | // 恢复之前保存的状态 48 | .match(SnapshotOffer.class, ss -> state = (ExampleState) ss.snapshot()) 49 | .build(); 50 | } 51 | 52 | @Override 53 | public Receive createReceive() { 54 | return receiveBuilder() 55 | .match(Cmd.class, c -> { 56 | final String data = c.getData(); 57 | final Evt evt = new Evt(data + "-" + getNumEvents()); 58 | 59 | // 持久化消息 60 | persist(evt, (Evt event) -> { 61 | state.update(event); 62 | getContext().system().eventStream().publish(event); 63 | }); 64 | }) 65 | // 触发持久化当前状态 66 | .matchEquals("snap", s -> saveSnapshot(state.copy())) 67 | .matchEquals("print", s -> System.out.println(state)) 68 | .build(); 69 | } 70 | ``` 71 | 72 | 在外部调用时,我们可以手动地触发进行状态存储: 73 | 74 | ```java 75 | persistentActor.tell(new Cmd("foo"), null); 76 | persistentActor.tell("snap", null); 77 | persistentActor.tell(new Cmd("buzz"), null); 78 | persistentActor.tell("print", null); 79 | ``` 80 | -------------------------------------------------------------------------------- /03~并发框架/Disq/codes/disq/src/main/java/net/intelie/disq/PersistentQueue.java: -------------------------------------------------------------------------------- 1 | package net.intelie.disq; 2 | 3 | import java.io.Closeable; 4 | import java.io.IOException; 5 | import java.util.concurrent.TimeUnit; 6 | 7 | public class PersistentQueue implements Closeable { 8 | private final InternalQueue queue; 9 | private final SerializerPool pool; 10 | 11 | public PersistentQueue(InternalQueue queue, SerializerPool pool) { 12 | this.queue = queue; 13 | this.pool = pool; 14 | } 15 | 16 | public SerializerPool pool() { 17 | return pool; 18 | } 19 | 20 | public RawQueue rawQueue() { 21 | return queue.rawQueue(); 22 | } 23 | 24 | public ArrayRawQueue fallbackQueue() { 25 | return queue.fallbackQueue(); 26 | } 27 | 28 | public void setPaused(boolean paused) { 29 | queue.setPaused(paused); 30 | } 31 | 32 | public void reopen() throws IOException { 33 | queue.reopen(); 34 | } 35 | 36 | public long bytes() { 37 | return queue.bytes(); 38 | } 39 | 40 | public long count() { 41 | return queue.count(); 42 | } 43 | 44 | public long remainingBytes() { 45 | return queue.remainingBytes(); 46 | } 47 | 48 | public long remainingCount() { 49 | return queue.remainingCount(); 50 | } 51 | 52 | public void clear() throws IOException { 53 | queue.clear(); 54 | } 55 | 56 | public void flush() throws IOException { 57 | queue.flush(); 58 | } 59 | 60 | public T blockingPop(long amount, TimeUnit unit) throws InterruptedException, IOException { 61 | try (SerializerPool.Slot slot = pool.acquire()) { 62 | return slot.blockingPop(queue, amount, unit); 63 | } 64 | } 65 | 66 | public T blockingPop() throws InterruptedException, IOException { 67 | try (SerializerPool.Slot slot = pool.acquire()) { 68 | return slot.blockingPop(queue); 69 | } 70 | 71 | } 72 | 73 | public T pop() throws IOException { 74 | try (SerializerPool.Slot slot = pool.acquire()) { 75 | return slot.pop(queue); 76 | } 77 | } 78 | 79 | public void push(T obj) throws IOException { 80 | try (SerializerPool.Slot slot = pool.acquire()) { 81 | slot.push(queue, obj); 82 | } 83 | } 84 | 85 | public T peek() throws IOException { 86 | try (SerializerPool.Slot slot = pool.acquire()) { 87 | return slot.peek(queue); 88 | } 89 | } 90 | 91 | public void close() { 92 | queue.close(); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /04~并发网络 IO/NIO/Channel.md: -------------------------------------------------------------------------------- 1 | # Channel 2 | 3 | Channel 是一个通道,可以通过它读取和写入数据,它就像自来水管一样,网络数据通过 Channel 读取和写入。通道与流的不同之处在于通道是双向的,流只是在一个方向上移动(InputStream 或 OutputStream 的子类),而且通道可以用于读、写或者同时用于读写。 4 | 5 | - 一个 Channel 可以读和写,而一个流一般只能读或者写 6 | - Channel 可以异步(asynchronously)的读和写 7 | - Channel 总是需要一个 Buffer,不管是读到 Buffer 还是从 Buffer 写到 Channel 8 | 9 | 因为 Channel 是全双工的,所以它可以比流更好地映射底层操作系统的 API。在 UNIX 网络编程模型中,底层操作系统的通道都是全双工的,同时支持读写操作。NIO 中包含了几个常见的 Channel,这几个 channles 包含了咱们开发中使用率较高的 文件 IO,UDP+TCP 网络 IO。 10 | 11 | - FileChannel 读取数据或者写入数据到文件中 12 | - DatagramChannel 读写数据通过 UDP 协议 13 | - SocketChannel 读写数据通过 TCP 协议 14 | - ServerSocketChannel 提供 TCP 连接的监听,每个进入的连接都会创建一个 SocketChannel。 15 | 16 | ![](http://hi.csdn.net/attachment/201107/17/0_1310888420STkI.gif) 17 | 18 | # 数据读取 19 | 20 | 在前面我们说过,任何时候读取数据,都不是直接从通道读取,而是从通道读取到缓冲区。所以使用 NIO 读取数据可以分为下面三个步骤: 21 | 22 | - 从 FileInputStream 获取 Channel 23 | - 创建 Buffer 24 | - 将数据从 Channel 读取到 Buffer 中 25 | 26 | 下面是一个简单的使用 NIO 从文件中读取数据的例子: 27 | 28 | ```java 29 | FileInputStream fin = new FileInputStream("test"); 30 | // 获取通道 31 | FileChannel fc = fin.getChannel(); 32 | // 创建缓冲区 33 | ByteBuffer buffer = ByteBuffer.allocate(1024); 34 | // 读取数据到缓冲区 35 | fc.read(buffer); 36 | buffer.flip(); 37 | while (buffer.remaining()>0) { 38 | byte b = buffer.get(); 39 | System.out.print(((char)b)); 40 | } 41 | 42 | fin.close(); 43 | ``` 44 | 45 | 我们也可以使用 RandomAccessFile 来随机读取文件内容: 46 | 47 | ```java 48 | RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw"); 49 | FileChannel inChannel = aFile.getChannel(); 50 | ByteBuffer buf = ByteBuffer.allocate(48); 51 | int bytesRead = inChannel.read(buf); 52 | while (bytesRead != -1) { 53 | System.out.println("Read " + bytesRead); 54 | buf.flip(); 55 | while(buf.hasRemaining()){ 56 | System.out.print((char) buf.get()); 57 | } 58 | buf.clear(); 59 | bytesRead = inChannel.read(buf); 60 | } 61 | aFile.close(); 62 | ``` 63 | 64 | # 数据写入 65 | 66 | 使用 NIO 写入数据与读取数据的过程类似,同样数据不是直接写入通道,而是写入缓冲区,可以分为下面三个步骤: 67 | 68 | - 从 FileInputStream 获取 Channel 69 | - 创建 Buffer 70 | - 将数据从 Channel 写入到 Buffer 中 71 | 72 | 下面是一个简单的使用 NIO 向文件中写入数据的例子: 73 | 74 | ```java 75 | static private final byte message[] = { 83, 111, 109, 101, 32, 98, 121, 116, 101, 115, 46 }; 76 | 77 | static public void main( String args[] ) throws Exception { 78 | FileOutputStream fout = new FileOutputStream("/test"); 79 | FileChannel fc = fout.getChannel(); 80 | ByteBuffer buffer = ByteBuffer.allocate(1024); 81 | for (int i=0; i Lenient.perform(queue, buffer, op)) 51 | .isInstanceOf(Error.class).hasMessage("abc"); 52 | assertThat(Lenient.performSafe(queue, buffer, op, 100)).isEqualTo(100); 53 | 54 | orderly.verify(op).call(buffer); 55 | orderly.verify(queue).reopen(); 56 | orderly.verify(op).call(buffer); 57 | orderly.verify(queue).reopen(); 58 | } 59 | 60 | @Test 61 | public void testExceptionOnClose() throws Exception { 62 | Closeable closeable = mock(Closeable.class); 63 | doThrow(new Error("abc")).when(closeable).close(); 64 | 65 | Lenient.safeClose(closeable); 66 | verify(closeable).close(); 67 | } 68 | 69 | @Test 70 | public void safeDeleteOnNonExistingFile() throws Exception { 71 | Lenient.safeDelete(Paths.get("/whatever/does/not/exist")); 72 | } 73 | } -------------------------------------------------------------------------------- /99~参考资料/极客时间~《Java 并发编程实战》/05~一不小心就死锁了,怎么办?.md: -------------------------------------------------------------------------------- 1 | > DocId: 7Jk3YhsJ1BEXk4Z 2 | 3 | # 一不小心就死锁了,怎么办? 4 | 5 | 在现实世界中,银行的转账操作是支持并发的,并且是真正并行进行的,银行的每个柜台都能够独立执行转账。如果我们能够在程序中模拟这种现实世界的并发转账操作,那么串行执行的问题就能得到解决。想象在古代,没有现代信息化手段,账户信息是以账本的形式存在的,每个账户对应一个账本,而这些账本都被统一存放在文件架上。当银行柜员执行转账操作时,他们需要从文件架上取下涉及转账的两个账本,然后进行操作。在这个过程中,柜员可能会遇到三种情况: 6 | 7 | 1. 文件架上同时有转出和转入的账本,柜员就可以直接取走这两个账本; 8 | 2. 如果文件架上只有其中一个账本,柜员会先取下现有的账本,然后等待另一个账本被其他柜员放回文件架; 9 | 3. 如果两个账本都不在文件架上,柜员就需要等待它们都被其他柜员送回来。 10 | 11 | 在编程实践中,如何实现类似古代银行柜员处理转账的流程?答案可以通过使用两把锁来模拟。我们为转出账户和转入账户各分配一把锁。在 transfer() 方法中,首先尝试获取转出账户 this 的锁(相当于拿到转出账本),随后尝试获取转入账户 target 的锁(相当于拿到转入账本)。只有当两把锁都成功获取时,我们才继续执行转账操作。这个过程可以用以下图形化的方式来表示。 12 | 13 | ![两个转账操作并行示意图](https://ngte-superbed.oss-cn-beijing.aliyuncs.com/uPic/ra44p67PKjlF.png) 14 | 15 | ```java 16 | class Account { 17 | private int balance; 18 | // 转账 19 | void transfer(Account target, int amt){ 20 | // 锁定转出账户 21 | synchronized(this) { 22 | // 锁定转入账户 23 | synchronized(target) { 24 | if (this.balance > amt) { 25 | this.balance -= amt; 26 | target.balance += amt; 27 | } 28 | } 29 | } 30 | } 31 | } 32 | ``` 33 | 34 | # 没有免费的午餐 35 | 36 | 与使用 Account.class 作为单一互斥锁相比,锁定的范围过于宽泛,而如果我们只锁定涉及转账的两个具体账户,锁定的范围就会小很多。这种锁我们在之前已经讨论过,被称为细粒度锁。采用细粒度锁能够提升程序的并行处理能力,是提升性能的一个关键策略。此时,你可能已经开始意识到,细粒度锁听起来如此简单有效,是否意味着需要付出一些额外的代价?在编写并发程序时,我们总是需要保持警惕。 37 | 38 | 确实,细粒度锁的使用并非没有成本,它可能带来的一个主要问题是死锁的风险。让我们先观察现实世界中的一个类似情形。设想客户向柜员张三请求进行一笔转账:从账户 A 向账户 B 转账 100 元。同时,另一位客户向柜员李四请求另一笔转账:从账户 B 向账户 A 转账 100 元。张三和李四都前往文件架取各自需要的账本。假设张三刚好拿到了 A 的账本,而李四拿到了 B 的账本。张三在拿到 A 的账本后,开始等待 B 的账本(此时在李四那里),李四在拿到 B 的账本后,开始等待 A 的账本(此时在张三那里)。他们需要等待多长时间呢?答案是他们会无限期地等待。因为张三不会归还 A 的账本,李四也不会归还 B 的账本。 39 | 40 | ![转账业务中的死等](https://ngte-superbed.oss-cn-beijing.aliyuncs.com/uPic/4wMZRnMqHcL0.png) 41 | 42 | 死锁的一个比较专业的定义是:一组互相竞争资源的线程因互相等待,导致“永久”阻塞的现象。死锁是如何在转账代码中发生的呢?假设线程 T1 正在执行从账户 A 到账户 B 的转账操作,即执行 账户 A.transfer(账户 B);同时,线程 T2 正在执行从账户 B 到账户 A 的转账操作,即执行 账户 B.transfer(账户 A)。当 T1 和 T2 都完成第一步操作后,T1 已经锁定了账户 A(对于 T1,this 是账户 A),T2 已经锁定了账户 B(对于 T2,this 是账户 B)。接下来,在执行第二步操作时,T1 尝试锁定账户 B,但发现它已被 T2 锁定,因此 T1 进入等待状态;同样,T2 尝试锁定账户 A,但发现它已被 T1 锁定,于是 T2 也开始等待。这样,T1 和 T2 就会无限期地等待对方释放锁,这种情况就是所谓的死锁。 43 | 44 | ```java 45 | class Account { 46 | private int balance; 47 | // 转账 48 | void transfer(Account target, int amt){ 49 | // 锁定转出账户 50 | synchronized(this){ ① 51 | // 锁定转入账户 52 | synchronized(target){ ② 53 | if (this.balance > amt) { 54 | this.balance -= amt; 55 | target.balance += amt; 56 | } 57 | } 58 | } 59 | } 60 | } 61 | ``` 62 | 63 | 资源分配图是一个有向图,它能够展示资源与线程之间的状态关系。在这个图中,资源被表示为方形节点,而线程则用圆形节点表示;如果存在一条从资源节点指向线程节点的边,则意味着线程已经持有该资源;反之,如果边是从线程节点指向资源节点,则表示线程正在请求资源,但还未获得。当转账操作导致死锁时,资源分配图将呈现出一种特定的结构,形象地描绘了一种“各自为营,相互等待”的局面。 64 | -------------------------------------------------------------------------------- /02~线程安全/线程通信/线程协作工具/Exchanger.md: -------------------------------------------------------------------------------- 1 | # Exchanger 2 | 3 | ```java 4 | public class ExchangeTest { 5 | public static void main(String[] args) { 6 | ExecutorService service = Executors.newCachedThreadPool(); 7 | final Exchanger exchanger = new Exchanger(); 8 | 9 | service.execute(new Runnable() { 10 | @Override 11 | public void run() { 12 | try { 13 | String data1 = "零食"; 14 | System.out.println("线程" + Thread.currentThread().getName() + 15 | "正在把数据 " + data1 + " 换出去"); 16 | Thread.sleep((long) Math.random() * 10000); 17 | String data2 = (String) exchanger.exchange(data1); 18 | System.out.println("线程 " + Thread.currentThread().getName() + 19 | "换回的数据为 " + data2); 20 | } catch (Exception e) { 21 | e.printStackTrace(); 22 | } 23 | } 24 | }); 25 | 26 | service.execute(new Runnable() { 27 | @Override 28 | public void run() { 29 | try { 30 | String data1 = "钱"; 31 | System.out.println("线程" + Thread.currentThread().getName() + 32 | "正在把数据 " + data1 + " 交换出去"); 33 | Thread.sleep((long)(Math.random() * 10000)); 34 | String data2 = (String) exchanger.exchange(data1); 35 | System.out.println("线程 " + Thread.currentThread().getName() + 36 | "交换回来的数据是: " + data2); 37 | } catch (Exception e) { 38 | e.printStackTrace(); 39 | } 40 | } 41 | }); 42 | } 43 | } 44 | ``` 45 | 46 | 最后结果为: 47 | 48 | ``` 49 | 线程pool-1-thread-1正在把数据 零食 换出去 50 | 线程pool-1-thread-2正在把数据 钱 交换出去 51 | 线程 pool-1-thread-1换回的数据为 钱 52 | 线程 pool-1-thread-2交换回来的数据是: 零食 53 | ``` 54 | 55 | # Asynchronous(异步) 56 | 57 | ## Timeouts 58 | 59 | 任何 future.get()调用都会阻塞,然后等待直到 callable 中止。在最糟糕的情况下,一个 callable 持续运行——因此使你的程序将没有响应。我们可以简单的传入一个时长来避免这种情况。 60 | 61 | ```java 62 | ExecutorService executor = Executors.newFixedThreadPool(1); 63 | 64 | Future future = executor.submit(() -> { 65 | try { 66 | TimeUnit.SECONDS.sleep(2); 67 | return 123; 68 | } 69 | catch (InterruptedException e) { 70 | throw new IllegalStateException("task interrupted", e); 71 | } 72 | }); 73 | 74 | future.get(1, TimeUnit.SECONDS); 75 | ``` 76 | 77 | 运行上面的代码将会产生一个 TimeoutException: 78 | 79 | ```java 80 | Exception in thread "main" java.util.concurrent.TimeoutException 81 | at java.util.concurrent.FutureTask.get(FutureTask.java:205) 82 | ``` 83 | -------------------------------------------------------------------------------- /03~并发框架/Disq/codes/disq/src/test/java/net/intelie/disq/ObjectPoolTest.java: -------------------------------------------------------------------------------- 1 | package net.intelie.disq; 2 | 3 | import com.google.common.util.concurrent.Uninterruptibles; 4 | import org.junit.Test; 5 | 6 | import java.util.concurrent.TimeUnit; 7 | import java.util.concurrent.atomic.AtomicInteger; 8 | 9 | import static org.assertj.core.api.Assertions.assertThat; 10 | 11 | public class ObjectPoolTest { 12 | 13 | @Test 14 | public void crazyTest() throws InterruptedException { 15 | AtomicInteger counter = new AtomicInteger(1000); 16 | ObjectPool pool = new ObjectPool<>(x -> counter.incrementAndGet(), 0, 5); 17 | 18 | Thread t1 = startThread(pool); 19 | Thread t2 = startThread(pool); 20 | Thread t3 = startThread(pool); 21 | Thread t4 = startThread(pool); 22 | 23 | t1.join(); 24 | t2.join(); 25 | t3.join(); 26 | t4.join(); 27 | 28 | int last = counter.get(); 29 | assertThat(last).isLessThan(1100); 30 | 31 | forceCollectSoftReferences(); 32 | 33 | try (ObjectPool.Ref ref = pool.acquire()) { 34 | assertThat(ref.obj()).isEqualTo(last + 1); 35 | } 36 | 37 | } 38 | 39 | @Test 40 | public void crazyTestMinPool() throws InterruptedException { 41 | AtomicInteger counter = new AtomicInteger(1000); 42 | ObjectPool pool = new ObjectPool<>(x -> counter.incrementAndGet()); 43 | 44 | Thread t1 = startThread(pool); 45 | Thread t2 = startThread(pool); 46 | Thread t3 = startThread(pool); 47 | Thread t4 = startThread(pool); 48 | 49 | t1.join(); 50 | t2.join(); 51 | t3.join(); 52 | t4.join(); 53 | 54 | int last = counter.get(); 55 | assertThat(last).isLessThan(1100); 56 | 57 | forceCollectSoftReferences(); 58 | 59 | try (ObjectPool.Ref ref = pool.acquire()) { 60 | assertThat(ref.obj()).isEqualTo(1001); 61 | } 62 | 63 | } 64 | 65 | private void forceCollectSoftReferences() { 66 | try { 67 | int max = (int) Math.min(Runtime.getRuntime().maxMemory() / 8, Integer.MAX_VALUE); 68 | long[] ignored = new long[max]; 69 | } catch (OutOfMemoryError e) { 70 | // Ignore 71 | } 72 | } 73 | 74 | Thread startThread(ObjectPool pool) { 75 | Thread thread = new Thread(() -> { 76 | for (int i = 0; i < 100; i++) { 77 | try (ObjectPool.Ref ref = pool.acquire()) { 78 | Uninterruptibles.sleepUninterruptibly(1, TimeUnit.MILLISECONDS); 79 | } 80 | } 81 | }); 82 | thread.start(); 83 | return thread; 84 | } 85 | } -------------------------------------------------------------------------------- /03~并发框架/Akka/邮箱与路由.md: -------------------------------------------------------------------------------- 1 | # 邮箱与路由 2 | 3 | ## Mailbox 4 | 5 | 整个 akka 的 Actor 系统是通过消息进行传递的,其实还可以使用 Inbox 消息收件箱来给某个 actor 发消息,并且可以进行交互。 6 | 7 | ```java 8 | // 创建 Inbox 收件箱 9 | Inbox inbox = Inbox.create(system); 10 | // 监听一个 actor 11 | inbox.watch(inboxTest); 12 | 13 | //通过inbox来发送消息 14 | inbox.send(inboxTest, Msg.WORKING); 15 | inbox.send(inboxTest, Msg.DONE); 16 | inbox.send(inboxTest, Msg.CLOSE); 17 | ``` 18 | 19 | 然后在 Inbox 中可以循环等待消息回复: 20 | 21 | ```java 22 | while(true){ 23 | try { 24 | Object receive = inbox.receive(Duration.create(1, TimeUnit.SECONDS)); 25 | if(receive == Msg.CLOSE){//收到的inbox的消息 26 | System.out.println("inboxTextActor is closing"); 27 | }else if(receive instanceof Terminated){//中断,和线程一个概念 28 | System.out.println("inboxTextActor is closed"); 29 | system.terminate(); 30 | break; 31 | }else { 32 | System.out.println(receive); 33 | } 34 | } catch (TimeoutException e) { 35 | e.printStackTrace(); 36 | } 37 | } 38 | ``` 39 | 40 | ## 路由与投递 41 | 42 | 通常在分布式任务调度系统中会有这样的需求:一组 actor 提供相同的服务,我们在调用任务的时候只需要选择其中一个 actor 进行处理即可。其实这就是一个负载均衡或者说路由策略,akka 作为一个高性能支持并发的 actor 模型,可以用来作为任务调度集群使用,当然负载均衡就是其本职工作了,akka 提供了 Router 来进行消息的调度。 43 | 44 | ```java 45 | // RouterActor,用于分发消息的 Actor 46 | // 创建多个子 Actor 47 | ArrayList routees = new ArrayList<>(); 48 | 49 | for(int i = 0; i < 5; i ++) { 50 | //借用上面的 inboxActor 51 | ActorRef worker = getContext().actorOf(Props.create(InboxTest.class), "worker_" + i); 52 | getContext().watch(worker);//监听 53 | routees.add(new ActorRefRoutee(worker)); 54 | } 55 | 56 | /** 57 | * 创建路由对象 58 | * RoundRobinRoutingLogic: 轮询 59 | * BroadcastRoutingLogic: 广播 60 | * RandomRoutingLogic: 随机 61 | * SmallestMailboxRoutingLogic: 空闲 62 | */ 63 | router = new Router(new RoundRobinRoutingLogic(), routees); 64 | 65 | @Override 66 | public void onReceive(Object o) throws Throwable { 67 | if(o instanceof InboxTest.Msg){ 68 | // 进行路由转发 69 | router.route(o, getSender()); 70 | }else if(o instanceof Terminated){ 71 | // 发生中断,将该actor删除 72 | router = router.removeRoutee(((Terminated)o).actor()); 73 | System.out.println(((Terminated)o).actor().path() + " 该actor已经删除。router.size=" + router.routees().size()); 74 | 75 | // 没有可用 actor 了 76 | if(router.routees().size() == 0){ 77 | System.out.print("没有可用actor了,系统关闭。"); 78 | flag.compareAndSet(true, false); 79 | getContext().system().shutdown(); 80 | } 81 | }else { 82 | unhandled(o); 83 | } 84 | } 85 | 86 | // 外部系统照常发送消息 87 | ActorRef routerActor = system.actorOf(Props.create(RouterActor.class), "RouterActor"); 88 | routerActor.tell(Msg.WORKING, ActorRef.noSender()); 89 | ``` 90 | -------------------------------------------------------------------------------- /03~并发框架/Disq/codes/disq/src/test/java/net/intelie/disq/DiskQueueReaderTest.java: -------------------------------------------------------------------------------- 1 | package net.intelie.disq; 2 | 3 | import com.google.common.base.Strings; 4 | import org.junit.Rule; 5 | import org.junit.Test; 6 | import org.junit.rules.TemporaryFolder; 7 | 8 | import java.io.IOException; 9 | import java.nio.charset.StandardCharsets; 10 | import java.nio.file.Path; 11 | 12 | import static org.assertj.core.api.Assertions.assertThat; 13 | 14 | public class DiskQueueReaderTest { 15 | @Rule 16 | public TemporaryFolder temp = new TemporaryFolder(); 17 | 18 | @Test 19 | public void canRead() throws IOException { 20 | String s = Strings.repeat("a", 512); 21 | 22 | Path path = temp.getRoot().toPath(); 23 | try (DiskRawQueue queue = new DiskRawQueue(path, 512)) { 24 | for (int i = 0; i < 5; i++) 25 | push(queue, s); 26 | } 27 | 28 | try (DiskQueueReader reader = new DiskQueueReader(path)) { 29 | assertThat(reader.count()).isEqualTo(5); 30 | assertThat(reader.bytes()).isEqualTo(5 * (512 + 4)); 31 | 32 | Buffer buffer = new Buffer(); 33 | while (reader.moveNext(buffer)) { 34 | assertBuffer(buffer, s); 35 | } 36 | } 37 | } 38 | 39 | private void assertBuffer(Buffer buffer, String s) { 40 | assertThat(new String(buffer.buf(), 0, buffer.count(), StandardCharsets.UTF_8)).isEqualTo(s); 41 | } 42 | 43 | @Test 44 | public void canReadTwice() throws IOException { 45 | String s1 = Strings.repeat("a", 512); 46 | String s2 = Strings.repeat("b", 512); 47 | 48 | Path path = temp.getRoot().toPath(); 49 | try (DiskRawQueue queue = new DiskRawQueue(path, 512)) { 50 | push(queue, s1); 51 | push(queue, s2); 52 | } 53 | 54 | try (DiskQueueReader reader = new DiskQueueReader(path)) { 55 | Buffer buffer = new Buffer(); 56 | reader.moveNext(buffer); 57 | assertBuffer(buffer, s1); 58 | } 59 | 60 | try (DiskQueueReader reader = new DiskQueueReader(path)) { 61 | Buffer buffer = new Buffer(); 62 | reader.moveNext(buffer); 63 | assertBuffer(buffer, s1); 64 | reader.moveNext(buffer); 65 | assertBuffer(buffer, s2); 66 | } 67 | } 68 | 69 | private void push(DiskRawQueue queue, String s) throws IOException { 70 | queue.push(new Buffer(s.getBytes(StandardCharsets.UTF_8))); 71 | } 72 | 73 | private String pop(DiskRawQueue queue) throws IOException { 74 | Buffer buffer = new Buffer(); 75 | if (!queue.pop(buffer)) return null; 76 | return new String(buffer.buf(), 0, buffer.count(), StandardCharsets.UTF_8); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /03~并发框架/Quartz/codes/examples/src/main/java/org/quartz/examples/example10/SimpleJob.java: -------------------------------------------------------------------------------- 1 | /* 2 | * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | * 16 | */ 17 | 18 | package org.quartz.examples.example10; 19 | 20 | import java.util.Date; 21 | import java.util.Set; 22 | 23 | import org.quartz.Job; 24 | import org.quartz.JobExecutionContext; 25 | import org.quartz.JobExecutionException; 26 | import org.quartz.JobKey; 27 | import org.slf4j.Logger; 28 | import org.slf4j.LoggerFactory; 29 | 30 | /** 31 | *

32 | * This is just a simple job that gets fired off many times by example 1 33 | *

34 | * 35 | * @author Bill Kratzer 36 | */ 37 | public class SimpleJob implements Job { 38 | 39 | private static Logger _log = LoggerFactory.getLogger(SimpleJob.class); 40 | 41 | /** 42 | * Empty constructor for job initilization 43 | */ 44 | public SimpleJob() { 45 | } 46 | 47 | /** 48 | *

49 | * Called by the {@link org.quartz.Scheduler} when a 50 | * {@link org.quartz.Trigger} fires that is associated with 51 | * the Job. 52 | *

53 | * 54 | * @throws JobExecutionException 55 | * if there is an exception while executing the job. 56 | */ 57 | @SuppressWarnings("unchecked") 58 | public void execute(JobExecutionContext context) 59 | throws JobExecutionException { 60 | 61 | // This job simply prints out its job name and the 62 | // date and time that it is running 63 | JobKey jobKey = context.getJobDetail().getKey(); 64 | _log.info("Executing job: " + jobKey + " executing at " + new Date() + ", fired by: " + context.getTrigger().getKey()); 65 | 66 | if(context.getMergedJobDataMap().size() > 0) { 67 | Set keys = context.getMergedJobDataMap().keySet(); 68 | for(String key: keys) { 69 | String val = context.getMergedJobDataMap().getString(key); 70 | _log.info(" - jobDataMap entry: " + key + " = " + val); 71 | } 72 | } 73 | 74 | context.setResult("hello"); 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /03~并发框架/Disq/codes/disq/src/main/java/net/intelie/disq/SerializerPool.java: -------------------------------------------------------------------------------- 1 | package net.intelie.disq; 2 | 3 | import java.io.Closeable; 4 | import java.io.IOException; 5 | import java.util.concurrent.TimeUnit; 6 | 7 | public class SerializerPool { 8 | private final ObjectPool pool; 9 | private final SerializerFactory factory; 10 | private final int initialBufferSize; 11 | private final int maxBufferSize; 12 | 13 | public SerializerPool(SerializerFactory factory, int initialBufferSize, int maxBufferSize) { 14 | this.factory = factory; 15 | this.initialBufferSize = initialBufferSize; 16 | this.maxBufferSize = maxBufferSize; 17 | this.pool = new ObjectPool<>(Slot::new, 0, 5); 18 | } 19 | 20 | public Slot acquire() { 21 | return pool.acquire().obj(); 22 | } 23 | 24 | public class Slot implements Closeable { 25 | private final ObjectPool.Ref ref; 26 | private final Serializer serializer; 27 | private final Buffer buffer; 28 | 29 | public Slot(ObjectPool.Ref ref) { 30 | this.ref = ref; 31 | this.serializer = factory.create(); 32 | this.buffer = new Buffer(initialBufferSize, maxBufferSize); 33 | } 34 | 35 | @Override 36 | public void close() { 37 | buffer.clear(); 38 | serializer.clear(); 39 | this.ref.close(); 40 | } 41 | 42 | public void push(InternalQueue queue, T obj) throws IOException { 43 | buffer.clear(); 44 | serializer.serialize(buffer, obj); 45 | queue.push(buffer); 46 | } 47 | 48 | public T pop(InternalQueue queue) throws IOException { 49 | buffer.clear(); 50 | if (!queue.pop(buffer)) 51 | return null; 52 | return serializer.deserialize(buffer); 53 | } 54 | 55 | public T peek(InternalQueue queue) throws IOException { 56 | buffer.clear(); 57 | if (!queue.peek(buffer)) 58 | return null; 59 | return serializer.deserialize(buffer); 60 | } 61 | 62 | public T blockingPop(InternalQueue queue) throws InterruptedException, IOException { 63 | buffer.clear(); 64 | queue.blockingPop(buffer); 65 | return serializer.deserialize(buffer); 66 | } 67 | 68 | public T blockingPop(InternalQueue queue, long amount, TimeUnit unit) throws InterruptedException, IOException { 69 | buffer.clear(); 70 | if (!queue.blockingPop(buffer, amount, unit)) 71 | return null; 72 | return serializer.deserialize(buffer); 73 | } 74 | 75 | public Buffer buffer() { 76 | return buffer; 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /03~并发框架/Disq/codes/disq/src/test/java/net/intelie/disq/DefaultSerializerTest.java: -------------------------------------------------------------------------------- 1 | package net.intelie.disq; 2 | 3 | import org.junit.Test; 4 | 5 | import java.io.ByteArrayOutputStream; 6 | import java.io.IOException; 7 | import java.util.Base64; 8 | 9 | import static org.assertj.core.api.Assertions.assertThat; 10 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 11 | 12 | public class DefaultSerializerTest { 13 | @Test 14 | public void willReadAndWrite() throws Exception { 15 | DefaultSerializer serializer = new DefaultSerializer<>(); 16 | 17 | Buffer buffer = new Buffer(); 18 | serializer.serialize(buffer, "test"); 19 | assertThat(buffer.count()).isEqualTo(11); 20 | 21 | assertThat(serializer.deserialize(buffer)).isEqualTo("test"); 22 | } 23 | 24 | @Test 25 | public void willReadAndWriteBig() throws Exception { 26 | String s = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus ac magna accumsan, tempus lorem sit amet, consequat diam. Sed placerat sagittis neque. Suspendisse sit amet pulvinar nulla. Fusce in ligula in ante auctor gravida eget vitae libero. Suspendisse eu gravida justo. Nam imperdiet, lacus ac euismod aliquam, augue ligula consequat felis, et sagittis eros augue a orci. Nulla odio neque, dictum ornare euismod ut, faucibus ac augue. Nullam justo justo, aliquam in quam non, tincidunt tincidunt libero. In suscipit sapien eu tortor dapibus, in laoreet leo vestibulum. Sed malesuada ante metus, sed imperdiet nisl rutrum non. Pellentesque elementum facilisis quam, at imperdiet diam viverra eget. Donec pharetra lobortis elementum. Vivamus lobortis tortor nec posuere ornare."; 27 | 28 | DefaultSerializer serializer = new DefaultSerializer<>(); 29 | 30 | Buffer buffer = new Buffer(); 31 | serializer.serialize(buffer, s); 32 | 33 | assertThat(serializer.deserialize(buffer)).isEqualTo(s); 34 | } 35 | 36 | @Test 37 | public void willFailIfClassNotFound() throws Exception { 38 | 39 | 40 | DefaultSerializer serializer = new DefaultSerializer<>(); 41 | 42 | byte[] bytes = Base64.getDecoder().decode("rO0ABXNyAC9uZXQuaW50ZWxpZS5kaXNxLkRlZmF1bHRTZXJpYWxpemVyVGVzdCRXaGF0ZXZlcnB4QEf+xdUJAgAAeHA="); 43 | 44 | // ByteArrayOutputStream baos = new ByteArrayOutputStream(); 45 | // serializer.serialize(baos, new Whatever()); 46 | // System.out.println(Base64.getEncoder().encodeToString(baos.toByteArray())); 47 | 48 | Buffer buffer = new Buffer(bytes); 49 | assertThatThrownBy(() -> serializer.deserialize(buffer)) 50 | .isInstanceOf(IOException.class) 51 | .hasMessageContaining("DefaultSerializerTest$Whatever") 52 | .hasCauseInstanceOf(ClassNotFoundException.class); 53 | 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /03~并发框架/RxJava/工程实践/应用案例.md: -------------------------------------------------------------------------------- 1 | # 基于 RxJava 的响应式编程 2 | 3 | # Loading Data 4 | 5 | - [loading-data-from-multiple-sources-with-rxjava](http://blog.danlew.net/2015/06/22/loading-data-from-multiple-sources-with-rxjava/) 6 | 7 | 假设我需要从网络上获取一些数据。每次需要数据的时候,我都可以简单的访问网络,但是,将数据缓存到磁盘或内存则可以更有效率。 8 | 9 | 更明确的说,我希望是这样的: 10 | 1、偶尔的从网络上获取新数据。 11 | 2、然而可以尽快的恢复数据(通过缓存网络数据的结果)。 12 | 13 | ## 按优先级加载有效数据 14 | 15 | 给每一数据源(网络、磁盘和内存)一个`Observable`接口,我们可以通过两个操作:`concat()`和`first()`,来实现一个简单的解决方案。`concat()`持有多个`Observables`,并且把它们连接在队列里。`first()`仅从队列里中获取到第一个条目。因此,如果你使用`concat().first()`可以从多个数据源中获取到第一个。 16 | 17 | ``` 18 | // Our sources (left as an exercise for the reader) 19 | Observable memory = ...; 20 | Observable disk = ...; 21 | Observable network = ...; 22 | 23 | // Retrieve the first source with data 24 | Observable source = Observable 25 | .concat(memory, disk, network) 26 | .first(); 27 | ``` 28 | 29 | 这种模式的关键是 concat()只在需要资源的时候才会订阅每个子 Observable。如果数据被缓存,就不需要通过速度慢的数据源来获取数据。注意 concat()中 Observables 数据源的顺序问题,因为它们是被一个接一个检索出来的。在实际应用场景中,我们还需要在`first`中判断获取到的数据是否有效以及是否过期,只要进行简单的修正即可: 30 | 31 | ``` 32 | Observable source = Observable.concat( 33 | sources.memory(), 34 | sources.disk(), 35 | sources.network() 36 | ) 37 | .first(data -> data != null && data.isUpToDate()); 38 | ``` 39 | 40 | ## 自动保存数据 41 | 42 | 很显然,下一步就是保存数据源。如果,你没有将网络请求的结果保存到磁盘,将磁盘的地址保存在内存中,那就再也没法挽救啦!上面所有的代码就是让网咯请求持久化。我的解决方式是在每次发出请求的时候保存或缓存数据源: 43 | 44 | ``` 45 | Observable networkWithSave = network.doOnNext(data -> { 46 | saveToDisk(data); 47 | cacheInMemory(data); 48 | }); 49 | 50 | Observable diskWithCache = disk.doOnNext(data -> { 51 | cacheInMemory(data); 52 | }); 53 | ``` 54 | 55 | 现在,如果你使用`networkWithSave`和`diskWithCache`,数据都将会在你下载的时候自动保存。(这种策略的另外一个好处就是`networkWithSave/diskWithCache`可以在任何地方使用,不仅仅在我们的多个数据源模式下。) 56 | 57 | ## 日志记录 58 | 59 | 有时候,我们还需要记录下每次请求的命中情况,譬如有时候我们需要去测试下缓存的命中率,可以用 compose 方法来实现: 60 | 61 | ``` 62 | // Save network responses to disk and cache in memory 63 | return observable.doOnNext(data -> { 64 | disk = data; 65 | memory = data; 66 | }) 67 | .compose(logSource("NETWORK")); 68 | ... 69 | // Simple logging to let us know what each source is returning 70 | Observable.Transformer logSource(final String source) { 71 | return dataObservable -> dataObservable.doOnNext(data -> { 72 | if (data == null) { 73 | System.out.println(source + " does not have any data."); 74 | } else if (!data.isUpToDate()) { 75 | System.out.println(source + " has stale data."); 76 | } else { 77 | System.out.println(source + " has the data you are looking for!"); 78 | } 79 | }); 80 | } 81 | ``` 82 | 83 | > 完整的测试代码可以参考[这里](https://github.com/wx-chevalier/WXJavaToolkits/blob/master/src%2Fmain%2Fjava%2Fwx%2Ftoolkits%2Fsysproc%2Fconcurrence%2Frxjava%2Fpractice%2Fdataloading%2FSources.java) 84 | -------------------------------------------------------------------------------- /03~并发框架/RxJava/基础使用/Completable.md: -------------------------------------------------------------------------------- 1 | # Completable 2 | 3 | Completable 与 Observable 相似,唯一的例外是前者发出完成信号或错误信号,但不发出任何实际对象。Completable 类包含几种便捷的方法,可用于从不同的反应式源数据中创建或获取它。 4 | 5 | # 创建与控制 6 | 7 | ```java 8 | Completable.fromRunnable(() -> {}); 9 | 10 | // 自定义 create 方法 11 | @Override 12 | public Completable storeUserDbos(int accountId, List users) { 13 | return Completable.create(emitter -> { 14 | ArrayList operations = new ArrayList<>(users.size()); 15 | appendUsersInsertOperation(operations, accountId, users); 16 | getContentResolver().applyBatch(MessengerContentProvider.AUTHORITY, operations); 17 | emitter.onComplete(); 18 | }); 19 | } 20 | 21 | // 从其他类型中创建 22 | Flowable flowable = Flowable 23 | .just("request received", "user logged in"); 24 | Completable flowableCompletable = Completable 25 | .fromPublisher(flowable); 26 | Completable singleCompletable = Single.just(1) 27 | .ignoreElement(); 28 | ``` 29 | 30 | 我们也可以使用 Completable.complete() 来立即结束当前的 Completable: 31 | 32 | ```java 33 | Completable 34 | .complete() 35 | .subscribe(new DisposableCompletableObserver() { 36 | @Override 37 | public void onComplete() { 38 | System.out.println("Completed!"); 39 | } 40 | 41 | @Override 42 | public void onError(Throwable e) { 43 | e.printStackTrace(); 44 | } 45 | }); 46 | ``` 47 | 48 | # 链式调用 49 | 50 | 当我们只在乎操作的成功时,我们可以在许多实际用例中采用 Completables 链: 51 | 52 | - 全做或者不做,例如执行 PUT 请求以更新远程对象,然后在成功后更新本地数据库 53 | 54 | - 事后记录和日记 55 | 56 | - 编排几个动作,例如 提取动作完成后运行分析作业 57 | 58 | ```java 59 | Completable first = Completable 60 | .fromSingle(Single.just(1)); 61 | 62 | Completable second = Completable 63 | .fromRunnable(() -> {}); 64 | 65 | Throwable throwable = new RuntimeException(); 66 | 67 | Completable error = Single.error(throwable) 68 | .ignoreElement(); 69 | 70 | first 71 | .andThen(second) 72 | .test() 73 | .assertComplete(); 74 | ``` 75 | 76 | 我们可以根据需要链接多个 Completables。同时,如果至少一个源未能完成,则结果 Completable 也将不会触发 `onComplete()`: 77 | 78 | ```java 79 | first 80 | .andThen(second) 81 | .andThen(error) 82 | .test() 83 | .assertError(throwable); 84 | ``` 85 | 86 | 此外,如果源之一是无限的或由于某种原因未达到 onComplete,则生成的 Completable 将永远不会触发 onComplete()或 onError()。 87 | 88 | ## 数组调用 89 | 90 | ```java 91 | Completable.mergeArray(first, second) 92 | .test() 93 | .assertComplete(); 94 | 95 | Completable.mergeArray(first, second, error) 96 | .test() 97 | .assertError(throwable); 98 | 99 | // 将 Flowable 转化为 Completable 100 | Completable allElementsCompletable = Flowable 101 | .just("request received", "user logged in") 102 | .flatMapCompletable(message -> Completable 103 | .fromRunnable(() -> System.out.println(message)) 104 | ); 105 | 106 | allElementsCompletable 107 | .test() 108 | .assertComplete(); 109 | ``` 110 | -------------------------------------------------------------------------------- /03~并发框架/Disq/codes/disq/src/test/java/net/intelie/disq/dson/DsonSerializerTest.java: -------------------------------------------------------------------------------- 1 | package net.intelie.disq.dson; 2 | 3 | import com.google.gson.Gson; 4 | import net.intelie.disq.Buffer; 5 | import org.junit.Test; 6 | 7 | import java.io.IOException; 8 | import java.util.Arrays; 9 | import java.util.Collections; 10 | import java.util.LinkedHashMap; 11 | import java.util.Map; 12 | 13 | import static org.assertj.core.api.Assertions.assertThat; 14 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 15 | 16 | public class DsonSerializerTest { 17 | @Test 18 | public void testFromGson() { 19 | String str = "{\"index_timestamp\":1.56294378011E12,\"wellbore_name\":\"1\",\"adjusted_index_timestamp\":1.562943817363E12,\"source\":\"WITS\",\"depth_value\":6717.527,\"uom\":\"unitless\",\"extra\":\"RBNvo1WzZ4o\",\"mnemonic\":\"STKNUM\",\"well_name\":\"MP72 - A11 ST\",\"depth_mnemonic\":\"DEPTMEAS\",\"value\":0.0,\"errors\":[\"missing_src_unit\",\"unknown_src_unit\"],\"timestamp\":1.562943818361E12,\"__type\":\"ensco75\",\"__src\":\"replay/rig11_b\"}"; 20 | Map map = new Gson().fromJson( 21 | str, 22 | Map.class); 23 | 24 | Buffer buffer = new Buffer(); 25 | DsonSerializer.Instance serializer = new DsonSerializer().create(); 26 | serializer.serialize(buffer, map); 27 | 28 | assertThat(buffer.count()).isLessThan(str.length()); 29 | } 30 | 31 | @Test 32 | public void testDeserializeInvalidStream() throws IOException { 33 | Buffer buffer = new Buffer(); 34 | buffer.write().write(254); 35 | 36 | DsonSerializer.Instance serializer = new DsonSerializer().create(); 37 | assertThatThrownBy(() -> serializer.deserialize(buffer)) 38 | .isInstanceOf(IllegalStateException.class) 39 | .hasMessage("unknown DSON type"); 40 | 41 | } 42 | 43 | @Test 44 | public void testSerialize() throws IOException { 45 | Map map = new LinkedHashMap<>(); 46 | map.put(111, "aaa"); 47 | map.put("âçãó", true); 48 | map.put("ccc", null); 49 | map.put(Arrays.asList("ddd", "eee"), Arrays.asList( 50 | Collections.singletonMap(222.0, false), 51 | Collections.singletonMap("fff", new Error("(╯°□°)╯︵ ┻━┻")) 52 | )); 53 | 54 | DsonSerializer.Instance serializer = new DsonSerializer().create(); 55 | Buffer buffer = new Buffer(); 56 | 57 | serializer.serialize(buffer, map); 58 | 59 | Map expected = new LinkedHashMap<>(); 60 | expected.put(111.0, "aaa"); 61 | expected.put("âçãó", true); 62 | expected.put("ccc", null); 63 | expected.put(Arrays.asList("ddd", "eee"), Arrays.asList( 64 | Collections.singletonMap(222.0, false), 65 | Collections.singletonMap("fff", "java.lang.Error: (╯°□°)╯︵ ┻━┻") 66 | )); 67 | 68 | assertThat(serializer.deserialize(buffer)).isEqualTo(expected); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /03~并发框架/Quartz/codes/examples/src/main/java/org/quartz/examples/example13/SimpleRecoveryJob.java: -------------------------------------------------------------------------------- 1 | /* 2 | * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | * 16 | */ 17 | 18 | package org.quartz.examples.example13; 19 | 20 | import org.quartz.Job; 21 | import org.quartz.JobDataMap; 22 | import org.quartz.JobExecutionContext; 23 | import org.quartz.JobExecutionException; 24 | import org.quartz.JobKey; 25 | import org.slf4j.Logger; 26 | import org.slf4j.LoggerFactory; 27 | 28 | import java.util.Date; 29 | 30 | /** 31 | *

32 | * A dumb implementation of Job, for unit testing purposes. 33 | *

34 | * 35 | * @author James House 36 | */ 37 | public class SimpleRecoveryJob implements Job { 38 | 39 | private static Logger _log = LoggerFactory.getLogger(SimpleRecoveryJob.class); 40 | 41 | private static final String COUNT = "count"; 42 | 43 | /** 44 | * Quartz requires a public empty constructor so that the scheduler can instantiate the class whenever it needs. 45 | */ 46 | public SimpleRecoveryJob() { 47 | } 48 | 49 | /** 50 | *

51 | * Called by the {@link org.quartz.Scheduler} when a {@link org.quartz.Trigger} fires that 52 | * is associated with the Job. 53 | *

54 | * 55 | * @throws JobExecutionException if there is an exception while executing the job. 56 | */ 57 | public void execute(JobExecutionContext context) throws JobExecutionException { 58 | 59 | JobKey jobKey = context.getJobDetail().getKey(); 60 | 61 | // if the job is recovering print a message 62 | if (context.isRecovering()) { 63 | _log.info("SimpleRecoveryJob: " + jobKey + " RECOVERING at " + new Date()); 64 | } else { 65 | _log.info("SimpleRecoveryJob: " + jobKey + " starting at " + new Date()); 66 | } 67 | 68 | // delay for ten seconds 69 | long delay = 10L * 1000L; 70 | try { 71 | Thread.sleep(delay); 72 | } catch (Exception e) { 73 | // 74 | } 75 | 76 | JobDataMap data = context.getJobDetail().getJobDataMap(); 77 | int count; 78 | if (data.containsKey(COUNT)) { 79 | count = data.getInt(COUNT); 80 | } else { 81 | count = 0; 82 | } 83 | count++; 84 | data.put(COUNT, count); 85 | 86 | _log.info("SimpleRecoveryJob: " + jobKey + " done at " + new Date() + "\n Execution #" + count); 87 | 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /02~线程安全/锁/synchronized/实现原理.md: -------------------------------------------------------------------------------- 1 | # 实现原理 2 | 3 | ## Monitor 4 | 5 | synchronized 关键字依赖于内部的 intrinsic lock 或者所谓的 monitor lock。每个对象都有一个与之关联的固有锁。按照惯例,线程必须在访问对象之前获取对象的监视器锁,然后在完成对它们的锁定后释放该监视器锁。据说线程在获得锁和释放锁之间拥有该锁。只要一个线程拥有监视器锁,其他任何线程都无法获得相同的锁。另一个线程在尝试获取锁时将阻塞。当线程释放锁时,将在该动作与任何随后的相同锁获取之间建立 happens-before 关系。 6 | 7 | ```java 8 | public class SynchronizedDemo { 9 | 10 | // 同步方法 11 | public synchronized void syncMethod() { 12 | System.out.println("Hello World"); 13 | } 14 | 15 | // 同步代码块 16 | public void syncBlock() { 17 | synchronized (this) { 18 | System.out.println("Hello World"); 19 | } 20 | } 21 | } 22 | ``` 23 | 24 | 以上的示例代码,编译后的字节码为: 25 | 26 | ```java 27 | public synchronized void syncMethod(); 28 | descriptor: ()V 29 | flags: ACC_PUBLIC, ACC_SYNCHRONIZED 30 | Code: 31 | stack=2, locals=1, args_size=1 32 | 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 33 | 3: ldc #3 // String Hello World 34 | 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 35 | 8: return 36 | 37 | public void syncBlock(); 38 | descriptor: ()V 39 | flags: ACC_PUBLIC 40 | Code: 41 | stack=2, locals=3, args_size=1 42 | 0: ldc #5 // class com/hollis/SynchronizedTest 43 | 2: dup 44 | 3: astore_1 45 | 4: monitorenter 46 | 5: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 47 | 8: ldc #3 // String Hello World 48 | 10: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 49 | 13: aload_1 50 | 14: monitorexit 51 | 15: goto 23 52 | 18: astore_2 53 | 19: aload_1 54 | 20: monitorexit 55 | 21: aload_2 56 | 22: athrow 57 | 23: return 58 | ``` 59 | 60 | 从 JVM 规范中可以看到 synchronized 在 JVM 里的实现原理,JVM 基于进入和退出 Monitor 对象来实现方法同步和代码块同步,但两者的实现细节不一样。代码块同步是使用 monitorenter 和 monitorexit 指令实现的,而方法同步是使用另外一种方式实现的,细节在 JVM 规范里并没有详细说明。但是,方法的同步同样可以使用这两个指令来实现。 61 | 62 | monitorenter 指令是在编译后插入到同步代码块的开始位置,而 monitorexit 是插入到方法结束处和异常处,JVM 要保证每个 monitorenter 必须有对应的 monitorexit 与之配对。任何对象都有一个 monitor 与之关联,当且一个 monitor 被持有后,它将处于锁定状态。线程执行到 monitorenter 指令时,将会尝试获取对象所对应的 monitor 的所有权,即尝试获得对象的锁。 63 | 64 | ## Java 对象头 65 | 66 | synchronized 用的锁是存在 Java 对象头里的。如果对象是数组类型,则虚拟机用 3 个字宽(Word)存储对象头,如果对象是非数组类型,则用 2 字宽存储对象头。在 32 位虚拟机中,1 字宽等于 4 字节,即 32bit,如下图所示: 67 | 68 | ![Java 对象头的长度](https://s3.ax1x.com/2021/01/29/yPKLCQ.png) 69 | 70 | Java 对象头里的 Mark Word 里默认存储对象的 HashCode、分代年龄和锁标记位。32 位 JVM 的 Mark Word 的默认存储结构如下表所示。 71 | 72 | ![Java 对象头的存储结构](https://s3.ax1x.com/2021/01/29/yPMSbV.png) 73 | 74 | 在运行期间,Mark Word 里存储的数据会随着锁标志位的变化而变化。Mark Word 可能变化为存储以下 4 种数据: 75 | 76 | ![Mark Word的状态变化](https://s3.ax1x.com/2021/01/29/yPMEvR.png) 77 | 78 | 在 64 位虚拟机下,Mark Word 是 64bit 大小的,其存储结构如下所示: 79 | 80 | ![Mark Word的存储结构](https://s3.ax1x.com/2021/01/29/yPQJW4.png) 81 | -------------------------------------------------------------------------------- /04~并发网络 IO/网络编程/SPI/SPI 使用.md: -------------------------------------------------------------------------------- 1 | # SPI 使用 2 | 3 | Java SPI 的限定如下: 4 | 5 | - 当服务的提供者,提供了接口的一种具体实现后,在 jar 包的 META-INF/services/目录中创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名。 6 | 7 | - SPI 所在的 jar 放在主程序的 classpath 中 8 | 9 | - 外部程序通过 java.util.ServiceLoader 动态装载实现模块,它通过扫描 META-INF/services 目录下的配置文件找到实现类的全限定名,把类加载到 JVM。注意:SPI 的实现类必须带一个不带参数的构造方法 10 | 11 | 该示例主要为了展示如何使用 SPI,接口是数字操作接口,普通的 API 的实现类是加法操作;两个 SPI 实现类分别是减法操作和乘法操作。INumOperate 接口的代码如下: 12 | 13 | ```java 14 | package com.demo.api; 15 | 16 | /** 17 | * 数字操作接口 18 | * 19 | */ 20 | public interface INumOperate { 21 | public int operator(int a, int b); 22 | } 23 | ``` 24 | 25 | 普通的 API 实现,加法操作,代码如下: 26 | 27 | ```java 28 | package com.demo.api.impl; 29 | 30 | import com.demo.api.INumOperate; 31 | 32 | /** 33 | * 数字相加 34 | * 35 | */ 36 | public class NumPlusOperateImpl implements INumOperate { 37 | 38 | @Override 39 | public int operator(int a, int b) { 40 | int r = a + b; 41 | System.out.println("[实现类机制]加法,结果:" + r); 42 | return r; 43 | } 44 | } 45 | ``` 46 | 47 | 实现乘法的 SPI,在语法结构上和普通 api 实现一模一样,如下: 48 | 49 | ```java 50 | package com.demo.spi.impl; 51 | 52 | import com.demo.api.INumOperate; 53 | 54 | /** 55 | * 数字相乘 56 | * 57 | */ 58 | public class NumMutliOperateImpl implements INumOperate { 59 | 60 | @Override 61 | public int operator(int a, int b) { 62 | int r = a * b; 63 | System.out.println("[SPI机制]乘法,结果:" + r); 64 | return r; 65 | } 66 | } 67 | 68 | package com.demo.spi.impl; 69 | 70 | import com.demo.api.INumOperate; 71 | 72 | /** 73 | * 数字相减 74 | * 75 | */ 76 | public class NumSubtractOperateImpl implements INumOperate { 77 | 78 | @Override 79 | public int operator(int a, int b) { 80 | int r = a - b; 81 | System.out.println("[SPI机制]减法,结果:" + r); 82 | return r; 83 | } 84 | 85 | } 86 | ``` 87 | 88 | 在 META-INFO/services 目录下(如果没有改目录,手工新建即可),新建一个以 com.demo.api.INumOperate 命名的文件,文件内容指明两个 SPI 的实现类的全限定名称,如下: 89 | 90 | ```java 91 | com.demo.spi.impl.NumMutliOperateImpl 92 | com.demo.spi.impl.NumSubtractOperateImpl 93 | ``` 94 | 95 | main 函数如下,主程序中没有显示指明 SPI 的实现,而是通过 ServiceLoader 动态加载实现类: 96 | 97 | ```java 98 | package com.demo; 99 | 100 | import com.demo.api.impl.NumPlusOperateImpl; 101 | import com.demo.api.INumOperate; 102 | import java.util.Iterator; 103 | import java.util.ServiceLoader; 104 | 105 | /** 106 | * 主程序 107 | * 108 | */ 109 | public class Main { 110 | 111 | public static void main(String[] args) { 112 | int a = 9; 113 | int b = 3; 114 | 115 | // 普通的实现类机制,加法 116 | INumOperate plus = new NumPlusOperateImpl(); 117 | plus.operator(a, b); 118 | 119 | // SPI机制,寻找所有的实现类,顺序执行 120 | ServiceLoader loader = ServiceLoader.load(INumOperate.class); // 查找SPI实现类,并加载到jvm 121 | Iterator iter = loader.iterator(); 122 | while (iter.hasNext()) { 123 | INumOperate op = iter.next(); 124 | op.operator(a, b); 125 | } 126 | } 127 | } 128 | ``` 129 | -------------------------------------------------------------------------------- /01~线程与线程池/01~线程基础/线程概念/生命周期.md: -------------------------------------------------------------------------------- 1 | # Java 线程的生命周期 2 | 3 | # 线程状态 4 | 5 | 线程的状态:NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED。注释也解释得很清楚各个状态的作用,而各个状态的转换也有一定的规则需要遵循的。 6 | 7 | ![](https://s2.ax1x.com/2019/09/02/nCyjPA.png) 8 | 9 | ```java 10 | public enum State { 11 | /** 12 | * Thread state for a thread which has not yet started. 13 | */ 14 | NEW, 15 | /** 16 | * Thread state for a runnable thread. A thread in the runnable 17 | * state is executing in the Java virtual machine but it may 18 | * be waiting for other resources from the operating system 19 | * such as processor. 20 | */ 21 | RUNNABLE, 22 | /** 23 | * Thread state for a thread blocked waiting for a monitor lock. 24 | * A thread in the blocked state is waiting for a monitor lock 25 | * to enter a synchronized block/method or 26 | * reenter a synchronized block/method after calling 27 | * {@link Object#wait() Object.wait}. 28 | */ 29 | BLOCKED, 30 | /** 31 | * Thread state for a waiting thread. 32 | * A thread is in the waiting state due to calling one of the 33 | * following methods: 34 | *
    35 | *
  • {@link Object#wait() Object.wait} with no timeout
  • 36 | *
  • {@link #join() Thread.join} with no timeout
  • 37 | *
  • {@link LockSupport#park() LockSupport.park}
  • 38 | *
39 | * 40 | *

A thread in the waiting state is waiting for another thread to 41 | * perform a particular action. 42 | * 43 | * For example, a thread that has called Object.wait() 44 | * on an object is waiting for another thread to call 45 | * Object.notify() or Object.notifyAll() on 46 | * that object. A thread that has called Thread.join() 47 | * is waiting for a specified thread to terminate. 48 | */ 49 | WAITING, 50 | /** 51 | * Thread state for a waiting thread with a specified waiting time. 52 | * A thread is in the timed waiting state due to calling one of 53 | * the following methods with a specified positive waiting time: 54 | *

    55 | *
  • {@link #sleep Thread.sleep}
  • 56 | *
  • {@link Object#wait(long) Object.wait} with timeout
  • 57 | *
  • {@link #join(long) Thread.join} with timeout
  • 58 | *
  • {@link LockSupport#parkNanos LockSupport.parkNanos}
  • 59 | *
  • {@link LockSupport#parkUntil LockSupport.parkUntil}
  • 60 | *
61 | */ 62 | TIMED_WAITING, 63 | /** 64 | * Thread state for a terminated thread. 65 | * The thread has completed execution. 66 | */ 67 | TERMINATED 68 | } 69 | ``` 70 | 71 | # 线程优先级 72 | 73 | 线程优先级其实只是对线程打的一个标志,但并不意味这高优先级的一定比低优先级的先执行,具体还要看操作系统的资源调度情况。通常线程优先级为 5,边界为[1,10]。 74 | 75 | ```java 76 | /** 77 | * The minimum priority that a thread can have. 78 | */ 79 | public final static int MIN_PRIORITY = 1; 80 | 81 | /** 82 | * The default priority that is assigned to a thread. 83 | */ 84 | public final static int NORM_PRIORITY = 5; 85 | 86 | /** 87 | * The maximum priority that a thread can have. 88 | */ 89 | public final static int MAX_PRIORITY = 10; 90 | ``` 91 | -------------------------------------------------------------------------------- /03~并发框架/Quartz/codes/examples/src/main/java/org/quartz/examples/example10/PlugInExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | * 16 | */ 17 | 18 | package org.quartz.examples.example10; 19 | 20 | import org.quartz.Scheduler; 21 | import org.quartz.SchedulerException; 22 | import org.quartz.SchedulerFactory; 23 | import org.quartz.SchedulerMetaData; 24 | import org.quartz.impl.StdSchedulerFactory; 25 | import org.slf4j.Logger; 26 | import org.slf4j.LoggerFactory; 27 | 28 | /** 29 | * This example will spawn a large number of jobs to run 30 | * 31 | * @author James House, Bill Kratzer 32 | */ 33 | public class PlugInExample { 34 | 35 | public void run() throws Exception { 36 | Logger log = LoggerFactory.getLogger(PlugInExample.class); 37 | 38 | // First we must get a reference to a scheduler 39 | SchedulerFactory sf = new StdSchedulerFactory(); 40 | Scheduler sched = null; 41 | try { 42 | sched = sf.getScheduler(); 43 | } catch (NoClassDefFoundError e) { 44 | log.error(" Unable to load a class - most likely you do not have jta.jar on the classpath. If not present in the examples/lib folder, please " + 45 | "add it there for this sample to run.", e); 46 | return; 47 | } 48 | 49 | log.info("------- Initialization Complete -----------"); 50 | 51 | log.info("------- (Not directly Scheduling any Jobs - relying on XML definitions of jobs/triggers to be loaded by plugin --"); 52 | 53 | log.info("------- Starting Scheduler ----------------"); 54 | 55 | // start the schedule 56 | sched.start(); 57 | 58 | log.info("------- Started Scheduler -----------------"); 59 | 60 | log.info("------- Waiting five minutes... -----------"); 61 | 62 | // wait five minutes to give our jobs a chance to run 63 | try { 64 | Thread.sleep(300L * 1000L); 65 | } catch (Exception e) { 66 | // 67 | } 68 | 69 | // shut down the scheduler 70 | log.info("------- Shutting Down ---------------------"); 71 | sched.shutdown(true); 72 | log.info("------- Shutdown Complete -----------------"); 73 | 74 | SchedulerMetaData metaData = sched.getMetaData(); 75 | log.info("Executed " + metaData.getNumberOfJobsExecuted() + " jobs."); 76 | } 77 | 78 | public static void main(String[] args) throws Exception { 79 | 80 | PlugInExample example = new PlugInExample(); 81 | example.run(); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /03~并发框架/Quartz/codes/examples/src/main/java/org/quartz/examples/example6/BadJob2.java: -------------------------------------------------------------------------------- 1 | /* 2 | * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | * 16 | */ 17 | 18 | package org.quartz.examples.example6; 19 | 20 | import java.util.Date; 21 | 22 | import org.quartz.DisallowConcurrentExecution; 23 | import org.quartz.Job; 24 | import org.quartz.JobExecutionContext; 25 | import org.quartz.JobExecutionException; 26 | import org.quartz.JobKey; 27 | import org.quartz.PersistJobDataAfterExecution; 28 | import org.slf4j.Logger; 29 | import org.slf4j.LoggerFactory; 30 | 31 | /** 32 | *

33 | * A job dumb job that will throw a job execution exception 34 | *

35 | * 36 | * @author Bill Kratzer 37 | */ 38 | @PersistJobDataAfterExecution 39 | @DisallowConcurrentExecution 40 | public class BadJob2 implements Job { 41 | 42 | // Logging 43 | private static Logger _log = LoggerFactory.getLogger(BadJob2.class); 44 | private int calculation; 45 | 46 | /** 47 | * Empty public constructor for job initialization 48 | */ 49 | public BadJob2() { 50 | } 51 | 52 | /** 53 | *

54 | * Called by the {@link org.quartz.Scheduler} when a {@link org.quartz.Trigger} 55 | * fires that is associated with the Job. 56 | *

57 | * 58 | * @throws JobExecutionException 59 | * if there is an exception while executing the job. 60 | */ 61 | public void execute(JobExecutionContext context) 62 | throws JobExecutionException { 63 | JobKey jobKey = context.getJobDetail().getKey(); 64 | _log.info("---" + jobKey + " executing at " + new Date()); 65 | 66 | // a contrived example of an exception that 67 | // will be generated by this job due to a 68 | // divide by zero error 69 | try { 70 | int zero = 0; 71 | calculation = 4815 / zero; 72 | } catch (Exception e) { 73 | _log.info("--- Error in job!"); 74 | JobExecutionException e2 = 75 | new JobExecutionException(e); 76 | // Quartz will automatically unschedule 77 | // all triggers associated with this job 78 | // so that it does not run again 79 | e2.setUnscheduleAllTriggers(true); 80 | throw e2; 81 | } 82 | 83 | _log.info("---" + jobKey + " completed at " + new Date()); 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /04~并发网络 IO/网络编程/SPI/HSF SPI.md: -------------------------------------------------------------------------------- 1 | # HSF SPI 2 | 3 | HSF 用自己的方式重新实现了一套 SPI 机制,不使用 Java 原生的 SPI 机制。 4 | 5 | - @Name:用于标注一个实现,相当于给这个拓展实现起了个别名。 6 | - @Order:当一个接口类有多个拓展时,拓展的先后顺序能够通过该注解进行定义,例如,用于责任链的构造顺序。 7 | - @Scope:用来描述一个扩展是否多实例,默认为单例,如果使用多例,则会在线程请求时,才创建并存放于 ThreadLocal 当中。 8 | - @Tag:用来描述一个服务实现的标记,可以使用这个注解形容一个接口的若干服务扩展,例如,当我们需要加载具有某类 tag 的扩展类时,我们可以指定 tag 列表,当指定的扩展实现类含有指定的 tag 时,则说明该扩展类符合条件。 9 | - @Shared:用来描述一个服务接口类型,被标注的服务是一个共享服务,表示该服务的实例将放置在 Shared Container 中。 10 | - HSFServiceContainer:该类为 hsf 加载 spi 的一个门面容器类,调用方统一使用该类提供的方法加载扩展服务类。 11 | - AppServiceContainer:该类为 spi 的具体实现类,你可以把它称为应用服务加载器。 12 | - ApplicationModel:代表了一个应用实例,持有了应用类所对应的类加载器,而 AppServiceContainer 只要委托该类加载器加载拓展服务实例类即可。 13 | 14 | # HSFServiceContainer 15 | 16 | HSFServiceContainer 作为加载 SPI 的门面容器类,提供了一系列的方法来加载拓展服务类,以下是它的代码,注意观察几点: 17 | 18 | - SHARED_CONTAINER 作为共享的容器,是最顶层的容器,无需指定父类容器以及用户应用模型(而其他的 AppServiceContainer 需要); 19 | - 方法 createAppServiceContainer()决定了通过 HSFServiceContainer 创建的容器,他们的父容器一定是 SHARED_CONTAINER 20 | - 一系列重载的 getInstances(...)方法中调用了 AppServiceContainer 中的同名方法,而该方法中内含了委派加载的逻辑。 21 | 22 | ```java 23 | public class HSFServiceContainer { 24 | // 共享的容器,它不隶属与任何一个应用 25 | public static final AppServiceContainer SHARED_CONTAINER = new AppServiceContainer(); 26 | 27 | // 创建一个AppServiceContainer 28 | public static AppServiceContainer createAppServiceContainer( 29 | ApplicationModel applicationModel 30 | ) { 31 | return new AppServiceContainer(applicationModel, SHARED_CONTAINER); 32 | } 33 | 34 | // 根据一个接口类型,获取容器中的一个服务实例 35 | public static T getInstance(Class classType) { 36 | AppServiceContainer appServiceContainer = getAppServiceContainer(classType); 37 | return appServiceContainer.getInstance(classType); 38 | } 39 | 40 | // 根据一个接口类型,获取容器中所有的拓展服务实例 41 | public static List getInstances(Class classType, String... tags) { 42 | AppServiceContainer appServiceContainer = getAppServiceContainer(classType); 43 | return appServiceContainer.getInstances(classType, tags); 44 | } 45 | 46 | /** 47 | * 根据接口类型,返回所有的扩展实例 48 | * 可以传入一组名称,如果该名称的类型是可选Optional,通过withDefault可以控制是否加载默认的实现 49 | * @param classType 接口类型 50 | * @param names 名称列表,如果传递空表示所有的类型 51 | * @param withDefault 是否包含默认 52 | * @param 类型 53 | * @return 实现列表, 如果不存在返回为空集合 54 | */ 55 | public static List getInstances( 56 | Class classType, 57 | String[] names, 58 | boolean withDefault 59 | ) { 60 | AppServiceContainer appServiceContainer = getAppServiceContainer(classType); 61 | return appServiceContainer.getInstances( 62 | classType, 63 | names, 64 | new String[] {}, 65 | withDefault 66 | ); 67 | } 68 | 69 | /** 70 | * 根据接口类型获取合适的 AppServiceContainer 71 | * 如果是@Shared,那么直接获取 SHARED_CONTAINER 72 | * 否则,根据上下文获取当前的 AppServiceContainer 73 | */ 74 | private static AppServiceContainer getAppServiceContainer( 75 | Class classType 76 | ) { 77 | return AppServiceContainer.isSharedType(classType) ? SHARED_CONTAINER 78 | : ApplicationModelFactory.getCurrentApplication().getServiceContainer(); 79 | } 80 | } 81 | ``` 82 | -------------------------------------------------------------------------------- /01~线程与线程池/02~线程池/03~Executors/README.md: -------------------------------------------------------------------------------- 1 | # Executors 详解 2 | 3 | 并发 API 引入了 ExecutorService 作为一个在程序中直接使用 Thread 的高层次的替换方案。Executos 支持运行异步任务,通常管理一个线程池,这样一来我们就不需要手动去创建新的线程。在不断地处理任务的过程中,线程池内部线程将会得到复用,因此,在我们可以使用一个 Executor Service 来运行和我们想在我们整个程序中执行的一样多的并发任务。 4 | 5 | ![Executor 类图](https://s2.ax1x.com/2019/09/02/nPC2c9.png) 6 | 7 | ![Executor 方法图](https://s2.ax1x.com/2019/09/02/nPCRXR.png) 8 | 9 | ## Executor, ExecutorService 和 Executors 10 | 11 | ![UML 关系类图](https://s3.ax1x.com/2021/02/26/yx2YgU.png) 12 | 13 | 正如上面所说,这三者均是 Executor 框架中的一部分。Java 开发者很有必要学习和理解他们,以便更高效的使用 Java 提供的不同类型的线程池。总结一下这三者间的区别,以便大家更好的理解: 14 | 15 | - Executor 和 ExecutorService 这两个接口主要的区别是:ExecutorService 接口继承了 Executor 接口,是 Executor 的子接口 16 | - Executor 和 ExecutorService 第二个区别是:Executor 接口定义了 `execute()`方法用来接收一个`Runnable`接口的对象,而 ExecutorService 接口中的 `submit()`方法可以接受`Runnable`和`Callable`接口的对象。 17 | - Executor 和 ExecutorService 接口第三个区别是 Executor 中的 `execute()` 方法不返回任何结果,而 ExecutorService 中的 `submit()`方法可以通过一个 Future 对象返回运算结果。 18 | - Executor 和 ExecutorService 接口第四个区别是除了允许客户端提交一个任务,ExecutorService 还提供用来控制线程池的方法。比如:调用 `shutDown()` 方法终止线程池。 19 | - Executors 类提供工厂方法用来创建不同类型的线程池。比如: `newSingleThreadExecutor()` 创建一个只有一个线程的线程池,`newFixedThreadPool(int numOfThreads)`来创建固定线程数的线程池,`newCachedThreadPool()`可以根据需要创建新的线程,但如果已有线程是空闲的会重用已有线程。 20 | 21 | 下表列出了 Executor 和 ExecutorService 的区别: 22 | 23 | | Executor | ExecutorService | 24 | | --------------------------------------------------------- | ------------------------------------------------------------------------ | 25 | | Executor 是 Java 线程池的核心接口,用来并发执行提交的任务 | ExecutorService 是 Executor 接口的扩展,提供了异步执行和关闭线程池的方法 | 26 | | 提供 execute()方法用来提交任务 | 提供 submit()方法用来提交任务 | 27 | | execute()方法无返回值 | submit()方法返回 Future 对象,可用来获取任务执行结果 | 28 | | 不能取消任务 | 可以通过 Future.cancel()取消 pending 中的任务 | 29 | | 没有提供和关闭线程池有关的方法 | 提供了关闭线程池的方法 | 30 | 31 | # Hello World 32 | 33 | 下面是使用 Executors 的第一个代码示例: 34 | 35 | ```java 36 | ExecutorService executor = Executors.newSingleThreadExecutor(); 37 | executor.submit(() -> { 38 | String threadName = Thread.currentThread().getName(); 39 | System.out.println("Hello " + threadName); 40 | }); 41 | // => Hello pool-1-thread-1 42 | ``` 43 | 44 | Executors 必须显式的停止,否则它们将持续监听新的任务。ExecutorService 提供了两个方法来达到这个目的:shutdwon() 会等待正在执行的任务执行完而,shutdownNow() 会终止所有正在执行的任务并立即关闭 executor。 45 | 46 | ```java 47 | try { 48 | System.out.println("attempt to shutdown executor"); 49 | executor.shutdown(); 50 | executor.awaitTermination(5, TimeUnit.SECONDS); 51 | } 52 | catch (InterruptedException e) { 53 | System.err.println("tasks interrupted"); 54 | } 55 | finally { 56 | if (!executor.isTerminated()) { 57 | System.err.println("cancel non-finished tasks"); 58 | } 59 | executor.shutdownNow(); 60 | System.out.println("shutdown finished"); 61 | } 62 | ``` 63 | -------------------------------------------------------------------------------- /03~并发框架/Quartz/codes/examples/src/main/java/org/quartz/examples/example6/BadJob1.java: -------------------------------------------------------------------------------- 1 | /* 2 | * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | * 16 | */ 17 | 18 | package org.quartz.examples.example6; 19 | 20 | import org.quartz.DisallowConcurrentExecution; 21 | import org.quartz.Job; 22 | import org.quartz.JobDataMap; 23 | import org.quartz.JobExecutionContext; 24 | import org.quartz.JobExecutionException; 25 | import org.quartz.JobKey; 26 | import org.quartz.PersistJobDataAfterExecution; 27 | import org.slf4j.Logger; 28 | import org.slf4j.LoggerFactory; 29 | 30 | import java.util.Date; 31 | 32 | /** 33 | *

34 | * A job dumb job that will throw a job execution exception 35 | *

36 | * 37 | * @author Bill Kratzer 38 | */ 39 | @PersistJobDataAfterExecution 40 | @DisallowConcurrentExecution 41 | public class BadJob1 implements Job { 42 | 43 | // Logging 44 | private static Logger _log = LoggerFactory.getLogger(BadJob1.class); 45 | private int calculation; 46 | 47 | /** 48 | * Empty public constructor for job initialization 49 | */ 50 | public BadJob1() { 51 | } 52 | 53 | /** 54 | *

55 | * Called by the {@link org.quartz.Scheduler} when a {@link org.quartz.Trigger} fires that 56 | * is associated with the Job. 57 | *

58 | * 59 | * @throws JobExecutionException if there is an exception while executing the job. 60 | */ 61 | public void execute(JobExecutionContext context) throws JobExecutionException { 62 | JobKey jobKey = context.getJobDetail().getKey(); 63 | JobDataMap dataMap = context.getJobDetail().getJobDataMap(); 64 | 65 | int denominator = dataMap.getInt("denominator"); 66 | _log.info("---" + jobKey + " executing at " + new Date() + " with denominator " + denominator); 67 | 68 | // a contrived example of an exception that 69 | // will be generated by this job due to a 70 | // divide by zero error (only on first run) 71 | try { 72 | calculation = 4815 / denominator; 73 | } catch (Exception e) { 74 | _log.info("--- Error in job!"); 75 | JobExecutionException e2 = new JobExecutionException(e); 76 | 77 | // fix denominator so the next time this job run 78 | // it won't fail again 79 | dataMap.put("denominator", "1"); 80 | 81 | // this job will refire immediately 82 | e2.setRefireImmediately(true); 83 | throw e2; 84 | } 85 | 86 | _log.info("---" + jobKey + " completed at " + new Date()); 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /03~并发框架/Disq/codes/disq/src/main/java/net/intelie/disq/dson/DsonBinaryWrite.java: -------------------------------------------------------------------------------- 1 | package net.intelie.disq.dson; 2 | 3 | import net.intelie.disq.Buffer; 4 | 5 | public abstract class DsonBinaryWrite { 6 | public static void writeUnicode(Buffer.OutStream stream, CharSequence str) { 7 | int length = str.length(); 8 | writeCount(stream, length); 9 | stream.unsafePrepare(length * 2); 10 | for (int i = 0; i < length; i++) { 11 | char c = str.charAt(i); 12 | stream.unsafeWrite((c) & 0xFF); 13 | stream.unsafeWrite((c >>> 8) & 0xFF); 14 | } 15 | } 16 | 17 | public static void writeLatin1(Buffer.OutStream stream, CharSequence str) { 18 | int length = str.length(); 19 | writeCount(stream, length); 20 | stream.unsafePrepare(length); 21 | for (int i = 0; i < length; i++) { 22 | char c = str.charAt(i); 23 | stream.unsafeWrite(c); 24 | } 25 | } 26 | 27 | public static void writeType(Buffer.OutStream stream, DsonType string) { 28 | stream.unsafePrepare(1); 29 | stream.unsafeWrite(string.getValue()); 30 | } 31 | 32 | public static void writeNumber(Buffer.OutStream stream, double value) { 33 | writeInt64(stream, Double.doubleToRawLongBits(value)); 34 | } 35 | 36 | public static void writeBoolean(Buffer.OutStream stream, boolean value) { 37 | stream.unsafePrepare(1); 38 | stream.write(value ? 1 : 0); 39 | } 40 | 41 | public static void writeCount(Buffer.OutStream stream, int value) { 42 | if ((value & 0x7F) == value) { 43 | stream.unsafePrepare(1); 44 | stream.unsafeWrite(value & 0x7F); 45 | } else if ((value & 0x3FFF) == value) { 46 | stream.unsafePrepare(2); 47 | stream.unsafeWrite(0x80 | value & 0x7F); 48 | stream.unsafeWrite((value >> 7) & 0x7F); 49 | } else { 50 | stream.unsafePrepare(5); 51 | stream.unsafeWrite(0x80 | value & 0x7F); 52 | stream.unsafeWrite(0x80 | (value >> 7) & 0x7F); 53 | stream.unsafeWrite((value >> 14) & 0xFF); 54 | stream.unsafeWrite((value >> 22) & 0xFF); 55 | stream.unsafeWrite((value >> 30) & 0xFF); 56 | } 57 | } 58 | 59 | public static void writeInt32(Buffer.OutStream stream, int value) { 60 | stream.unsafePrepare(4); 61 | stream.unsafeWrite((value) & 0xFF); 62 | stream.unsafeWrite((value >> 8) & 0xFF); 63 | stream.unsafeWrite((value >> 16) & 0xFF); 64 | stream.unsafeWrite((value >> 24) & 0xFF); 65 | } 66 | 67 | public static void writeInt64(Buffer.OutStream stream, long value) { 68 | stream.unsafePrepare(8); 69 | stream.unsafeWrite((int) ((value) & 0xFF)); 70 | stream.unsafeWrite((int) ((value >>> 8) & 0xFF)); 71 | stream.unsafeWrite((int) ((value >>> 16) & 0xFF)); 72 | stream.unsafeWrite((int) ((value >>> 24) & 0xFF)); 73 | stream.unsafeWrite((int) ((value >>> 32) & 0xFF)); 74 | stream.unsafeWrite((int) ((value >>> 40) & 0xFF)); 75 | stream.unsafeWrite((int) ((value >>> 48) & 0xFF)); 76 | stream.unsafeWrite((int) ((value >>> 56) & 0xFF)); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /03~并发框架/RxJava/基础使用/Observable.md: -------------------------------------------------------------------------------- 1 | # Observable(被观察者) 2 | 3 | 在 ReactiveX 中,一个观察者(Observer)订阅一个可观察对象(Observable)。观察者对 Observable 发射的数据或数据序列作出响应。这种模式可以极大地简化并发操作,因为它创建了一个处于待命状态的观察者哨兵,在未来某个时刻响应 Observable 的通知,不需要阻塞等待 Observable 发射数据。 4 | 5 | ![Observable 事件流](https://s2.ax1x.com/2019/12/19/QqTJHS.png) 6 | 7 | Observable 即被观察者,它决定什么时候触发事件以及触发怎样的事件。RxJava 使用 create() 方法来创建一个 Observable,并为它定义事件触发规则: 8 | 9 | ```java 10 | //匿名类方式 11 | Observable observable = Observable.create(new Observable.OnSubscribe() { 12 | @Override 13 | public void call(Subscriber subscriber) { 14 | subscriber.onNext("Hello"); 15 | subscriber.onNext("Hi"); 16 | subscriber.onNext("Aloha"); 17 | subscriber.onCompleted(); 18 | } 19 | }); 20 | 21 | 22 | //lambda方式 23 | Observable observable = Observable.create((subscriber)->{ 24 | subscriber.onNext("Hello"); 25 | subscriber.onNext("Hi"); 26 | subscriber.onNext("Aloha"); 27 | subscriber.onCompleted(); 28 | }); 29 | ``` 30 | 31 | 可以看到,这里传入了一个 `OnSubscribe` 对象作为参数。`OnSubscribe` 会被存储在返回的 `Observable` 对象中,它的作用相当于一个计划表,当 `Observable` 被订阅的时候,`OnSubscribe` 的 `call()` 方法会自动被调用,事件序列就会依照设定依次触发(对于上面的代码,就是观察者`Subscriber` 将会被调用三次 `onNext()` 和一次 `onCompleted()`)。这样,由被观察者调用了观察者的回调方法,就实现了由被观察者向观察者的事件传递,即观察者模式。 32 | 33 | # 背景知识 34 | 35 | 在很多软件编程任务中,或多或少你都会期望你写的代码能按照编写的顺序,一次一个的顺序执行和完成。但是在 ReactiveX 中,很多指令可能是并行执行的,之后他们的执行结果才会被观察者捕获,顺序是不确定的。为达到这个目的,你定义一种获取和变换数据的机制,而不是调用一个方法。在这种机制下,存在一个可观察对象(Observable),观察者(Observer)订阅(Subscribe)它,当数据就绪时,之前定义的机制就会分发数据给一直处于等待状态的观察者哨兵。 36 | 37 | 这种方法的优点是,如果你有大量的任务要处理,它们互相之间没有依赖关系。你可以同时开始执行它们,不用等待一个完成再开始下一个(用这种方式,你的整个任务队列能耗费的最长时间,不会超过任务里最耗时的那个)。有很多术语可用于描述这种异步编程和设计模式,在在本文里我们使用这些术语:一个观察者订阅一个可观察对象 (An observer subscribes to an Observable)。通过调用观察者的方法,Observable 发射数据或通知给它的观察者。在其它的文档和场景里,有时我们也将 Observer 叫做 Subscriber、Watcher、Reactor。这个模型通常被称作 Reactor 模式。 38 | 39 | # 创建 Observable 40 | 41 | `create()` 方法是 RxJava 最基本的创造事件序列的方法。基于这个方法,RxJava 还提供了一些方法用来快捷创建事件队列。 42 | 43 | ## just 44 | 45 | `just(T...)`: 将传入的参数依次发送出来。 46 | 47 | ```java 48 | Observable observable = Observable.just("Hello", "Hi", "Aloha"); 49 | // 将会依次调用: 50 | // onNext("Hello"); 51 | // onNext("Hi"); 52 | // onNext("Aloha"); 53 | // onCompleted(); 54 | ``` 55 | 56 | ## from 57 | 58 | `from(T[])` / `from(Iterable)` : 将传入的数组或 `Iterable` 拆分成具体对象后,依次发送出来。 59 | 60 | ```java 61 | String[] words = {"Hello", "Hi", "Aloha"}; 62 | Observable observable = Observable.from(words); 63 | // 将会依次调用: 64 | // onNext("Hello"); 65 | // onNext("Hi"); 66 | // onNext("Aloha"); 67 | // onCompleted(); 68 | ``` 69 | 70 | 上面 `just(T...)` 的例子和 `from(T[])` 的例子,都和之前的 `create(OnSubscribe)` 的例子是等价的。 71 | 72 | ## interval 73 | 74 | interval 用于创建一个根据固定的时间间隔发射序列数据的 Observable。 75 | 76 | ![](http://reactivex.io/documentation/operators/images/interval.c.png) 77 | 78 | # Subscribe(订阅) 79 | 80 | 在创建好了 Observable 之后,即有了待观察对象之后,就需要设置每当这个对象发射消息时候的响应动作。 81 | 82 | ```java 83 | observable.subscribe(observer); 84 | // 或者: 85 | observable.subscribe(subscriber); 86 | ``` 87 | 88 | 有人可能会注意到,`subscribe()` 这个方法有点怪:它看起来是`observalbe` 订阅了 `observer` / `subscriber`而不是`observer` / `subscriber` 订阅了 `observalbe`,这看起来就像杂志订阅了读者一样颠倒了对象关系。这让人读起来有点别扭,不过如果把 API 设计成 `observer.subscribe(observable)` / `subscriber.subscribe(observable)`,虽然更加符合思维逻辑,但对流式 API 的设计就造成影响了,比较起来明显是得不偿失的。 89 | 90 | # Single 91 | -------------------------------------------------------------------------------- /02~线程安全/并发容器/ConcurrentHashMap/语法使用.md: -------------------------------------------------------------------------------- 1 | # ConcurrentHashMap 2 | 3 | # 案例:计数器 4 | 5 | 统计文本中单词出现的次数,把单词出现的次数记录到一个 Map 中,代码如下: 6 | 7 | ```java 8 | private final Map wordCounts = new ConcurrentHashMap<>(); 9 | public long increase(String word) { 10 | Long oldValue = wordCounts.get(word); 11 | Long newValue = (oldValue == null) ? 1L : oldValue + 1; 12 | wordCounts.put(word, newValue); 13 | return newValue; 14 | } 15 | ``` 16 | 17 | 如果多个线程并发调用这个 increase()方法,increase()的实现就是错误的,因为多个线程用相同的 word 调用时,很可能会覆盖相互的结果,造成记录的次数比实际出现的次数少。ConcurrentMap 接口定义了几个基于 CAS(Compare and Set)操作,很简单,但非常有用,下面的代码用 ConcurrentMap 解决上面问题: 18 | 19 | ```java 20 | private final ConcurrentMap wordCounts = new ConcurrentHashMap<>(); 21 | public long increase(String word) { 22 | Long oldValue, newValue; 23 | while (true) { 24 | oldValue = wordCounts.get(word); 25 | if (oldValue == null) { 26 | // Add the word firstly, initial the value as 1 27 | newValue = 1L; 28 | if (wordCounts.putIfAbsent(word, newValue) == null) { 29 | break; 30 | } 31 | } else { 32 | newValue = oldValue + 1; 33 | if (wordCounts.replace(word, oldValue, newValue)) { 34 | break; 35 | } 36 | } 37 | } 38 | return newValue; 39 | } 40 | ``` 41 | 42 | 值得一提的是,如果这里没有用 replace,那么同样会存在问题。上面的实现每次调用都会涉及 Long 对象的拆箱和装箱操作,很明显,更好的实现方式是采用 AtomicLong,下面是采用 AtomicLong 后的代码: 43 | 44 | ```java 45 | private final ConcurrentMap wordCounts = new ConcurrentHashMap<>(); 46 | public long increase(String word) { 47 | AtomicLong number = wordCounts.get(word); 48 | if (number == null) { 49 | AtomicLong newNumber = new AtomicLong(0); 50 | number = wordCounts.putIfAbsent(word, newNumber); 51 | if (number == null) { 52 | number = newNumber; 53 | } 54 | } 55 | return number.incrementAndGet(); 56 | } 57 | 58 | ``` 59 | 60 | 这个实现仍然有一处需要说明的地方,如果多个线程同时增加一个目前还不存在的词,那么很可能会产生多个 newNumber 对象,但最终只有一个 newNumber 有用,其他的都会被扔掉。对于这个应用,这不算问题,创建 AtomicLong 的成本不高,而且只在添加不存在词是出现。但换个场景,比如缓存,那么这很可能就是问题了,因为缓存中的对象获取成本一般都比较高,而且通常缓存都会经常失效,那么避免重复创建对象就有价值了。下面的代码演示了怎么处理这种情况: 61 | 62 | ```java 63 | private final ConcurrentMap> cache = new ConcurrentHashMap<>(); 64 | public ExpensiveObj get(final String key) { 65 | Future future = cache.get(key); 66 | if (future == null) { 67 | Callable callable = new Callable() { 68 | 69 | @Override 70 | public ExpensiveObj call() throws Exception { 71 | 72 | return new ExpensiveObj(key); 73 | } 74 | }; 75 | FutureTask task = new FutureTask<>(callable); 76 | 77 | future = cache.putIfAbsent(key, task); 78 | 79 | if (future == null) { 80 | future = task; 81 | task.run(); 82 | } 83 | } 84 | 85 | try { 86 | return future.get(); 87 | } catch (Exception e) { 88 | cache.remove(key); 89 | 90 | throw new RuntimeException(e); 91 | } 92 | } 93 | ``` 94 | 95 | 解决方法其实就是用一个 Proxy 对象来包装真正的对象,跟常见的 lazy load 原理类似;使用 FutureTask 主要是为了保证同步,避免一个 Proxy 创建多个对象。注意,上面代码里的异常处理是不准确的。最后再补充一下,如果真要实现前面说的统计单词次数功能,最合适的方法是 Guava 包中 AtomicLongMap;一般使用 ConcurrentHashMap,也尽量使用 Guava 中的 MapMaker 或 cache 实现。 96 | --------------------------------------------------------------------------------