
JUC
文章平均质量分 95
Java高并发
-代号9527
逢山开路,遇水搭桥!纸上得来终觉浅,绝知此事要躬行。
展开
-
线程池的使用:批量导入、数据汇总、异步保存搜索记录
也可以使用CountDownLatch,线程池的线程每处理一个提交的任务,就让CountDownLatch减一(CountDownLatch初始化等待计数等于批次数),阻塞请求过来的那个线程,直到CountDownLatch减为0,最后统计下总耗时,返回给前端。在一个电商网站中,用户下单之后,需要查询数据,数据包含了三部分:订单信息、包含的商品、物流信息。搜索时,保存历史搜索记录。用户搜索一个keyword时,存历史搜索记录,不能影响到查询的性能,用异步去处理,将存历史记录的任务提交到线程池。原创 2024-06-12 17:08:22 · 1615 阅读 · 0 评论 -
【JUC】三十二、邮戳锁StampedLock
前面提到了从无锁 ⇒ 独占锁 ⇒ 读写锁,但读写锁存在写锁饥饿的情况。本篇邮戳锁(也称版本锁、票据锁)即是对读写锁的再一次演化。原创 2023-12-21 10:55:02 · 1094 阅读 · 0 评论 -
【JUC】三十一、AQS源码
同理,Node C进来,node C的前置节点为node B,不是head,都不用tryAcquire尝试抢锁了,直接执行两次shouldParkAfterFailedAcquire,把它前置节点node B的waitStatus从0改为-1,还是到这儿,把B的waitStatus由默认的0改为-1,返回false。此时,再回addWaiter方法进行下一次循环,尾节点tail不再为null,设置我最终要返回Node B的前置节点为oldTail,oldTail = tail,也即刚才的那个虚拟节点。原创 2023-12-19 21:33:29 · 937 阅读 · 0 评论 -
【JUC】三十、什么是AQS
AQS,即AbstractQueuedSynchronizer,抽象的队列同步器。AQS是JUC的基石,好比JVM之于Java。AQS是用来实现锁或者其它同步器组件的公共基础部分的抽象实现, 是重量级基础框架及整个JUC体系的基石,主要用于解决锁分配给"谁"的问题。AbstractOwnableSynchronizer(下面两兄弟的父类)AbstractQueuedSynchronizer:简称AQS,since1.5三者都是抽象类。原创 2023-12-15 19:28:29 · 1397 阅读 · 0 评论 -
【JUC】二十九、synchronized锁升级之轻量锁与重量锁
synchronized锁升级,目的还是实现一个性能优化,思想就是:先自旋,不行了再阻塞。一直都是围绕尽量避免内核态和用户态频繁切换来展开的。实际上是把之前的悲观锁(重量级锁)变成在一定条件下使用偏向锁以及使用轻量级(自旋锁CAS)的形式。太精辟了这句!道出了这几种锁的关系。另外,synchronized在修饰方法和代码块时,在字节码上实现方式有很大差异,但是内部实现还是基于对象头的MarkWord来实现的。原创 2023-12-14 21:31:14 · 1458 阅读 · 0 评论 -
【JUC】二十八、synchronized锁升级之偏向锁
如果第一个线程正在执行synchronized方法(处于同步块),它还没有执行完,其它线程来抢夺,该偏向锁会被取消掉并出现锁升级(升级为轻量锁),且此时轻量级锁由原持有偏向锁的线程持有,继续执行其同步代码,而正在竞争的线程会进入自旋等待获得该轻量级锁。Hotspot 的作者发现,大多数情况下:多线程的情况下,锁不仅不存在多线程竞争,还存在锁由同一个线程多次获得的情况,偏向锁就是在这种情况下出现的,它的出现是为了解决只有在一个线程执行同步代码块时提高性能。原创 2023-12-14 09:05:50 · 983 阅读 · 0 评论 -
【JUC】二十七、synchronized锁升级之无锁
关于synchronized同步,能用无锁结构就不要用锁;能锁块,就不要锁整个方法;能用对象锁,就不要用类锁。用锁能够保证数据的安全性,但性能下降。无锁,性能提升,但安全性下降,如何平衡?原创 2023-12-13 20:18:47 · 942 阅读 · 0 评论 -
【JUC】二十六、Java对象内存布局和对象头
64位虚拟机下,对象分代年龄占4bit,也就是说,最大是1111,对应十进制就是15,这也是前面说的15次以后(到最大了)会从新生区到养老区的原因。在HotSpot虚拟机里,对象在堆内存中的存储布局可以划分为三个部分:对象头(Header)、实例数据 (Instance Data)、对齐填充 (Padding),其中,对齐填充用于保证对象内存总长度是8个字节的倍数。在64位系统中,Mark Word占了8个字节,类型指针占了8个字节,一共是16个字节。类型指针只有4字节,而不是前面说的8字节的事,后面说。原创 2023-12-13 09:24:57 · 1098 阅读 · 0 评论 -
【JUC】二十五、ThreadLocal内存泄漏问题(强软弱虚四种引用)
不再会被使用的对象或者变量占用的内存不能被回收,就是内存泄露(累积可能导致OOM)。原创 2023-12-12 14:39:25 · 1161 阅读 · 0 评论 -
【JUC】二十四、线程局部变量ThreadLocal
(通过ThreadLocal对象的set方法,结果把ThreadLocal对象自己当做key放进了ThreadLoalMap中),每个线程要用到这个T的时候,用当前的线程去Map里面获取,如此,每个线程都拥有了自己独立的变量,人手一份,竞争条件被彻底消除,没有竞争一说,(自己用自己的变量不麻烦别人,不和其他人共享,人人有份,人各一份),主要解决了让每个线程绑定自己的值,通过使用get和set方法,获取默认值或将其值更改为当前线程所存的副本的值,从而。以上Demo里的线程每次都是新建的,所以这个无影响。原创 2023-12-11 16:21:17 · 1087 阅读 · 0 评论 -
【JUC】二十三、LongAdder:多线程计数的更优解
此时,base可以+1,cell0、cell1也可以做+1,再调add,此时Cell[] as不再等于null,且length-1=1>0(2次幂) ,继续看||后面的条件,a = as[getProbe() & m],算坑位,比如算到了cell1这个单元格,此时,假设cell1中有值,为1,做个CAS,x为1,则1改为2,返回true,取反为false,跳出方法,但+1也随着条件判断完成了。cellsBusy:初始化cells或者扩容cells需要获取锁,0表示无锁状态,1表示其他线程已经持有了锁。原创 2023-12-08 13:26:04 · 1451 阅读 · 0 评论 -
【JUC】二十二、常用原子类
原子类:java.util.concurrent.atomic,原子类支持原子操作,底层是CAS思想和Unsafe类,因此不用加synchronized,也可以在多线程下保证安全性。以银行账户对象为例,更新余额money,高并发下,需要加synchronized,但这样锁的是整个账户对象,能否对money属性单独进行原子更新。以上这个更新原子类,只能更新int类型属性,换AtomicReferenceFieldUpdater,更新引用类型属性。应用案例:多个线程来同时做自增,搭配辅助类CountDown。原创 2023-12-05 16:51:39 · 972 阅读 · 0 评论 -
【JUC】二十一、CAS比较并交换
CAS,即Compare And Swap的缩写,译:比较并交换,实现并发算法时常用到的一种技术。它包含三个操作数:内存位置、预期原值及更新值。如果相匹配,那么处理器会自动将该位置值更新为新值如果不匹配,处理器不做任何操作多个线程同时执行CAS操作只有一个会成功CAS有3个操作数,位置内存值V,旧的预期值A,要修改的更新值B当且仅当旧的预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做或重来一次,这种反复重试的行为就叫自旋。原创 2023-12-05 10:44:07 · 1031 阅读 · 0 评论 -
【JUC】二十、volatile变量的特点与使用场景
volatile变量不适合参与到依赖当前值的运算,比如i = i+1;i++之类的依靠volatile变量的可见性,其适合用于保存某个状态的Boolean值凭什么java写了一个volatile关键字,就可以让系统底层加入内存屏障?两者关系怎么勾搭上的?什么是内存屏障?内存屏障是一种 屏障指令,它使得 CPU或编译器对屏障指令的前和后所发出的内存操作执行一个排序的约束。也叫内存栅栏或栅栏指令。内存屏障能干嘛?阻止屏障两边的指令重排序写数据时加入屏障,强制将线程私有工作内存的数据刷回主物理内存。原创 2023-12-04 16:19:08 · 1023 阅读 · 0 评论 -
【JUC】十九、volatile与内存屏障
重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段,有时候会改变程序语句的先后顺序,重排后的指令不能改变原有的串行语义,因此不存在数据的依赖关系,则可以重排序,反之,禁止重排序。,这些指令就是上面的一个个组成人墙的警察,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作,避免代码重排序。再说有序性,即禁止进行重排。在写指令(类比代码中的set)之后插入写屏障,会强制把写缓冲区(线程的工作内存)的数据刷回到主内存中。原创 2023-11-30 20:32:07 · 1283 阅读 · 0 评论 -
【JUC】十八、happens-before先行发生原则
如果线程A的操作(x=5)先行发生于线程B的操作(y=x),或者说这两个事件存在先行发生原则,那y=5一定成立,反之则不一定,因为x=5的改变可能还没从A线程的工作内存刷回主内存,线程就暂时挂起了。现在有线程A和线程B,线程A(在时间上先)调用了setValue方法,然后线程B调用同一对象的getValue方法,那线程B的返回值是?:对一个volatile变量的写操作先行发生于后面对这个变量的读操作,前面的写对后面的读是可见的,这里的”后面“同样是指时间上的先后。一定是先发烧了,温度计才能检测到体温变了。原创 2023-11-30 17:42:39 · 1321 阅读 · 0 评论 -
【JUC】十七、JMM下的三大特性
我们定义的所有共享变量都储存在物理主内存中每个线程都有自己独立的工作内存,里面保存该线程使用到的变量的副本(主内存中该变量的一份拷贝)线程对共享变量所有的操作都必须先在线程自己的工作内存中进行后写回主内存,不能直接从主内存中读写(不能越级)不同线程之间也无法直接访问其他线程的工作内存中的变量,线程间变量值的传递需要通过主内存来进行(同级不能相互访问)原创 2023-11-30 15:29:13 · 940 阅读 · 0 评论 -
【JUC】十六、LockSupport类实现线程等待与唤醒
先发permit,再LockSupport.park(),就像持证上岗,或者高速公路的ETC,提前缴费买了通行证后走高速,遇到关卡一路通畅,此时park形同虚设。A2:因为凭证的数量最多为1,连续调用两次unpark并不会有两个凭证,而调用两次park却要消耗两个凭证,凭证不够,不能放行。可以发现,将notify先于wait执行,等待被唤醒的线程会陷入无限等待中,就像叫你起床的人,先走了,你睡着以后没人再叫你了。A1:因为unpark后线程t获得了一个凭证,之后线程t再调用park,就凭证消费,畅通无阻。原创 2023-11-29 19:18:26 · 995 阅读 · 0 评论 -
【JUC】十五、中断协商机制
interrupted和isInterrupted这两个方法底层调用的都是同一个方法(native),区别是传的值不同,静态方法传了true,即清理了中断标志位,实例方法isInterrupted则不会,因为传了false。此时,t1线程已经属于不活动的线程,或者说run方法执行结束了,线程进入死亡状态,此时标志位为false。因此,想中断一个线程,可手动调用该线程的interrupt方法(可以自己调,也可以别的线程调),该方法仅仅是将线程对象的中断标识设为true。原创 2023-11-29 13:48:27 · 1380 阅读 · 0 评论 -
【JUC】十四、synchronized进阶:Monitor
由此,每一个被锁的对象和Monitor对象关联,Monitor对象中又记录了当前持有锁的线程,当一个monitor对象被某个线程持有后,它便处于锁定状态,因此每个Java对象都可以成为一个锁。当多个线程同时访问一段同步代码时,首先会进入 _EntryList 集合,当线程获取到对象的monitor 后,进入 _Owner 区域,并把monitor中的_owner变量设置为当前线程,同时monitor中的计数器count加1。在Java虚拟机HotSpot中,monitor由ObjectMonitor实现,原创 2023-11-29 08:44:12 · 1127 阅读 · 0 评论 -
【JUC】十三、CompletableFuture的常用方法
两个任务都完成后,最终把两个任务的结果一起交给thenCombine来处理,先完成的任务先等着,等待其他分支完成任务。二者都是计算结果存在依赖关系时用的,即两个任务所在的线程串行化。接收任务的执行结果,并进行消费处理,无返回结果,相关方法:thenAccept。三目运算符,除非不支持并行,一般就是FrokJoinPool。原创 2023-11-28 10:20:53 · 935 阅读 · 0 评论 -
【JUC】十二、CompletableFuture(上)
针对前面提到的Future接口的实现类FutureTask的缺点,考虑传入一个回调函数,当任务完成时,自动去调用,since Java8,有了Future接口的新实现CompleteableFuture,CompletableFuture提供了一种观察者模式类似的机制,可以让任务执行完成后通知监听的一方。原创 2023-11-27 15:19:06 · 1037 阅读 · 0 评论 -
【JUC】十一、Future接口与其实现类FutureTask的优缺点
当主线程需要执行一个很耗时的子任务时,可通过Future把这个任务放到异步线程去执行,主线程接着做它的事或者先行结束,等过一会儿再去获取子任务执行的结果即可,这就是Future接口做的事。既然get容易阻塞,那就先调isDone,判断futureTask是否执行完成,完成再get,没完成就休息一会儿再调isDone看是否完成,轮询查看执行结果。Future接口,定义了操作异步任务执行的一些方法,如获取异步任务的执行结果、取消异步任务的执行、判断任务是否被取消、判断任务是否执行完毕等,原创 2023-11-23 10:42:23 · 213 阅读 · 0 评论 -
【JUC】十、ForkJoin
一个个任务执行在一个个线程上,倘若某一个任务耗时很久,期间其他线程都无事可做,显然没有利用好多核CPU这一计算机资源,因此,出现了"分而治之"的解决方案。原创 2023-11-21 21:35:39 · 123 阅读 · 0 评论 -
【JUC】九、线程池ThreadPool
对于FixedThreadPool和SingleThreadPool,代码底层用的阻塞队列是LinkedBlockingQueue类型的,队列长度为Integer.MAX_VALUE,可能堆积大量请求,导致OOM对于CachedThreadPool,其源码中写的最大线程数量为Integer.MAX_VALUE,创建大量线程,调度难度大且会OOM2, //常驻或核心线程数5, //最大线程数2L,new ArrayBlockingQueue(3), //阻塞队列。原创 2023-11-16 20:41:37 · 593 阅读 · 0 评论 -
【JUC】八、阻塞队列
队列放满了,put不进去了,put的线程阻塞(挂起),等可以put了,再唤起取没了,只能等待,取的线程阻塞(挂起),等可以取了,再唤起而亮点就在于什么时候阻塞线程,什么时候唤起线程,则由BlockingQueue一手包办。原创 2023-11-16 17:16:25 · 156 阅读 · 0 评论 -
【JUC】七、读写锁的演化 && 锁降级
演化流程: 无锁 一> 独占锁 一> 读写锁 一> 邮戳锁无锁情况下,多线程抢夺资源,造成混乱且有数据安全问题独占锁下,两个写线程不能共享,这很合理,但如果全是读的线程,也排队读,就有待改进了读写锁:读写互斥,写写互斥,合理,但优化成了写写可以共享,解决了上面的改进点。读多写少的时候,就很合适。但读写锁也有缺点:写锁饥饿问题、锁降级针对读写锁的缺点,出现邮戳锁StampedLock(后面章节写)原创 2023-11-15 21:45:13 · 725 阅读 · 0 评论 -
【JUC】六、辅助类
如果把Semaphore类看成我们前面并发编程步骤里的资源类,那它特殊的地方就是,这个资源类的一个对象有几把对象锁,是我们可以自己在构造方法里设置的,而普通的自定义资源类,想要n把对象锁,就得new上n个对象。原创 2023-11-15 16:47:23 · 243 阅读 · 0 评论 -
【JUC】五、线程的第三种创建方式 Callable
前面的Thread类或者Runnable接口方式创建线程,线程终止时,即run方法运行结束时,无法使线程返回结果,而Callable接口的优势正在于此。而java.lang.Runnable是void run( ) 方法:查看文档可以发现Thread的构造方法中,没有传Callable类型形参的,那就找个中间类:Thread可以传入Runnable,Runnable有个实现类叫FutureTask,它的构造方法可以传Callable,这就是个Thread和Callable的中间人。原创 2023-11-14 20:17:21 · 248 阅读 · 0 评论 -
【JUC】四、可重入锁、公平锁、非公平锁与死锁现象的排查
另外,lock与unlock必须成对,当然这里内层锁的unlock注释掉,也能运行成功,进入大门后你是可以自由活动的,但你少个unlock,后面线程再想lock,就等不到锁了,相当于你进门,自己休息好了再出来却不带钥匙就把门关了。如此:t1锁到对象2的时候,发现已被锁,则等待,而另一边:t2锁到对象1的时候,发现对象1已被锁,两个线程同时陷入无休止的等待…t1线程执行某同步代码块,用到了对象1的锁和对象2的锁,即t1线程需要先锁对象1,再锁对象2,全锁以后,算同步代码块执行结束,然后一下释放两个对象锁。原创 2023-11-14 16:38:09 · 134 阅读 · 0 评论 -
【JUC】三、集合的线程安全
拿到迭代器对象,要遍历集合时,modCount已经赋值给expectedModCount,而此时其他线程继续add,modCount+1,modCount和expectedModCount就不相等了。List接口的另一个实现类Vector,其add方法加了关键字,使用它可解决线程安全问题,但很古老了,since1.2,很少用了。解决办法类比上面的List,使用。同样一种古老的解决方案,可以用。解决办法类比List,用。原创 2023-11-13 15:51:47 · 617 阅读 · 0 评论 -
【JUC】二、线程间的通信(虚假唤醒与ReentrantLock的Condition)
可以看到,前面的例子中,AA线程执行完后,共享的那把对象锁被谁抢到,或者说接下来是哪个线程执行,这一点是随机的。这里要实现的就是将这个通信变成定制化的。Lock代替了synchronized,Condition接口替代Object类的wait、notify等监视器方法。注意,这里可能还有一个因唤醒不当而导致阻塞的情况,多次运行会出现:光标闪烁,但没有输出,也没有exit 0。,这就导致了wait前面的判断条件只有第一次生效,这就是虚假唤醒。案例:启动两个线程,实现对同一个数。发生这个错误的原因是,原创 2023-11-12 20:31:05 · 227 阅读 · 0 评论 -
【JUC】一、synchronized关键字与Lock接口
DOS窗口运行java HelloWorld,先启动JVM,JVM是一个进程,JVM启动一个主线程调用main方法,同时再启动一个垃圾回收线程来负责看护、回收垃圾。,甲在窗口1买票,乙在窗口2买票,谁也不用等谁 一个个售票窗口就像一个个栈,有自己独立的空间。如启动了10个线程,就会有10个栈空间,每个栈和每个栈之间互不干扰,各自执行各自的,这就是多线程并发。ReentrantLock即可重入锁,类比排队上厕所,进门后锁门,用完后解锁,下个人继续上锁,用完解锁。Java中,线程A和线程B,原创 2023-11-12 20:10:09 · 211 阅读 · 0 评论