JAVA并发十二连招,你能接住吗?
21张图带你领略集合的线程不安全
https://mp.weixin.qq.com/s/bJ3DSeXfW2h40I5u2C81BA
个人珍藏的80道多线程并发面试题(1-10答案解析)
https://mp.weixin.qq.com/s/1PsHA8RbuMqLNHEdeHpUuQ
个人珍藏的80道多线程并发面试题(11-20答案解析)
https://mp.weixin.qq.com/s/PbiGclcypbc38F411Xn4bQ
阿里面试官问我Java线程和操作系统线程什么关系
https://mp.weixin.qq.com/s/Gxqnf5vjyaI8eSYejm7zeQ
Java 线程的状态及转换
https://mp.weixin.qq.com/s/6kAMFCXT46mWfxRdega1vA
创建线程有几种方式?
https://juejin.cn/post/6930237689870417927
wait/notify/notifyAll解析
https://juejin.cn/post/6934673900156485640
如何正确地停止一个线程?
https://www.cnblogs.com/greta/p/5624839.html
如何停止一个正在运行的java线程
http://ibruce.info/2013/12/19/how-to-stop-a-java-thread/
这些线程安全的坑,你在工作中踩了么?
https://juejin.cn/post/6950933916245049351
synchronized
Java并发——Synchronized关键字和锁升级,详细分析偏向锁和轻量级锁的升级_tongdanping的博客-CSDN博客
synchronized锁的是什么?说说对它的理解
修饰实例方法与对象时,需要获取的是实例对象的监视器锁。
修饰静态方法与class时,需要获取的是Class对象的监视器锁。
线程a访问实例对象的同步方法,线程b访问静态同步方法时,两者不互斥。
在JDK1.6以前,使用synchronized就只有一种方式即重量级锁,而在JDK1.6以后,引入了偏向锁,轻量级锁,重量级锁
线程是怎么知道某个对象是否被其他线程占有的?——通过对象头中的MarkWorld
在64位的虚拟机中:
为什么要有偏向锁?
大多数时候是不存在锁竞争的,常常是一个线程多次获得同一个锁,因此如果每次都要竞争锁会增大很多没有必要付出的代价,为了降低获取锁的代价,才引入的偏向锁。
线程获取到偏向锁的过程?
- 对象一开始处于无锁状态
- 线程1检查到是否偏向标志位为0,因此将其设置为1
- 记录抢到锁的线程ID
- 进入偏向状态
- 线程1再次进入该对象的同步方法时,检查到threadID指向自己,因此无需再次获得锁。
什么时候偏向锁升级为轻量级锁?
当有另外一个线程竞争获取这个锁时,由于该锁已经是偏向锁,当发现对象头 Mark Word 中的线程 ID 不是自己的线程 ID,就会进行 CAS 操作获取锁。
如果获取成功,直接替换 Mark Word 中的线程 ID 为自己的 ID,该锁会保持偏向锁状态;
如果获取锁失败,代表当前锁有一定的竞争,先进行偏向锁撤销,再将偏向锁将升级为轻量级锁。
偏向锁是怎么撤销的?
如果线程在全局安全点检查时,还需要执行同步方法,则先撤销偏向锁,再升级锁。如果线程已经执行完同步方法后,并有其他线程需要使用时,将偏向锁的拥有者切换为另外线程。
为什么要有轻量级锁?
轻量级锁考虑的是竞争锁对象的线程不多,而且线程持有锁的时间也不长的情景。因为阻塞线程需要CPU从用户态转到内核态,代价较大。因此考虑成用线程自旋代替阻塞。
轻量级锁什么时候升级为重量级锁?
自旋次数超过jvm最大自旋次数或者超过自适应次数
为什么要有重量级锁?
当线程竞争激烈时,大量线程自旋空转严重消耗cpu,自旋影响大于阻塞,那么这个时候轻量级锁就会膨胀为重量级锁。重量级锁把除了拥有锁的线程都阻塞,防止CPU空转。
重量级锁是怎么实现的?
当synchronized处于重量级锁时,MarkWorld存储了指向重量级监视器锁的指针,对象的监视器(monitor)锁对象由ObjectMonitor对象实现(C++),其数据结构如下:
ObjectMonitor() {
_count = 0; //用来记录该对象被线程获取锁的次数
_waiters = 0;
_recursions = 0; //锁的重入次数
_owner = NULL; //指向持有ObjectMonitor对象的线程
_WaitSet = NULL; //处于wait状态的线程,会被加入到_WaitSet
_WaitSetLock = 0 ;
_EntryList = NULL ; //处于等待锁block状态的线程,会被加入到该列表
}
线程在获取锁的几个状态的转换图:
- 当多个线程同时访问该方法,那么这些线程会先被放进_EntryList队列,此时线程处于blocking状态
- 当一个线程获取到了实例对象的监视器(monitor)锁,那么就可以进入running状态,执行方法,此时,ObjectMonitor对象的_owner指向当前线程,_count加1表示当前对象锁被一个线程获取
- 当running状态的线程调用wait()方法,那么当前线程释放monitor对象,进入waiting状态,ObjectMonitor对象的_owner变为null,_count减1,同时线程进入_WaitSet队列,直到有线程调用notify()方法唤醒该线程,则该线程重新进入EntryList
- 如果当前线程执行完毕,那么也释放monitor对象,进入waiting状态,ObjectMonitor对象的_owner变为null,_count减1
那么Synchronized修饰的代码块/方法如何获取monitor对象的呢?——不管是方法同步还是代码块同步都是基于进入和退出monitor对象来实现
Synchronized代码块同步在需要同步的代码块开始的位置插入monitorenter指令,在同步结束的位置或者异常出现的位置插入monitorexit指令(一个方法可以被分成多个代码块,因此需要明确加锁的范围)
Synchronized方法同步是检查方法的ACC_SYNCHRONIZED标志,如果有这个标志,那么线程在执行方法前会先去获取对象的monitor对象,如果获取成功则执行方法代码,执行完毕后释放monitor对象,如果monitor对象已经被其它线程获取,那么当前线程被阻塞。
在JVM中monitorenter和monitorexit字节码依赖于底层的操作系统的Mutex Lock来实现的,但是由于使用Mutex Lock需要将当前线程挂起并从用户态切换到内核态(synchronized锁升级优化 - 知乎)来执行,这种切换的代价是非常昂贵的。
锁升级可逆吗?
为了避免无用的自旋,轻量级锁一旦膨胀为重量级锁就不会再降级为轻量级锁了;
偏向锁升级为轻量级锁也不能再降级为偏向锁。
一句话就是锁可以升级不可以降级,但是偏向锁状态可以被重置为无锁状态。
一个synchronized跟面试官扯了半个小时
https://mp.weixin.qq.com/s/ts2Pjz3VpWm50kY-Ru7iTA
【对线面试官】synchronized
17 张图看穿 synchronized 关键字
说一说你对synchronized锁的理解?
https://juejin.cn/post/6844904191375179784
Java锁的膨胀过程以及一致性哈希对锁膨胀的影响
https://juejin.cn/post/6924276177276239879#heading-11
你知道Synchronized作用是同步加锁,可你知道它在JVM中是如何实现的吗?
关于 Synchronized 的一个点,网上99%的文章都错了
Synchronized 升级到重量级锁之后就下不来了?你错了!
https://mp.weixin.qq.com/s/SUzNj4MsRJJzkhiquuLy0Q
面试被问线程池,真香
https://mp.weixin.qq.com/s/xtVKp9fUoIs8d94-5P2pEA
你管这破玩意叫线程池?
https://mp.weixin.qq.com/s/OKTW_mZnNJcRBrIFHONR3g
【对线面试官】线程池
线程池七大参数+四种拒绝策略 + 如何合理配置线程数等简介
https://blog.csdn.net/nrsc272420199/article/details/106085365
Java线程池实现原理及其在美团业务中的实践
https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html
如何设置线程池参数?美团给出了一个让面试官虎躯一震的回答
https://mp.weixin.qq.com/s/EhBt44Rj0c5E-UVf742aGw
线程池到底设置多少线程比较合适?
https://mp.weixin.qq.com/s/e1slhQ7qMWeLGzJbeImywQ
1000 多个并发线程,10 台机器,每台机器 4 核,设计线程池大小
https://mp.weixin.qq.com/s/KHSi0M4sIN-rBbpAKfnBNw
ThreadPoolExecutor 线程池异常消失之刨根问底
https://juejin.cn/post/6925258524096200718
线程池运用不当的一次线上事故
https://mp.weixin.qq.com/s/Qpdxfal-m0KOUxLeMJlPxw
2W 字带你,深入一下线程池
血泪教训,线程池引发的内存泄露
https://mp.weixin.qq.com/s/0KGNDELlrfhTmvzdUz0bIg
记一次故障引发的线程池使用的思考
https://mp.weixin.qq.com/s/Pclus98-FOEVZE3p3AGGKw
【对线面试官】ThreadLocal
浅谈ThreadLocal
https://blog.csdn.net/qq_33591903/article/details/97790015
ThreadLocal使用不好,小心造成内存泄露!
https://blog.csdn.net/qq_33591903/article/details/106736396
ThreadLocal 内存泄漏问题探究
https://juejin.cn/post/6844904165383110664
面试官:说说你对ThreadLocal的了解
https://mp.weixin.qq.com/s/wABcn9euUVxJKLsSgJYR3Q
一个ThreadLocal和面试官大战30个回合
https://mp.weixin.qq.com/s/EIBxpgHOjWrCBl3xjS6OLQ
面试中再问到ThreadLocal,应该这么答
https://mp.weixin.qq.com/s/By6XM7QChuxSWH-U-7YbzQ
反制面试官 | 14张原理图 | 再也不怕被问 volatile!
https://juejin.cn/post/6876681462712631303
一个volatile跟面试官扯了半个小时
https://mp.weixin.qq.com/s/DiEftiV_kTOlR4YmD4pggg
volatile原理
volatile的特性,保证可见性与禁止指令重排序,但不能保证原子性
(1)如何保证可见性?
先谈谈JMM,即java内存模型
背景:处理器与内存的处理速度相差很大,因此在处理器与内存之间加入了高速缓存,带来了缓存一致性问题。
JMM的规定
所有的共享变量都存储于主内存,这里所说的变量指的是实例变量和类变量,不包含局部变量,因为局部变量是线程私有的,因此不存在竞争问题。
每一个线程还存在自己的工作内存,线程的工作内存,保留了被线程使用的变量的副本。
线程对变量的读,取都必须在工作内存中完成,而不能直接读写主内存中的变量。
不同线程之间也不能直接访问对方工作内存中的变量,线程间变量的值的传递需要通过主内存中转来完成。
被volatile修饰的变量,当线程a在自己的工作内存中修改了这个变量时,将会同步到主内存中,并让其他线程立即看到这个变量的最新值。
其他线程怎么立即感受到共享变量的变化的?
通过缓存一致性协议MESI
协议内容——当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。
每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。
不停的嗅探会导致什么问题?
导致总线风暴——不停的从主内存嗅探,无效的交互会导致总线带宽达到峰值。
(2)如何禁止指令重排序的?
为什么会有指令重排序?
为了提高性能,编译器和处理器常常会对既定的代码执行顺序进行指令重排序。
但不管怎么重排序,单线程下的执行结果不能被改变——这就是as-if-serial语义
编译器在生成指令的时候,在volatile修饰的变量的操作前或后加上内存屏障来禁止指令重排序的。
大白话聊聊Java并发面试问题之volatile到底是什么?
说说AQS原理吧
AQS主要是采用state,通过对state的CAS判断来获取锁和解锁,并且存在等待队列和条件等待队列来park相关线程之后入队等待,有公平和非公平两者模式来唤醒等待的线程。
为什么需要个AQS
我:主要是为了封装和抽象,通过封装了公共的方法,减少重复代码。
老板让只懂Java基本语法的我,基于AQS实现一个锁
https://mp.weixin.qq.com/s/1Nq_izUkOGmtvkIQ9c0QRQ
万字超强图文讲解AQS以及ReentrantLock应用
https://mp.weixin.qq.com/s/5bEQMmj7KAy2olIbBVZa9A
【对线面试官】AQS&&ReentrantLock
大白话聊聊Java并发面试问题之谈谈你对AQS的理解?
从ReentrantLock的实现看AQS的原理及应用
https://tech.meituan.com/2019/12/05/aqs-theory-and-apply.html
万长文字 | 16张图解开AbstractQueuedSynchronizer
https://mp.weixin.qq.com/s/Y4GbMdNmSDvHtomxtObRSg
通俗易懂的ReentrantLock
https://mp.weixin.qq.com/s/ks1-_tsTdWm1FEux42rgZw
ReentrantLock核心原理,绝对干货
https://mp.weixin.qq.com/s/gC8Uj8otLGnOKN6ryb1uZQ
深入分析AQS实现原理
https://segmentfault.com/a/1190000017372067
AQS工作原理分析
https://segmentfault.com/a/1190000014102756
基于AQS实现自己的同步工具
https://juejin.cn/post/6844904169619324941
漫画:什么是 CAS 机制?
漫画:什么是CAS机制?(进阶篇)
【对线面试官】 CAS
由浅入深C A S,小白也能与BAT面试官对线
https://mp.weixin.qq.com/s/GR7lLGp9JH4bsAgQB3uLrw
大白话聊聊Java并发面试问题之Java 8如何优化CAS性能?
实现生产者消费者模式
https://juejin.cn/post/6935815791329673253
线程的状态
Java线程的6种状态及切换(透彻讲解)_潘建南的博客-CSDN博客_线程状态
JDK的Thread源码中,将线程的状态分为6种:
- 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
- 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。
- 阻塞(BLOCKED):表示线程阻塞于锁。
- 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
- 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
- 终止(TERMINATED):表示该线程已经执行完毕。
注意点:
- 调用obj的wait(), notify()方法前,必须获得obj锁,也就是必须写在synchronized(obj) 代码段内
- t2调用t1.join()时,全程t2不会释放调用前已经持有的对象锁。t2首先要去获得以t1为对象的同步锁,接着join内部调用wait()方法,t2释放t1锁,并进入t1的等待队列中。t1线程运行结束后,在jvm层面会唤醒等待队列中的所有线程,因此t2被唤醒,继续执行剩余的代码。
- 执行Thread.sleep的线程,不释放锁进行定时等待
- 执行Thread.yield(),让出cpu时间片,但也不释放锁
synchronized与lock的比较
(1)synchronized为关键字,lock为一个接口
(2)synchronized可作用与实例对象、类、方法上,lock作用于Lock对象上
(3)synchronized:执行完同步方法释放锁或出现异常由jvm自动释放锁;lock必须手动加锁解锁,需要在finally中手动释放锁
(4)synchronized不可以判断是否成功获取锁,而lock可以(通过tryLock())
(5)都是可重入锁
synchronized
- 处于偏向锁时,偏向当前线程id
- 处于轻量级锁时,对象的Mark Word指向当前线程的栈帧
- 当处于重量级锁时,Mark Word指向monitor对象的起始地址,而在HotSpot中,monitor是由ObjectMonitor实现的,其中owner指向持有该对象的线程,recursions为重入的次数
lock通过对aqs中的state字段做加1减1的操作来实现可重入的
(6)synchronized为非公平锁,而lock的实现类ReentranLock(默认为非公平锁)可以指定公平性
ReentrantLock 在遇到竞争时有公平和非公平两种处理模式,在创建对象时可以指定。
当线程要获取锁失败时,会将线程添加到一个由链表实现的队列尾部,依次选取队列头部的线程运行。
非公平模式下,在还未将新来的线程添加到尾部时,如果当前没有正在运行的线程,就直接插队运行新线程了。
(7)synchronized为不可中断锁,而lock为可中断锁
(8)资源竞争不激烈,synchronized的性能优于ReetrantLock。资源竞争很激烈,synchronized的性能远低于ReetrantLock,且synchronized锁升级不可逆
大白话聊聊Java并发面试问题之公平锁与非公平锁是啥?
线程死锁
产生死锁的条件有四个:
-
互斥条件:所谓互斥就是进程在某一时间内独占资源。
-
请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
-
不剥夺条件:进程已获得资源,在末使用完之前,不能强行剥夺。
-
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
线程死锁是因为多线程访问共享资源,由于访问的顺序不当所造成的,通常是一个线程锁定了一个资源A,而又想去锁定资源B;在另一个线程中,锁定了资源B,而又想去锁定资源A以完成自身的操作,两个线程都想得到对方的资源,而不愿释放自己的资源,造成两个线程都在等待,而无法执行的情况。
要解决死锁,可以从死锁的四个条件出发,只要破坏了一个必要条件,那么我们的死锁就解决了。在java中使用多线程的时候一定要考虑是否有死锁的问题哦。
JAVA并发十二连招
不可不说的Java“锁”事
https://tech.meituan.com/2018/11/15/java-lock.html
你用对锁了吗?浅谈 Java “锁” 事
Java锁的那些事儿
https://mp.weixin.qq.com/s/dwpTgeVXyH9F7-dklk7Iag
面试官:说说CountDownLatch,CyclicBarrier,Semaphore的原理?
CountDownLatch的使用和原理解析
https://zhuanlan.zhihu.com/p/95835099
深入理解CyclicBarrier原理
https://blog.csdn.net/qq_39241239/article/details/87030142
【对线面试官】CountDownLatch和CyclicBarrier
Semaphore 使用及原理
https://zhuanlan.zhihu.com/p/98593407
LongAdder实现原理
https://blog.csdn.net/qq_33591903/article/details/106282228
CopyOnWrite思想
Java魔法类:Unsafe应用解析
https://tech.meituan.com/2019/02/14/talk-about-java-magic-class-unsafe.html
老大吩咐的可重入分布式锁,终于完美的实现了!!!
https://juejin.cn/post/6844904191219990535#heading-3