
JUC并发原理及源码
文章平均质量分 93
JUC并发原理及源码
东阳马生架构
回归初心,保持好奇心,享受那些"成功解题"的喜悦~
展开
专栏收录文章
- 默认排序
- 最新发布
- 最早发布
- 最多阅读
- 最少阅读
-
二叉树红黑树和B+树等
假设从结点A出发到叶结点的普通路径有n个黑色结点(包含出发结点),那么从黑色结点B出发到叶结点的普通路径就应该有n + 2个黑色结点,从红色结点D出发到叶结点的普通路径就应该有n + 1个黑色结点,从黑色结点C出发到叶结点的普通路径就应该有n + 1个黑色结点,从黑色结点E出发到叶结点的普通路径就应该有n + 1个黑色结点。那么根据二叉查找树的删除结点的方法,要找删除结点的中序后继填补,也就是需要找删除结点的右子树中最小的结点和删除结点进行位置交换,然后删除交换位置后,在原来中序后继结点位置的删除结点。原创 2025-02-27 22:00:34 · 775 阅读 · 0 评论 -
JUC并发—15.红黑树详解
关于上图的补充说明:假设从结点A出发到叶结点的普通路径有n个黑色结点(包含出发结点),那么从黑色结点B出发到叶结点的普通路径就应该有n + 2个黑色结点,于是从红色结点D出发到叶结点的普通路径就应该有n + 1个黑色结点,于是从黑色结点C出发到叶结点的普通路径就应该有n + 1个黑色结点,于是从黑色结点E出发到叶结点的普通路径就应该有n + 1个黑色结点。可先删除结点110。否则父结点到删除结点的空叶结点与到兄弟结点的空叶结点的黑结点数不同,由于兄弟结点为黑色,所以兄弟结点的唯一孩子结点必然为红色。原创 2025-02-27 20:51:07 · 1338 阅读 · 0 评论 -
JUC并发—14.Future模式和异步编程分析二
在uniApplyStage()方法中,首先会创建一个新的CompletableFuture对象,然后根据CompletableFuture的uniApply()方法判断源任务是否已经完成。源任务还没执行完成的处理过程具体如下:首先把回调任务封装成一个UniApply对象,然后调用CompletableFuture的push()方法,把UniApply对象压入源任务所在CompletableFuture对象中的stack的栈顶,最后调用UniApply的tryFire()方法来尝试执行该回调任务。原创 2025-02-26 23:35:52 · 1193 阅读 · 0 评论 -
JUC并发—14.Future模式和异步编程分析一
比如可以通过get()方法阻塞式获取异步任务的执行结果(可中断),比如也可以通过join()方法阻塞式获取异步任务的执行结果(不可中断),此外通过complete()方法可实现线程间的数据传递 + 唤醒被get()阻塞的线程。对于回调对象的执行,可以放到非任务线程中,也可以放到任务线程中。如果在FutureTask的run()方法中调用Callable接口的call()方法执行任务时,需要比较长的时间,那么为了能够正确获得返回值,Future接口的get()方法必须实现阻塞,直到call()方法执行完毕。原创 2025-02-26 23:33:10 · 988 阅读 · 0 评论 -
JUC并发—13.Future模式和异步编程简介
CompletableFuture的thenAccept()方法表示:第一个任务执行完成后,执行第二个任务(回调方法)时,会将第一个任务的执行结果作为入参,传递到第二个任务中,但是第二个任务是没有返回值的。CompletableFuture的thenApply()方法表示:第一个任务执行完成后,执行第二个任务(回调方法)时,会将第一个任务的执行结果作为入参,传递到第二个任务中,并且第二个任务是有返回值的。thenCompose()方法会在某个任务执行完成后,将该任务的执行结果作为方法入参去执行指定的方法。原创 2025-02-25 19:03:05 · 1046 阅读 · 0 评论 -
JUC并发—12.ThreadLocal源码分析
由于ThreadLocalMap只持有ThreadLocal对象的弱引用,此时没有任何强引用指向Heap堆中的Threadlocal对象,所以ThreadLocal对象就可以顺利被GC回收,此时Entry对象中的key = null。相对第一种方式,第二种方式显然更不好控制。在ThreadLocalMap的set(key, value)方法中,首先根据ThreadLocal对象的hashCode和数组长度进行位与运算(即取模),来获取set()方法要设置的元素,应该放置在数组的哪个位置(即数组下标i)。原创 2025-02-24 22:50:41 · 1004 阅读 · 0 评论 -
JUC并发—11.线程池源码分析
如果线程池的线程数量 >= corePoolSize,就不会创建新的线程和执行任务,此时唯一做的事情就是通过offer()方法把任务提交到阻塞队列里进行排队。然后通过Thread的start()方法启动这个新创建的线程,如果线程启动失败,则要从集合中删除新增的线程,并回退增加的线程数。然后在其构造方法中会通过getThreadFactory().newThread(this),将实现了Runnable接口的Worker传递进去来创建一个线程,这样新创建的线程的引用就会指向传递进去的Worker实例。原创 2025-02-23 23:11:57 · 1182 阅读 · 0 评论 -
JUC并发—10.锁优化与锁故障
通过这两级缓存,就可以大大降低每分钟并发读写缓存注册表的冲突次数,比如一级缓存就可以将每分钟并发读写冲突的次数从10次降为2次,因为一级缓存的数据最多每30秒更新一次。当服务注册表的某个key数据没有更新时,二级缓存的数据180秒后就会过期,过期的30秒后一级缓存的数据变为null。二级缓存(读写缓存)中的数据的过期策略是:当有服务实例发生注册、下线时,就会主动过期(更新)其对应的二级缓存。当占用部分资源的线程进一步申请其他资源时,如果申请不到,则可以主动释放其占有的资源,这样不可抢占条件就被破坏了。原创 2025-02-22 23:51:00 · 792 阅读 · 0 评论 -
JUC并发—9.并发安全集合四
某服务注册中心实例接收到服务实例A的请求时,首先会把服务实例A的服务请求信息存储到本地的内存注册表里,也就是把服务实例A的服务请求信息写到第一个内存队列中,之后该服务注册中心实例对服务实例A的请求处理就可以结束并返回。服务实例向任何一个服务注册中心实例发送注册、下线、心跳的请求,该服务注册中心实例都需要将这些信息同步到其他的服务注册中心实例上,从而确保所有服务注册中心实例的内存注册表的数据是一致的。当向线程池提交任务时,首先会把任务放入阻塞队列中,然后线程池中会有对应的工作线程专门处理阻塞队列中的任务。原创 2025-02-21 23:25:12 · 1397 阅读 · 0 评论 -
JUC并发—9.并发安全集合三
如果不使用写时复制机制,那么即便有写线程先更新了array引用的数组中的元素,后续的读线程也只是具有对使用volatile修饰的array引用的可见性,而不会具有对array引用的数组中的元素的可见性。此时其他读线程读到的(get或者迭代)都是数组array的数据,于是在同一时刻,读线程和写线程看到的数据是不一致的。CopyOnWrite就是写时复制。由于在写数据的时候,首先更新的是复制了原数组数据的新数组。所以同一时间大量的线程读取数组数据时,都会读到原数组的数据,因此读写之间不会出现并发冲突的问题。原创 2025-02-21 23:23:49 · 938 阅读 · 0 评论 -
JUC并发—8.并发安全集合二
首先通过tabAt()方法从Node数组中获取位置为index的元素并赋值给变量b,然后使用synchronized对Node数组中位置为index的元素b进行加锁,接着通过for循环遍历Node数组中位置为index的元素b这个链表,并且根据链表中每个结点的数据封装成一个TreeNode对象来组成新链表,最后把新链表的头结点作为参数传给TreeBin构造方法来完成红黑树的构建。首先计算dir的值。所以bound = 16,i = 31,当前线程负责的迁移区间为[bound, i] = [16, 31]。原创 2025-02-20 19:09:40 · 1605 阅读 · 0 评论 -
JUC并发—8.并发安全集合一
在JDK1.8中,HashMap采用数组 + 链表 + 红黑树的数据结构来存储数据,并且优化了JDK1.7中的数组扩容方案,解决了死循环和数据丢失的问题。比如HashTable使用synchronized来保证线程的安全性,比如Collections.synchronizedMap可以把一个线程不安全的Map,通过synchronized的方式,将其变成安全的。上述复合操作的安全问题的解决方案是,可以对复合操作加锁,也可以使用ConcurrentMap接口来解决复合操作的安全问题。原创 2025-02-20 19:09:17 · 1288 阅读 · 0 评论 -
JUC并发总结二
同时还会让线程2的工作内存中的flag变量的缓存过期,这样当线程2后续从工作内存里读取flag变量的值时,发现缓存已经过期就会重新从主内存中加载flag = 1的值。重量级锁的本质是:没有获得锁的线程会通过park()方法挂起,接着被获得锁的线程通过unpark()方法唤醒后再次抢占锁,直到抢占成功。加锁的方式是首先在每个线程的栈帧中分配一个Lock Record,然后把锁对象中的Mark Word拷贝到Lock Record中,最后把锁对象的Mark Word的指针指向Lock Record。原创 2025-02-20 18:47:22 · 534 阅读 · 0 评论 -
JUC并发总结一
大纲1.Java集合包源码2.Thread源码分析3.volatile关键字的原理4.Java内存模型JMM5.JMM如何处理并发中的原子性可见性有序性1.Java集合包源码(1)ArrayList源码总结如果不会频繁插入元素,导致频繁的移动元素位置、List扩容,而主要用于遍历集合或通过索引随机读取元素,那么可以用ArrayList。如果会频繁插入元素到List中,那么尽量还是不要用ArrayList,因为很可能会造成大量的元素移动 + 数组扩容 + 元素拷贝。remove()和add(index, el原创 2025-02-19 19:34:40 · 370 阅读 · 0 评论 -
JUC并发—7.AQS源码分析三
调用CountDownLatch的await()方法时,会先调用AQS的acquireSharedInterruptibly()模版方法,然后会调用CountDownLatch的内部类Sync实现的tryAcquireShared()方法。如果没有变化,则退出自旋。被阻塞的线程,除了会被CyclicBarrier的nextGeneration()方法唤醒外,还会被Thread的interrupt()方法唤醒、被中断异常唤醒,而这些唤醒会调用CyclicBarrier的breakBarrier()方法。原创 2025-02-19 19:31:35 · 984 阅读 · 0 评论 -
JUC并发—6.AQS源码分析二
对公平锁来说,用于判断读线程在抢占锁时是否应该阻塞的readerShouldBlock()方法,以及用于判断写线程在抢占锁时是否应该阻塞的writerShouldBlock()方法,都会通过hasQueuedPredecessors()方法判断当前队列中是否有线程排队。由于该first结点对应的线程在await()方法中加入Condition队列后被阻塞,所以该first结点对应的线程在被唤醒后,会回到await()方法中继续执行,也就是会执行AQS的acquireQueued()方法去尝试获取锁。原创 2025-02-18 22:29:32 · 922 阅读 · 0 评论 -
JUC并发—5.AQS源码分析一
如果当前结点的前驱结点不是队头结点或者当前线程尝试抢占锁失败,那么都会调用shouldParkAfterFailedAcquire()方法,修改当前线程结点的前驱结点的状态为SIGNAL + 决定是否应挂起当前线程。在AQS的release()方法中,首先会执行Sync的tryRelease()方法,而Sync的tryRelease()方法会通过递减state变量的值来释放锁资源。比如非公平锁的实现中,如果读线程在尝试获取锁时发现,AQS的等待队列中的头结点的后继结点是独占锁结点,那么读线程会阻塞。原创 2025-02-17 20:09:23 · 807 阅读 · 0 评论 -
JUC并发—4.wait和notify以及Atomic原理
最后如果通过自旋竞争锁失败,则把当前线程构建成一个ObjectWaiter结点,插入到ObjectMonitor的_cxq队列的队头,再调用park()方法阻塞当前线程。假设主内存的Long型变量X、Y已被CPU1和CPU2分别读入自己的缓存,且Long型变量X、Y在CPU缓存和主内存中都是放在同一行缓存行中的。这就是伪共享问题,缓存行上的不同变量,读CPU受到写CPU的影响。其中wait()方法会释放锁,并让当前线程进入等待状态,而notify()方法和notifyAll()方法会唤醒等待获取锁的线程。原创 2025-02-16 23:55:46 · 1084 阅读 · 0 评论 -
JUC并发—3.volatile和synchronized原理
于是此时线程1的操作栈顶的n值,就变成了过期数据,所以线程1执行put_field指令后就会把较小的n值同步回主内存中。加锁的方式是首先在每个线程的栈帧中分配一个Lock Record,然后把锁对象中的Mark Word拷贝到Lock Record中,最后把锁对象的Mark Word的指针指向Lock Record。如果有多个线程共享一个并未声明位volatile的long或double类型的变量,且同时对它们进行读取和修改,那么某些线程可能会读取到一个既不是原值,也不是其他线程修改值的"半个变量"的值。原创 2025-02-14 17:18:23 · 1137 阅读 · 0 评论 -
JUC并发—2.Thread源码分析及案例应用
所以如果想尽快停止系统,那么可用interrupt()方法打断工作线程的休眠,同时通过判断工作线程需要运行的标志位是否为false来立即退出线程。比如在main线程中创建一个继承了Thread的ServiceAliveMonitor线程,那么在创建该线程的过程中,通过currentThread()获取到的就是main线程。在Java里,每个线程都有一个父线程。如果main()方法启动后要开启几个无限循环工作的线程来处理一些请求,比如类似于Web服务器的,那么这些无限循环工作的线程就是工作线程。原创 2025-02-13 22:49:25 · 1177 阅读 · 0 评论 -
JUC并发—1.Java集合包底层源码剖析
此时再调用add()方法插入一个元素,也就是需要插入第11个元素,那么肯定是插入不进去的。虽然通过table[i]数组索引直接定位元素的时间复杂度是O(1),但如果链表存在大量元素,会是导致对该链表get()操作的性能急剧下降的。其中的分支代码"if ((p = tab[i = (n - 1) & hash]) == null)"的意思是:如果通过哈希寻址算法定位到的下标为i的数组元素为空(即tab[i]为空),那么就可以直接将一个新创建的Node对象放到数组的tab[i]这个位置。原创 2025-02-12 23:37:43 · 988 阅读 · 0 评论