并发集合编程
2025-11-30 12:50:28 0 举报
AI智能生成
并发编程相关内容
作者其他创作
大纲/内容
并发编程理论基础
多线程编程及常见问题
1、为什么需要多线程?
支持更多的处理核心,异步提高响应速度
2、Java多线程并发不安全指的是什么?
对同一个数据的修改操作丢失
3、Java多线程并发出现问题的根源是什么?
每个线程都有自己的私有本地内存,共享数据的实际操作是
存在一个主内存,主内存通过交流私有本地内存达到共享,存在读写误差
存在一个主内存,主内存通过交流私有本地内存达到共享,存在读写误差
并发&并行
4、并发编程如何处理线程之间的通信和同步?
JAVA内存模型(JMM)
5、JMM是干什么的?
解释:Java内存模型提供如何按需禁用缓存和编译优化的方法,具体方法包括
Synchronized,Volatile,Final以及happens-before
Synchronized,Volatile,Final以及happens-before
可以保证可见性,原子性,有序性
6、重排序是干什么的?happen-before规则解决了什么问题?
重排序提高性能,三重(编译器,指令集,内存系统),出现内存可见性问题。
happen-before规则提供了阻止重排序出错误的一些规则。
happen-before规则提供了阻止重排序出错误的一些规则。
7、happen-before规则?
1、单一线程顺序规则: 在一个线程内,在程序前面的操作happen-before于后面的操作
2、监视器锁规则: 对一个锁的解锁操作happen-before后面对同一个锁的加锁操作
3、volatile变量规则:对一个volatile变量的写规则happen-before于后面对这个变量的读操作
4、传递性规则:如果a happen-before b,b happen-before c 则 a happen-before c
5、线程start()规则:如果线程A执行操作ThreadB.start(),那么线程A的ThreadB.start()操作
happen-before于线程B中的任意操作
happen-before于线程B中的任意操作
6、Join规则:如果线程A执行操作ThreadB.join()并成功返回,那么B线程内的任意操作happen-before
于线程A从ThreadB.join()操作成功返回
于线程A从ThreadB.join()操作成功返回
7、线程中断规则:对线程interrupt()方法调用 happen-before 于被中断线程的代码检测中断事件的发生,
可以通过interrupted()方法检测到是否有中断发生。
可以通过interrupted()方法检测到是否有中断发生。
8、对象终结规则:一个对象的初始化完成(构造函数执行结束)happen-before 于它的finalize()方法开始。
关键字
Synchronized
1、用在普通方法上,锁是当前实例对象。
2、用在方法块上,锁是Synchronized()里指定的对象
3、用在静态方法上,锁是当前类的Class对象。
2、用在方法块上,锁是Synchronized()里指定的对象
3、用在静态方法上,锁是当前类的Class对象。
原理分析: 1、Synchronized加锁的对象有一个monitor计数器,方法的加减锁实现了可重入性原理
锁的优化
1、Java对象保存在内存中,由对象头(存储锁),实例数据,对齐填充字节三部分组成
2、对象头中的Markwoed存储对象的hashcode,常见状态有四种(轻量级锁,重量级锁,GC标记,偏向锁)
锁的升级和对比
一般情况锁只能升级,不能降级;
但是也存在特殊情况(HotSpot VM虚拟机会尝试在SWT的停顿中对空闲(hide)的重量级锁进行降级)
但是也存在特殊情况(HotSpot VM虚拟机会尝试在SWT的停顿中对空闲(hide)的重量级锁进行降级)
无锁:
偏向锁:解决同一个线程多次获取锁的情况,
轻量级锁:得不到锁就自旋等待(多次访问)
重量级锁:得不到锁就阻塞,
偏向锁:解决同一个线程多次获取锁的情况,
轻量级锁:得不到锁就自旋等待(多次访问)
重量级锁:得不到锁就阻塞,
Volatile
作用
当一个变量被声明为volatile,JMM确保变量修改后,所有线程看到的变量保持一致
可见性的实现
可见性解释:当一个线程修改了数据后,其他线程能够立刻得知这个修改。
volatile读写的实现:
当写一个Volatile变量的时候,会先在线程私有本地内存中进行修改,然后再刷新到主内存中
当读一个Volatile变量的时候,会先将线程私有本地内存无效化,再从主内存中读取共享变量
有序性的实现
Volatile的happen-before规则
写-读;程序顺序,传递性
volatile禁止重排序
V写:本地内存全部刷入主存
V读:本地全部失效,全部从主存中读取变量
V读:本地全部失效,全部从主存中读取变量
Final
使用:修饰方法,类,变量
blank final: 允许空白final 但是再第一次使用的时候被赋值
static final :定义时赋值,否则不通过
重排序禁止规则
并发线程基础
进程/线程
进程是操作系统分配资源的基本单位,是程序一次运行在操作系统中的动态概念
线程是操作系统调度的基本单位,理解为进程的实体,比进程粒度更小,真正执行在cpu上的单元
过程:启动java程序,创建一个进程,进程中有多个线程,线程有各自的计数器,堆栈,局部变量,运行在不同处理器上
线程优先级,状态
状态转换图
相关解释
优先级:就是一个可以指定线程可以多分还是少分时间片的属性。
线程的创建/中断/终止
线程创建方法
实现Runnable接口
子主题
实现callable接口
特点是有返回值
继承thread类
子主题
中断是一种属性,其他线程通过调用该线程的interrupt进行中断操作,
反过来该线程调用isInterrupt判断自己是否中断,并响应
反过来该线程调用isInterrupt判断自己是否中断,并响应
线程不是强制停止,A发出想要B中断信号,B自行选择停止,延后,忽略
目的:由于B实现的任务的不同,例如读写文件,A不清楚B的工作进度,需要B自行掌握停止时间
线程间的通信
Volatile
非原子性,可见性,有序性(重排序的内存屏障,缓存一致性的刷新操作)
特定的读写策略
轻量级,无上下文切换
Synchronized
被修饰后,确保同一时间内只有一个线程处于方法或者同步块中,保证可见性(原子性,可见性,有序性,监视锁+内存屏障)
重量级,有上下文切换
wait/notify
等待/通知机制
通知机制大致分三类
一直询问好了没
间隔固定时间就询问好了没
休眠一段时间,再问好了没
Thread.join
A调用B.join(),表示A需要等待B执行完成后,才会从B.join()处返回继续执行
ThreadLocal
线程变量,以ThreadLocal对象为key,任意对象为值得存储结构,底层在线程里维护了一个map,map的key为各种ThreadLocal对象
Java锁
Lock锁
Lock是面向锁的使用者,定义使用者与锁的交互接口,隐藏实现细节;
AQS面向锁的实现者,简化了锁的实现方法hi,屏蔽了同步状态管理,线程的排队,等待与唤醒等底层操作。
AQS面向锁的实现者,简化了锁的实现方法hi,屏蔽了同步状态管理,线程的排队,等待与唤醒等底层操作。
Lock对比Synchronized
可重入锁中的公平和非公平获取锁的实现
ReentranReadWriteLock读写锁
通过分离出读锁和写锁,并发性比其他排他锁要高一些。
锁降级:先获取写锁,再获取读锁,再释放读锁。(如果先解锁再获取读锁,可能在两者之间写锁被获取走了,你获取读锁会失败。)
ReentrantLock可重入锁
重入解释:重进入是指任意线程在获取到锁之后能够再次获取该锁而不会被锁所堵塞。
重入实现需要解决两个问题:1、线程再次获取锁 2、锁的最终释放(当锁上的计数器为0,表示锁成功释放)
公平锁和非公平锁
类的关系
LockSupport
提供基本的线程阻塞和唤醒的功能
Condition
是java提供的线程通信机制,允许创建多个等待队列实现更加复杂的线程通信场景
每个condition对象都包含这一个等待队列(FIFO),复用AQS.NODE,与同步队列中的节点一致。
AQS拥有一个同步队列和多个等待队列
2、等待
3、通知
AQS队列同步器⭐
AQS介绍
解释:(AbstractQueuedSynchronizer)使用一个int成员变量表示同步状态,
通过内置的FIFO队列来完成资源获取线程的排队工作‘
通过内置的FIFO队列来完成资源获取线程的排队工作‘
提供三个方法:getState(),setState(),compareAndSetState()
AQS核心原理讲解
Condition
Java主流锁介绍
线程要不要锁住同步资源?
锁住
悲观锁
对于同一个数据的并发操作,悲观锁认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。Java中,synchronized关键字和Lock的实现类都是悲观锁。
适合写操作多的
不锁住
乐观锁
乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作(例如报错或者自动重试)。
乐观锁在Java中是通过使用无锁编程来实现,最常采用的是CAS算法,Java原子类中的递增操作就通过CAS自旋实现的。
乐观锁在Java中是通过使用无锁编程来实现,最常采用的是CAS算法,Java原子类中的递增操作就通过CAS自旋实现的。
适合读操作多的
锁住同步资源失败,线程要不要堵塞?
堵塞
不堵塞
自旋锁
适应性自旋锁
多个线程竞争同步资源的流程细节有没有区别?
不锁住资源,多个线程中只能有一个能修改资源成功,其他线程会重试
无锁
同一个线程执行同步资源时自动获取资源
偏向锁
多个线程竞争同步资源时,没有获取资源的锁自旋等待锁释放
轻量级锁
多个线程竞争同步资源时,没有获取资源的线程堵塞,等待唤醒
重量级锁
多个线程竞争锁时,要不要排队?
排队
公平锁
多个线程按照申请锁的顺序来获取锁。
优点:等待锁的线程不会饿死
缺点:整体吞吐效率比非公平锁低
优点:等待锁的线程不会饿死
缺点:整体吞吐效率比非公平锁低
子主题
先尝试插队,插队失败再排队
非公平锁
多个线程加锁时直接尝试获取锁,获取不到才会到等待队列的队尾等待。
优点:整体吞吐效率高
缺点:等待队列中的线程可能会饿死
优点:整体吞吐效率高
缺点:等待队列中的线程可能会饿死
子主题
一个线程中多个流程能不能获取同一把锁?
能
可重入锁(递归锁)
同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象或者class),
不会因为之前已经获取过还没释放而阻塞。
不会因为之前已经获取过还没释放而阻塞。
其父类AQS中维护了一个同步状态status来计数重入次数,status初始值为0。用来计数重入的次数
不能
非可重入锁
非可重入锁死锁的情况
多个线程能不能共享一把锁
能
共享锁
共享锁是指该锁可被多个线程所持有。如果线程T对数据A加上共享锁后,则其他线程只能对A再加共享锁,不能加排它锁。获得共享锁的线程只能读数据,不能修改数据。
state字段表示持有锁的次数
不能
排他锁(独享锁)
排他锁是指该锁一次只能被一个线程所持有。如果线程T对数据A加上排它锁后,则其他线程不能再对A加任何类型的锁。获得排它锁的线程即能读数据又能修改数据。JDK中的synchronized和JUC中Lock的实现类就是互斥锁。
state字段32位高16位表示读,低16位表示写。
并发安全容器
ConcurrentHashMap
因为hashtable使用synchronized对整个对象加锁,例如将整个Hash表加锁,然后只有一个线程进行put操作
JDK1.5-1.7:ConcurrentHashMap使用分段机制Segment数组(每个Segment内部是HashEntry数组),即将整个hash表分成多个分段,每个Segment元素,类似一个hashTable,针对segment加锁即可,可以实现多线程put操作。
结构图
具体创建逻辑:是默认Segment【2】,segment数组长度为16,负载因子0.75,也就是插入第一个元素不会触发扩容,插入第二个元素会扩容,根据容量和并发数确定每个segment数组大小,segment是一组hashentry,创建一个segment对象并创建segment数组。
segment会扩容,但是ConcurrentHashMap不会扩容
JDK1.7之前使用分段锁机制实现,最大并发度收到Segment个数限制。
JDK1.8摒弃这种设置,选择与hashMap类似的数组+链表+红黑树方式实现,加锁采用CAS和Sysnchronized实现
JDK1.8摒弃这种设置,选择与hashMap类似的数组+链表+红黑树方式实现,加锁采用CAS和Sysnchronized实现
数据结构,(数组长度超过64才能有红黑树,不红红,黑路同)
put方法:
计算hash值;判断数组是否为空,初始化;判断位置是否为空是否可以放置;
如果槽点上有值,若是链表则遍历添加,若是红黑树则根据逻辑遍历添加;
最后判断整体是否需要将链表转化成红黑树。
(根据未初始化,空,扩容,链表,红黑树不同情况左处理)
计算hash值;判断数组是否为空,初始化;判断位置是否为空是否可以放置;
如果槽点上有值,若是链表则遍历添加,若是红黑树则根据逻辑遍历添加;
最后判断整体是否需要将链表转化成红黑树。
(根据未初始化,空,扩容,链表,红黑树不同情况左处理)
get方法:
1、计算hash值,根据值找到对应槽点
2、数组为空或null ,返回null
3、节点刚好是想要的,返回节点值
4、是红黑树或正在扩容,用find方法继续查找
5、是链表,则遍历链表查询。
1、计算hash值,根据值找到对应槽点
2、数组为空或null ,返回null
3、节点刚好是想要的,返回节点值
4、是红黑树或正在扩容,用find方法继续查找
5、是链表,则遍历链表查询。
CopyOnWirteArrayList
CopyOnWrite机制
CopyOnWrite含义:当容器需要被修改的时候,不能直接修改当前容器,是需要先将当前容器进行Copy,
复制出一个新的容器,然后修改新的容器,完成修改后,再将原容器的引用指向新的容器。这样就完成了整个过程
复制出一个新的容器,然后修改新的容器,完成修改后,再将原容器的引用指向新的容器。这样就完成了整个过程
优点:适合读多写少,迭代期间允许修改集合内容
缺点:内存占用问题(内存创建两个对象),数据一致性问题(副本赋值给旧本的过程中容易被其他线程读取旧值。)
使用场景
是CopyOnwrteArraySet的底层原理
是CopyOnwrteArraySet的底层原理
ConcurrentLinkedQueue
目的:
实现一个线程安全的队列有两种方式:一种是使用阻塞算法,另一种是使用非阻塞算法。使用阻塞算法的队列
可以用一个锁(入队和出队用同一把锁)或两个锁(入队和出队用不同的锁)等方式来实现。非阻塞的实现方式则可以
使用循环CAS的方式来实现。
实现一个线程安全的队列有两种方式:一种是使用阻塞算法,另一种是使用非阻塞算法。使用阻塞算法的队列
可以用一个锁(入队和出队用同一把锁)或两个锁(入队和出队用不同的锁)等方式来实现。非阻塞的实现方式则可以
使用循环CAS的方式来实现。
核心采用了CAS算法(wait-free算法),内部死循环+casNext方法 ,是乐观锁思想。
CAS(Compare-And-Swap)是一种无锁编程算法,它通过硬件指令来保证操作的原子性。CAS操作包含三个操作数:内存位置(V)、预期原值(A)和新值(B)。
CAS 的全称是 Compare-And-Swap,中文译为“比较并交换”。它是一种用于实现多线程同步的原子操作。
它的核心思想是:“我认为V的值应该是A,如果是,那么将V的值更新为B,否则不修改,并告诉我现在V的值实际是多少”。
这个操作是作为一条CPU原子指令来实现的,因此不会造成数据不一致的问题。
CAS 的全称是 Compare-And-Swap,中文译为“比较并交换”。它是一种用于实现多线程同步的原子操作。
它的核心思想是:“我认为V的值应该是A,如果是,那么将V的值更新为B,否则不修改,并告诉我现在V的值实际是多少”。
这个操作是作为一条CPU原子指令来实现的,因此不会造成数据不一致的问题。
Java中阻塞的队列BlockingQueue
实现原理:
1、生产者消费者原理,利用一个队列以及通知模式,拿取元素
1、生产者消费者原理,利用一个队列以及通知模式,拿取元素
Fork/join 框架
简介:
FFork/join框架是Java7 提供的一个用于并行执行任务的框架,是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大人物结果的框架。
FFork/join框架是Java7 提供的一个用于并行执行任务的框架,是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大人物结果的框架。
工作窃取算法:指某个线程从其他队列里窃取任务来执行。
目的:一些空闲线程与其干等着任务,不如主动帮其他线程干活
目的:一些空闲线程与其干等着任务,不如主动帮其他线程干活
双端队列,窃取其他线程从尾部拿,被窃取线程执行任务从头部拿
原子类和并发工具类
CAS
概念:
CAS对比交换,是一条cpu原子指令,起作用是比较两个值是否相等,然后原子地更新某个位置的值。
CAS是乐观锁,Synchronized是悲观锁。
CAS对比交换,是一条cpu原子指令,起作用是比较两个值是否相等,然后原子地更新某个位置的值。
CAS是乐观锁,Synchronized是悲观锁。
乐观锁的主要实现方式:Compare And Swap(比较与交换)
概念:
CAS是一种无锁算法。在不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步。java.util.concurrent包中的原子类就是通过CAS来实现了乐观锁。
CAS算法涉及到三个操作数:
需要读写的内存值 V。
进行比较的值 A。
要写入的新值 B。
当且仅当 V 的值等于 A 时,CAS通过原子方式用新值B来更新V的值(“比较+更新”整体是一个原子操作),否则不会执行任何操作。一般情况下,“更新”是一个不断重试的操作。
CAS是一种无锁算法。在不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步。java.util.concurrent包中的原子类就是通过CAS来实现了乐观锁。
CAS算法涉及到三个操作数:
需要读写的内存值 V。
进行比较的值 A。
要写入的新值 B。
当且仅当 V 的值等于 A 时,CAS通过原子方式用新值B来更新V的值(“比较+更新”整体是一个原子操作),否则不会执行任何操作。一般情况下,“更新”是一个不断重试的操作。
三大问题
ABA问题
A1-B-A2情况,在执行A-C的时候,以为是A1-C其实是A2-C,中间数据丢失
举例
解决办法:
使用版本号;1A-2B-3A,比较当前引用是否等于预期引用,当前标志是否等于预期标志,如果两个比较都相等,再原子操作修改数据
使用版本号;1A-2B-3A,比较当前引用是否等于预期引用,当前标志是否等于预期标志,如果两个比较都相等,再原子操作修改数据
自旋CAS循环时间长,开销大
只能保证一个共享变量的原子操作
解决:使用循环CAS保证原子性,但是多个共享变量操作时,循环CAS无法保证操作原子性,考虑用锁
Atomic原子类
CountDownLatch
CyclicBarrier
Semaphore
Java线程池和Executor框架
Java线程池原理
线程池的目的:
降低资源消耗;
提高响应速度;
提高线程的可管理性;
降低资源消耗;
提高响应速度;
提高线程的可管理性;
任务调度流程
堵塞队列
获取任务流程图
Excutor框架
ThreadPoolExecution详解
ScheduledThreadPoolExecution详解
FutureTask详解
收藏
收藏
0 条评论
下一页