Java
文章平均质量分 92
只有看过我的类库才能算是真正了解我的男人
anlian523
人若无名,专心练剑!
展开
-
JUC框架 源码解析系列文章目录 JDK8
前言笔者在接下来这段时间将对JUC框架内的重点内容进行深度解析,由于内容颇多,需要分成多篇文章,所以在这里列一个目录,方便大家翻阅。目录JUC框架的基础是CAS和自旋,而CAS则是利用Unsafe类提供的CAS操作,而原子类则依靠于CAS和自旋。下面几篇文章从源码分析JUC框架内的几个重要的原子类。JUC AtomicInteger源码解析 JDK8JUC AtomicIntegerArray源码解析 JDK8JUC AtomicStampedReference源码解析 JDK8AQS(A原创 2020-06-07 11:37:05 · 9458 阅读 · 13 评论 -
JUC线程池 ScheduledThreadPoolExecutor源码解析 JDK8
ThreadPoolExecutor的功能已经很强大了,但却没有延时任务的功能。对于ScheduledThreadPoolExecutor,它的父类ThreadPoolExecutor所做的提交任务都是延时时间为0的延时任务。同时,它也提供了执行周期任务的功能。原创 2020-09-03 20:21:42 · 732 阅读 · 1 评论 -
JUC线程池 ThreadPoolExecutor源码解析 JDK8
ThreadPoolExecutor提供了管理线程的功能,它的最大好处在于能够复用线程,而不必在想要异步执行时用原始的new Thread().start()起线程。在构造ThreadPoolExecutor时通过给定不同的参数,可以得到不同效果的线程池。这样,使用者可以不用关心线程的管理,而专心于业务的编写。原创 2020-08-31 23:19:08 · 1314 阅读 · 0 评论 -
JUC框架 CompletableFuture源码解析 JDK8
我们知道FutureTask实现了task异步执行,但对于执行结果的获取,如果异步执行还在进行中,那么线程只能get阻塞等待,或者轮询isDone,这两种方式都和我们开始实现异步的初衷相违背。所以就诞生了这个CompletableFuture,它的最大不同之处在于,通过提供回调函数的概念,把处理执行结果的过程也放到异步线程里去做。原创 2020-08-24 23:41:27 · 2386 阅读 · 1 评论 -
JUC框架 FutureTask源码解析 JDK8
FutureTask的使用方法已经在上一篇进行了讲解,其实它和SynchronousQueue很像,执行task的线程是生产者,获取执行结果的线程是消费者,消费者阻塞的原因不是生产者还没来,是因为生产者还没有生产出来执行结果。只不过,这里只有一个生产者(FutureTask对象),却可以对应到多个消费者(对同一个FutureTask对象调用get的不同线程)。原创 2020-08-17 00:05:00 · 958 阅读 · 0 评论 -
JUC框架 从Runnable到Callable到FutureTask 使用浅析
本文旨在简单讲解Runnable、Callable、FutureTask这几个线程执行相关的接口和类。为后面FutureTask源码讲解作铺垫。原创 2020-08-15 19:49:39 · 644 阅读 · 0 评论 -
JUC集合类 SynchronousQueue源码解析 JDK8
SynchronousQueue其实就是LinkedTransferQueue的升级版,相同的是它们都作为生产者和消费者交互的通道,可以直接让生产者和消费者打交道。不同的是,SynchronousQueue做的更彻底,不去支持无关的共有操作(比如size()),只提供必要的入队出队方法。并且,SynchronousQueue提供了两种逻辑结构,栈和队列。理解LinkedTransferQueue是搞懂SynchronousQueue的前提。原创 2020-08-15 11:42:42 · 2876 阅读 · 0 评论 -
JUC集合类 LinkedTransferQueue源码解析 JDK8
LinkedTransferQueue是一种特殊的无界阻塞队列,它提供一种Transfer的功能,用以保证生产者把数据传输给消费者。其他的普通队列,生产者是不需要关心消费者是否存在的,但现在的LinkedTransferQueue却需要保证生产者把数据确实传输给了消费者,才算是一次成功的入队操作,否则算作入队失败。原创 2020-08-09 22:38:05 · 804 阅读 · 0 评论 -
JUC集合类 DelayQueue源码解析 JDK8
DelayQueue是一个无界阻塞队列,它和PriorityBlockingQueue一样是一个优先队列,但区别在于队列元素只能放置Delayed对象,而且只有元素到期后才能将其出队。内部是一个最小堆,堆顶永远是最先“到期”的那个元素。如果堆顶元素没有到期,即使线程发现队列中有元素,也不能将其出队。DelayQueue需要依赖于元素对Delayed接口正确实现,即保证到期时间短的Delayed元素.compareTo(到期时间长的Delayed元素) < 0,这样可以让到期时间短的Delayed元素原创 2020-08-07 22:59:40 · 614 阅读 · 1 评论 -
JDK8 PriorityBlockingQueue(Collection<? extends E> c)构造器 源码解析
PriorityBlockingQueue的这个(Collection<? extends E> c)重载版本构造器的源码有几个地方有点难懂,但本文讲述内容不是PriorityBlockingQueue的重点理解内容,所以本文单独讲解。原创 2020-08-05 22:41:53 · 442 阅读 · 0 评论 -
JUC集合类 PriorityBlockingQueue源码解析 JDK8
PriorityBlockingQueue是一个无界阻塞队列,它的出队方式不再是FIFO,而是优先级高的先出队。其内部实现是最小堆,即堆顶元素是逻辑上最小的那个元素,也是最先出队的那个元素。简单的说,如果a.compareTo(b) < 0的话,那么a将先出队。原创 2020-08-04 22:03:07 · 522 阅读 · 0 评论 -
JUC集合类 LinkedBlockingDeque源码解析 JDK8
和ArrayBlockingQueue一样,使用一个Lock和两个Condition来控制并发和阻塞。因为两端都可以入队出队,所以用一个锁才能保证正确。和LinkedBlockingQueue不同的是,我们初始化时,连dummy node也没有。从first端移除的节点,next指针指向自身,以区别于first指向节点(next指针不会指向自身,可能为真实节点或null)。从last端移除的节点同理。原创 2020-07-30 23:55:45 · 718 阅读 · 3 评论 -
JUC集合类 LinkedBlockingQueue源码解析 JDK8
LinkedBlockingQueue是一种FIFO(first-in-first-out 先入先出)的有界阻塞队列,底层是单链表,也支持从内部删除元素。并发操作依赖于加锁的控制,支持阻塞式的入队出队操作。相比ArrayBlockingQueue的一个Lock,LinkedBlockingQueue使用了两个Lock,分别对应入队动作和出队动作,这便提高了并发量。原创 2020-07-29 23:58:02 · 928 阅读 · 0 评论 -
JDK8 ArrayBlockingQueue迭代器 源码解析
ArrayBlockingQueue的迭代器的整体设计很复杂,主要在于它需要去保证自己总是能保持 队首队尾索引,并以队首队尾索引为基础进行下一次的遍历。迭代器使用了一种“队首快照”的办法,用于发现 返回元素 相较与上一次快照时,实际上它已经被删除的情况。使得lastRet为REMOVE,从而让Itr#remove不会执行删除动作。还考虑内部删除节点的情景,这种情况需要 索引左移。原创 2020-07-26 19:59:14 · 611 阅读 · 0 评论 -
JUC集合类 ArrayBlockingQueue源码解析 JDK8
ArrayBlockingQueue是一种FIFO(first-in-first-out 先入先出)的有界阻塞队列,底层是数组,也支持从内部删除元素。并发操作依赖于加锁的控制,支持阻塞式的入队出队操作。正因为有界,所以才会阻塞。加锁实现完全依赖于AQS,需要读者比较熟悉AQS 独占锁的获取过程和AQS Condition接口的实现。对ArrayBlockingQueue的源码解析,更像是了解一次AQS的最佳实践。原创 2020-07-25 17:44:29 · 973 阅读 · 2 评论 -
JUC集合类 ConcurrentLinkedDeque源码解析 JDK8
ConcurrentLinkedDueue是一个无界的双端队列,它的并发操作是基于CAS的无锁实现,所以不会产生阻塞。要想理解ConcurrentLinkedDueue,先去理解了ConcurrentLinkedQueue才是快捷方式,因为它们二者使用的概念和函数实现的套路实际上都很类似。原创 2020-07-25 11:46:38 · 721 阅读 · 0 评论 -
JUC集合类 ConcurrentLinkedQueue源码解析 JDK8
ConcurrentLinkedQueue是一种FIFO(first-in-first-out 先入先出)的队列,底层是单链表,一般来说,队列只支持队尾入队、队头出队,但此类还支持从内部删除某个特定的节点。使用非阻塞算法来处理并发操作,这也意味着实现里充满了CAS和自旋。原创 2020-07-22 00:44:59 · 1153 阅读 · 5 评论 -
JDK8 ConcurrentHashMap的Bug集锦
JDK8的ConcurrentHashMap并不是完美的,从https://bugs.openjdk.java.net/projects/JDK/issues上也可以看到JDK的很多Bug,当然,通过给concurrency-interest发邮件也可以和Doug Lea直接对话。最重要的是,知道了这些bug的存在,可以避免我们去分析这些不正确的代码的“正确性”。原创 2020-07-19 11:02:50 · 1588 阅读 · 7 评论 -
JUC集合类 ConcurrentHashMap源码解析 JDK8
ConcurrentHashMap是一种支持并发操作的HashMap,并发控制方面,它使用更小粒度的锁——对每个哈希桶的头节点加锁。虽然这样使得效率更高,能让读写操作最大程序的并发执行,但也造成了读写操作的一致性很弱,比如size()返回的大小可能已经与真实大小不一样,比如clear()调用返回后Map中却拥有着元素。JDK8的ConcurrentHashMap并不是完美,在阅读源码之前,建议先看看ConcurrentHashMap有些什么bug,以免在这些地方钻牛角尖。原创 2020-07-19 10:58:27 · 2216 阅读 · 1 评论 -
ThreadLocalRandom#getProbe #advanceProbe浅析
简单的说,一个线程的probe探针值,是一个hash值,它不会和其他线程重复(一定情况下)。ThreadLocalRandom的probe机制在线程数比较少,且probe移动次数也比较小时,是可以保证探针不重复的。但是不重复的情况指的是在int的范围内不重复,在有限的桶位面前,由于只取probe值的后几位bit,那么很大概率也会重复。原创 2020-07-12 14:02:02 · 2475 阅读 · 0 评论 -
JUC集合类 ConcurrentSkipListMap源码解析 JDK8
ConcurrentSkipListMap的实现是完全的无锁编程lock free。为了保证并发删除和插入正常执行,加入了marker机制,以及删除过程的三个步骤。base层和index层的头节点都是dummy节点,如果没有dummy节点,实现将会变得复杂。每个节点插入后,将新建的index层是完全随机的。如果新建层数超过了现有最大层数,那么新建层数则为最大层数+1。SkipList跳表使用空间换取时间,以得到查找效率。正因为插入节点的新建index层数是完全随机的,所以也使得实现更简单,整个跳表从原创 2020-07-06 22:38:10 · 850 阅读 · 1 评论 -
JUC集合类 CopyOnWriteArraySet源码解析 JDK8
但CopyOnWriteArraySet的底层实现完全依赖了CopyOnWriteArrayList,它持有了一个CopyOnWriteArrayList类型的成员,很多方法的实现都是直接调用CopyOnWriteArrayList的同名方法。这意味着CopyOnWriteArraySet的底层实现不再是散列表的实现,而只是一个普通数组,只不过现在CopyOnWriteArraySet表现地像一个HashSet而已,不过这对于使用者来说已经足够了。原创 2020-06-27 17:46:58 · 653 阅读 · 0 评论 -
JUC集合类 CopyOnWriteArrayList源码解析 JDK8
volatile + return操作/赋值操作,保证了数组成员的可见性和原子性。读操作不用加锁,直接获取数组成员。写操作需要加锁,并拷贝原数组成员。优点:保留了读操作的高性能。避免了并发修改抛出的异常。缺点:写操作太多时,将产生高内存消耗。因为需要拷贝出新数组。读操作不能保证看到最新的数据,即使写操作已经开始执行了。因为直到写操作执行setArray之前,读操作都无法看到最新数据。场景:读操作多,写操作少的场景。读操作允许看到非最新数据的场景。原创 2020-06-27 15:19:44 · 934 阅读 · 2 评论 -
JUC框架 ReentrantReadWriteLock源码解析 JDK8
同步器的state被划分为两个部分,分别记录被拿走的读锁和写锁的总数。分别记录各个线程拿走的读锁的工作交给了各个线程自己,通过ThreadLocal实现。不仅写锁可以重入(这类似于ReentrantLock),读锁也可以重入。尝试获取写锁时,会因为其他写锁或任意读锁(包括自己)的存在,而进入阻塞等待的过程,抛入sync queue中去。尝试获取读锁时,会因为其他写锁(不包括自己的写锁)的存在,而进入阻塞等待的过程,抛入sync queue中去。读锁的非公平获取中,apparentlyFirstQu原创 2020-06-26 12:07:06 · 1278 阅读 · 3 评论 -
ReentrantReadWriteLock的readerShouldBlock与apparentlyFirstQueuedIsExclusive 深入理解读锁的非公平实现
readerShouldBlock的非公平实现,并不是完全的非公平实现(即直接返回false)。readerShouldBlock的不完全的非公平实现,是为了防止写锁无限等待。readerShouldBlock在一定概率下,防止了new reader的读锁获取动作,转而让new reader去sync queue中排队。原创 2020-06-26 09:48:35 · 1931 阅读 · 4 评论 -
JUC框架 Semaphore源码解析 JDK8
Semaphore的AQS子类是一个很标准的共享锁实现。获得信号量 == 减小AQS的state。释放信号量 == 增加AQS的state。共享锁的AQS子类实现方法需要自旋,这一点在Semaphore和CountDownLatch都有体现。独占锁的AQS子类实现方法不需要自旋。获得信号量失败一定是因为信号量已经减到0了,且获得失败就会阻塞。原创 2020-06-24 23:07:55 · 754 阅读 · 0 评论 -
JUC框架 CyclicBarrier源码解析 JDK8
CyclicBarrier类似于CountDownLatch但又不同,在CyclicBarrier里,每个线程既要CountDown减小计数器,也要阻塞等待直到count为0(即等待线程到齐)。CyclicBarrier基于独占锁和条件队列而实现。CyclicBarrier可以重复使用,每有parties个线程到达barrier,开启新的一代。然后旧的一代线程就会相继从CyclicBarrier#await退出。CyclicBarrier实现了all-or-none breakage model的原则原创 2020-06-21 18:14:18 · 931 阅读 · 1 评论 -
JUC框架 CountDownLatch源码解析 JDK8
CountDownLatch的使用场景:当某个量化为数字的条件被满足后,几个线程任务才可以同时开始执行。调用CountDownLatch#await的线程将等待条件被满足,条件满足后,调用CountDownLatch#await的若干线程将从同一个时间点继续执行。调用CountDownLatch#countDown,让量化为数字的条件减一。调用CountDownLatch#await的线程,和调用CountDownLatch#countDown的线程,是两类线程,并无关系。唤醒阻塞在await的若干原创 2020-06-20 11:23:06 · 1032 阅读 · 0 评论 -
interrupt()中断对LockSupport.park()的影响
park调用后一定会消耗掉permit,无论unpark操作先做还是后做。如果中断状态为true,那么park无法阻塞。unpark会使得permit为1,并唤醒处于阻塞的线程。interrupt()会使得中断状态为true,并调用unpark。sleep() / wait() / join()调用后一定会消耗掉中断状态,无论interrupt()操作先做还是后做。原创 2020-06-15 22:32:55 · 4652 阅读 · 17 评论 -
AQS深入理解系列(四)Condition接口的实现
一个新设计的出现,总是为了替换现有的略有不足的设计。而Condition接口的出现,是为了代替监视器锁的wait/notify机制,提供更强大的功能。先简单说明一下它们之间的相同之处,以便之后更好地理解Condition接口的实现:调用wait()的线程必须已经处于同步代码块中,换言之,调用wait()的线程已经获得了监视器锁;调用await()的线程则必须是已经获得了lock锁。执行wait()时,当前线程会释放已获得的监视器锁,进入到该监视器的等待队列中;执行await()时,当前线程会释放已获原创 2020-06-14 18:59:43 · 2100 阅读 · 0 评论 -
AQS深入理解系列(三)共享锁的获取与释放
独占锁是线程独占的,同一时刻只有一个线程能拥有独占锁,AQS里将这个线程放置到exclusiveOwnerThread成员上去。共享锁是线程共享的,同一时刻能有多个线程拥有共享锁,但AQS里并没有用来存储获得共享锁的多个线程的成员。如果一个线程刚获取了共享锁,那么在其之后等待的线程也很有可能能够获取到锁。但独占锁不会这样做,因为锁是独占的。当然,如果一个线程刚释放了锁,不管是独占锁还是共享锁,都需要唤醒在后面等待的线程。原创 2020-06-08 10:26:13 · 3045 阅读 · 9 评论 -
AQS深入理解系列(二) 独占锁的释放过程
首先会尝试设置状态从小于0变成0。一般可以这样认为,如果head的状态为0,代表head后继线程即将被唤醒,或者已经被唤醒。如果遇到s == null,说明我们遇到一种中间状态,next指针还没有指好。如果遇到s.waitStatus > 0,说明head后继刚取消了。这两种情况,都需要从队尾的prev往前找。关于prev的有效性,已经在上一篇博客讲过了。注意循环条件t != null && t != node,它会从队尾一直往前找,直到t是null或t已经到达了node。一般情况下,不会出现t !=原创 2020-06-04 23:44:02 · 1591 阅读 · 1 评论 -
AQS深入理解系列(一) 独占锁的获取过程
对于独占锁,AQS的state代表代表锁的状态,为0代表没有线程持有锁,非0代表有线程持有了锁。获得了锁的线程会将自己设置为exclusiveOwnerThread。addWaiter负责new出一个包装当前线程的node,enq负责将node添加到队尾,如果队尾为空,它还负责添加dummy node。acquireQueued是整个获取锁过程的核心,这里是指它的那个死循环。一般情况下,每次循环做的事就是:尝试获取锁,获取锁失败,阻塞,被唤醒。如果某一次循环获取锁成功,那么之后会返回到用户代码调用处。原创 2020-06-03 00:03:19 · 4137 阅读 · 7 评论 -
AQS深入理解 shouldParkAfterFailedAcquire源码分析 状态为0或PROPAGATE的情况分析
检测到0后,一定要设置成SIGNAL的原因:设置前驱状态为SIGNAL,以便当前线程阻塞后,前驱能根据SIGNAL状态来唤醒自己。(具体看章节释放锁后,唤醒head后继的条件)设置成SIGNAL后会返回false的原因:返回false后,下一次循环开始,会重新获取node的前驱,前驱如果就是head,那么还会重新尝试获取锁。这次重新尝试是有必要的,某些场景下,重新尝试获取锁会成功。原创 2020-05-31 16:28:37 · 8559 阅读 · 10 评论 -
AQS深入理解 doReleaseShared源码分析 JDK8
doReleaseShared会尝试唤醒 head后继的代表线程,如果线程已经唤醒,则仅仅设置PROPAGATE状态。上一条的“尝试唤醒 head后继的代表线程”和“设置PROPAGATE状态”都是CAS操作,如果CAS失败,循环会马上continue并再次尝试。当检测到局部变量h与当前最新head是不同对象时,说明有acquire thread刚获取了锁,那下一个等待线程也很可能可以获取锁,所以不会break,循环继续。原创 2020-05-24 22:29:42 · 6117 阅读 · 12 评论 -
AQS深入理解 setHeadAndPropagate源码分析 JDK8
如果propagate > 0不成立,而h.waitStatus < 0成立。这说明旧head的status<0。但如果你看doReleaseShared的逻辑,会发现在unparkSuccessor之前就会CAS设置head的status为0的,在unparkSuccessor也会进行一次CAS尝试,因为head的status为0代表一种中间状态(head的后继代表的线程已经唤醒,但它还没有做完工作),或者代表head是tail。而这里旧head的status<0,只能是由于doReleaseShared原创 2020-05-24 19:12:14 · 5847 阅读 · 24 评论 -
Java并发 volatile可见性的验证
加了同步块,子线程会退出。这是因为synchronized具体过程是:获得同步锁;清空工作内存;从主内存拷贝对象副本到工作内存;执行代码(计算或者输出等);刷新主内存数据;释放同步锁。简单的说,synchronized进入时,会将 主内存中最新的变量,拷贝进 自己线程 的工作内存。synchronized退出时,会把 自己线程的工作内存的变量 弄进 主内存中。原创 2020-05-17 21:35:42 · 1106 阅读 · 1 评论 -
AQS深入理解 hasQueuedPredecessors源码分析 JDK8
分析hasQueuedPredecessors的返回判断。首先要知道,hasQueuedPredecessors返回true代表有别的线程在CHL队列中排了当前线程之前;返回false代表当前线程处于CHL队列的第一个线程。分析h != t返回false的情况。此时hasQueuedPredecessors返回false。当h和t都为null,返回false。此时说明队列为空,还从来没有Node入过队。当h和t都指向同一个Node,也返回false。此时说明队列中只有一个dummy node,那说原创 2020-05-17 18:13:35 · 4552 阅读 · 15 评论 -
JUC AtomicIntegerArray源码解析 JDK8
相比其他原子类,原子数组类AtomicIntegerArray,它的成员不再是volatile的,而是final的。从设计上来讲,作为原子数组类的数组成员初始化后,确实也不应该被修改引用了。计算数组元素的地址偏移时,需要两个值,base和元素索引。get/set函数不能像其他原子类一样,get只有return成员,set直接赋值成员。只能借助unsafe的getIntVolatile / putIntVolatile。原创 2020-05-10 21:21:33 · 598 阅读 · 0 评论 -
JUC AtomicInteger源码解析 JDK8
AtomicInteger类通过volatile语义加上CAS操作,使得对AtomicInteger的操作实现了一种非阻塞同步,从而保证了线程安全。非阻塞在于它没有使用synchronized或者Lock,而是循环加CAS,既然是循环执行,那么肯定没有阻塞线程,也就没有切换线程所带来的消耗。原创 2020-05-10 15:52:05 · 1307 阅读 · 0 评论