-
wait 释放锁
-
notify 不释放锁
-
配合synchronized
-
CountDownLatch
-
volatile
案例
-
集合遍历问题(list)
-
共享成员变量
线程安全的集合
- Collections.synchronized***
编码规约
-
考虑代码健壮性
-
常见场景敏感性
常用处理方法
-
ThreadLocal
-
线程局部变量,是一种多线程间并发访问变量的解决方案。与synchronized等加锁方式不同,ThreadLocal完全不提供锁,而是以空间换时间的手段,为每个线程提供变量的独立副本,以保障线程安全。
-
从性能上说,ThreadLocal不具绝对优势,在并发不是很高的时候,加锁的性能会更好,但作为与锁完全无关的线程安全解决方案在高并发量或者锁竞争激烈的场景,使用ThreadLocal可以在一定程度上减少锁竞争问题。
-
Volatile
-
概念定义
-
volatile关键字主要作用是使变量的更改在多个线程间即时可见。
-
作用
-
在多个线程间可进行变量的变更,使得线程间进行数据的共享可见。
-
阻止指令重排序,happens-before语义
-
内存模型JMM
-
一个线程可以执行的操作有使用(use)、赋值(assign)、装载(load)、存储(store)、锁定()(lock)、解锁(unlock)
-
而主内存可以执行的操作有读(read)、写(write)、锁定(lock)、解锁(unlock),每个操作都是原子性的操作
-
volatile的作用就是强制线程到主内存(共享内存)里去读取变量,而不是线程工作内存里去读取数据,从而实现了多线程间的变量的可见,也就是满足了线程安全的可见性
-
解决的主要问题是一个线程对共享变量的写入何时对另一个线程可见
-
所有的变量都存储在主内存中,每一个线程都有一个私有的本地内存,本地内存中存储了该线程使用到的变量是主内存中的拷贝,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量(volatile变量也不例外)
-
内存屏障
-
指令重排序
-
程序指令的执行顺序可能与代码书写顺序不一致,这个过程就是指令重排序。
-
意义
-
JVM能根据处理器的特性,充分利用多级缓存、多核心等进行适当的指令重排序,是程序在保证业务正确运行的同时,充分利用CPU的执行特点,最大限度发挥机器性能
-
有些场景不合适指令重排序(使用volatile解决)
-
HappensBefore (JMM内存机制)
-
代码中一个操作执行结果需要对另一个操作可见,那么这两个操作之间必须要存在happens-before关系,使用这个语义来阐述操作之间的内存可见性。
-
相应规则
-
一个线程的每一个操作,happens-before于该线程中后续任意操作(写操作happens-before于其后的读操作)
-
对一个锁的解锁,happens-before与随后的对这个锁的加锁
-
对一个volatile域的写,happens-before于任意后续对这个volatile域的读
-
传递性规则:A h-b B 且 B h-b C 则 A h-b C
-
应用案例
-
停止线程
-
Atomic
-
概念定义
-
封装了一系列的基础类型和对象操作,主要目的是为了实现原子性操作(多线程操作的线程安全操作)
-
保证一个JVM中是原子性、(同时不能为复合操作,否则中间的操作读取复合操作部分可能不对 虽然最终结果是对的 可以通过加锁解决
-
主要核心类
-
AtomicInteger
-
AtomicLong
-
AtomicBoolean
-
AtomicIntegerArray
-
AtomicLongArray
-
AtomicReference
同步锁
-
Vector
-
HashTable
底层机制
-
都是由JDK的Collections.synchronized***等工厂方法去创建实现的
-
使用synchronized关键字对每个公用的方法都进行同步
-
使用Object mutex对象锁使得每次只能有一个线程访问容器的状态
问题
-
还可能出现(并发修改异常)
-
并发处理性能差
-
并发场景下,严重降低吞吐量
-
锁竞争
ConcurrentHashMap
-
基本使用
-
size()
-
消除伪共享(缓存行)
-
分之思想
-
volatile long value
-
基本原理
-
内部结构使用段(Segment)来表示不同的部分,每一段其实就是一个小的HashTable升级版,它们有自己的锁,降低了锁的粒度
-
只要多个修改操作发生在不同的段上,就可以并发进行,默认16段,所以最高支持16个线程的并发修改操作
-
减小锁的粒度达到降低锁竞争的方式,并且代码中大多共享变量采用volatile关键字声明,目的是第一时间获取修改的内容,性能非常好。
CopyOnWrite
-
基本使用
-
jdk中常用的
-
CopyOnWriteArrayList
-
ConyOnWriteArraySet
-
最佳使用场景
-
读多写少,元素不能太多
-
基本原理
-
往一个容器添加元素时,不直接往当前的容器添加,而是将当前容器进行Copy,复制出一个新容器,然后新容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。
-
采用读写分离的思想,读和写不同的容器,实现对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前这个容器不会添加任何的元素。
ConcurrentSkipListMap
- 支持并发排序
(顶层接口Queue)
-
并发队列(ConcurrentLinkedQueue)
-
适应高并发场景的队列,通过无锁的方式,实现高并发状态下的高性能,通常性能好于BlockingQueue,是一个基于链表的无界线程安全队列
-
该队列遵循先进先出原则,头是最先加入,尾是最后加入,该队列不允许null元素。
-
基本使用
-
add()和offer()都是添加元素方法,在这里这两个方法没有任何区别
-
poll()和peek()都是取头元素节点,区别在于前者是会删除元素,后者不会
-
阻塞队列(BlockingQueue)
-
基本使用
-
offer(anObject) 将anObject加到BlockingQueue里,即如果BlockingQueue可以容纳则返回true,否则返回false (不会阻塞线程)
-
offer(E e, long timeout ,TimeUint uint ) 设置等待时间,如果指定时间内,还不能往队列加入则返回失败。
-
put() 把anObject加入BlockingQueue里,如果BlockingQueue没有空间,调用此方法的线程会被阻塞直到队列里有空间为止。
-
poll(long timeout,TimeUint uint) 从队列里取出一个队首元素,如果在指定时间内,队列一旦有元素可取,则立即返回队列中的元素,否则直到时间超时还没有元素可取,返回失败。
-
take()取出BlockiQueue里排在队首的元素,若队列为空,阻塞进入等待状态直到队列有新的元素被加入。
-
drainTo() 一次性从队列获取所有可用的元素(还可以指定获取的个数),通过该方法,可以提高获取元素的效率,不需要多次分批加锁或释放锁。
-
常用实现
-
基于数组的阻塞队列实现ArrayBlockingQueue
-
内部维护一个定长数组,缓存队列中的数据元素,其内部没有实现读写分离,也就意味着生产和消费不能完全并行。长度需要定义,可以指定先进先出或者先进后出,由于是定长所以也叫有界队列。
-
子主题 2
-
LinkedBlockingDeque
-
PriorityBlockingQueue
-
基于优先级的阻塞队列,出队列是依据优先级
-
优先级的判断是通过构造函数传入的Compator对象来决定,也就是说传入的队列的元素必须实现Comparable接口,在实现PriorityBlockingQueue时,内部控制线程同步的锁采用的是公平锁,也一个无界队列。
-
元素不能为基本类型
-
SynchronousQueue
-
一种没有缓冲的阻塞队列
-
生产者生产的数据直接会被消费者获取并消费
-
DelayQueue
-
带有延迟时间的队列
-
其中的元素只有指定延迟时间到了,才能够从队列中获取到该元素,DelayQueue的元素必须实现Delayed接口,该队列是一个无界队列,应用场景:对缓存超时的数据进行移除,任务超时处理,空闲链接超时关闭等。
-
阻塞队列手写模拟
-
拥有固定长度的装载元素的容器
-
计数器统计容器的容量大小
-
当队列里面没有元素的时候需要执行线程等待
-
当队列元素已满的时候执行的线程也需要等待
-
合理的线程间通信
常见锁
-
同步锁
-
Lock
-
LockSupport
常见实现
-
对象锁
-
类锁
Unsafe
-
概念定义
-
由于Java不能直接访问操作系统底层,而是通过本地方法来访问。Unsafe类提供了硬件级别的原子操作
-
主要提供的功能
-
内存操作
-
allocateMemory
-
分配内存
-
reallocateMemory
-
扩充内存
-
freeMemory
-
释放内存
-
字段的定位与修改
-
可以定位对象某字段的内存位置也可以修改对象的字段值,即使它是私有的
-
挂起与恢复
-
将一个线程进行挂起是通过park方法实现的,调用park后,线程将一直阻塞直到超时或中断等条件出现
-
unpark可以终止一个挂起的线程,使其恢复正常。
-
整个并发框架中对线程的挂起操作被封装在LockSupport类中,这个类中有各种版本的pack方法,但最终都调用了Unsafe.park()方法
-
CAS操作(乐观锁)
-
Compare And Swap 简单来说就是比较并交换。
-
CAS包含三个操作数,内存位置(V)、预期原值(A) 和 新值(B)
-
如果内存位置的值与预期原值匹配,那么处理器会自动将该位置值更新为新值,否则,处理器不做任何处理,无论哪种情况,它都会在CAS指令之前返回该位置的值。
-
CAS有效的说明:当某个位置v应该包含值A,如果包含该值,则将B放到这个位置,否则,不用更改该位置,值需告诉这个位置现在的值即可
-
Java并发包(java.util.concurrent)中大量使用CAS操作,涉及到并发的方法都调用了sun.misc.Unsafe类方法进行CAS操作,在Unsafe中是通过compareAndSwapXXX方法实现的
AQS
-
AQS核心
-
概念定义
-
AbstractQueuedSynchronizer 抽象队列式的同步器,AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现底层依赖于它,比如:ReetrantLock/Semaphore/CountDownLatch
-
内部设计
-
它内部维护一个volatile int state(代表共享资源)和一个FIFO线程等待队列(多线程争用资源被阻塞时会放入此队列),这里volatile是核心关键字,具体volatile的语义,state的访问方式有三种
-
getState()
-
setState()
-
compareAndSetState()
-
AQS定义两者资源共享方式
-
独占式Exclusive
-
只有一个线程能执行(如:ReetrantLoack)
-
共享式Share
-
多个线程可同时执行,(如:Semaphore、CountDownLatch)不同的自定义同步器争用共享资源的方式也不同
-
CLH队列(FIFO)锁)
-
head、tail
-
核心方法
-
自定义同步器在实现时只需要实现共享资源state的获取和释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队),AQS已经在底层实现好了。自定义同步器实现主要实现如下几种方法
-
isHeldExclusively()
-
该线程是否正在独占资源,只有用到Condition才需要实现它
-
tryAcquire()
-
独占方式,尝试获取资源,成功则返回tru,失败则返回false
-
tryRelease()
-
独占方式,成功则返回true,失败则返回false
-
tryAcquireShared(int)
-
共享方式,尝试获取资源,负数表示失败,0表示成功,但没有剩余可以资源;正数表示成功,且有剩余资源
-
tryRelaeseShared(int)
-
共享方式,尝试获取资源,成功则返回true,失败则返回false
-
锁方式
-
公平锁
-
队列排队,先进先服务
-
非公平锁
-
ReetrantLock重入锁
-
state初始化为0,表示未锁定状态,A线程lock()时,会调用tryAcquire()独占该锁并将state+1,此后其他线程在tryAcquire()时就会失败,直到A线程unlock()到state=0(释放锁)为止,其他线程才能有机会获取该锁,当然释放锁之前,A线程自己是可以重新获取此锁的(state会累加)这就是可重入的概念,但要注意,获取多少次就要释放多少次,这样才能保证state能回到零态的
-
重入锁,在需要进行同步的代码部分加上锁定,不要忘记最后一定要释放锁定,不然会造成锁无法释放,其他线程进不了的问题
-
重载的有参构造可以选择公平锁和非公平锁实现方法
-
应用场景
-
重入锁需求
-
阻塞唤醒多个线程
-
Condition
-
可配合ReetrantLock实现阻塞唤醒线程通信
-
在使用Lock,可以使用一个等待、通知的类,它就是Condition,这个类一定是针对具体某一把锁的,也就是在只有锁的基础上才会产生Condition
-
我们可以通过一个Lockd对象产生多个Condition进行多线程间的交互,可以使得部分需要唤醒的线程唤醒,其他线程则继续等待通知
-
await、signal signalall
-
ReadWriteLock读写锁
-
ReetrantReadWriteLock
-
其核心是实现读写分离的锁,在高并发访问下,读多写少的情况下,性能高于重入锁
-
分成两把锁,读锁和写锁,在读锁下,多线程可以并发访问,但在写锁的时候只能一个一个顺序访问
-
口诀:读读共享,写写互斥,读写互斥
-
lockSupport(线程锁)
-
LockSupport提供park()和unpark()方法实现阻塞线程和解除线程阻塞,实现的阻塞和解除阻塞是基于‘许可(permit)’ 作为关联,permit相当于一个信号量。默认为0,线程之间不再需要一个Object获取其他变量存储状态,不需要关心对方状态
-
对象Object锁缺点:先阻塞再唤醒 配合synchronized一起使用
-
LockSupport静态方法 park和unpark
-
没有顺序限制
-
针对具体某个线程
-
不需要在同步代码块里,所以线程间也不需要维护一个共享的同步对象,实现线程间的解耦
-
unpark函数可以先于park调用,所以无需担心线程的执行先后顺序
-
底层调用Unsafe类的方法
-
减少锁竞争
-
避免死锁
-
减少锁的持有时间
-
减少锁的粒度
-
锁的功能分离
-
尽量使用无锁的操作,如原子操作(Atomic系列类),volatile关键字,CAS
-
AQS源码分析
-
acquire(int)
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
面试结束复盘查漏补缺
每次面试都是检验自己知识与技术实力的一次机会,面试结束后建议大家及时总结复盘,查漏补缺,然后有针对性地进行学习,既能提高下一场面试的成功概率,还能增加自己的技术知识栈储备,可谓是一举两得。
以下最新总结的阿里P6资深Java必考题范围和答案,包含最全MySQL、Redis、Java并发编程等等面试题和答案,用于参考~
重要的事说三遍,关注+关注+关注!
更多笔记分享
一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!
AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算
**
[外链图片转存中…(img-MLDGhGPJ-1712167084554)]
面试结束复盘查漏补缺
每次面试都是检验自己知识与技术实力的一次机会,面试结束后建议大家及时总结复盘,查漏补缺,然后有针对性地进行学习,既能提高下一场面试的成功概率,还能增加自己的技术知识栈储备,可谓是一举两得。
以下最新总结的阿里P6资深Java必考题范围和答案,包含最全MySQL、Redis、Java并发编程等等面试题和答案,用于参考~
重要的事说三遍,关注+关注+关注!
[外链图片转存中…(img-8cptKvDC-1712167084555)]
[外链图片转存中…(img-qyj0L0QF-1712167084555)]
更多笔记分享
[外链图片转存中…(img-FgpJeE4K-1712167084556)]
一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!
AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算