并发必学必会,一图概览
2024-01-18 18:32:26 4 举报
AI智能生成
并发必备知识点
作者其他创作
大纲/内容
线程状态
NEW(初始化状态)
RUNNABLE(可运行 / 运行状态)
BLOCKED(阻塞状态)
WAITING(无时限等待)
TIMED_WAITING(有时限等待)
TERMINATED(终止状态)
RUNNABLE(可运行 / 运行状态)
BLOCKED(阻塞状态)
WAITING(无时限等待)
TIMED_WAITING(有时限等待)
TERMINATED(终止状态)
销毁
interrupt
stop
run方法结束,正常结束
并发特性
原子性
synchronized,atomicxxx,lock
可见性
synchronized,volatile,Lock,final,内存屏障
有序性
synchronized,volatile,Lock,内存屏障
JMM
通信机制
内存共享
消息传递
wait/notify
JUC包中实现的,synchronized一起使用
Condition
设计猜想
作用:实现线程的阻塞和唤醒
前提条件:必须要获得锁
await/signal:signalAll
await让线程阻塞,并且释放锁
signal唤醒阻塞的线程
加锁的操作必然会涉及AQS阻塞队列
await释放锁的时候,AQS队列中不存在已经释放锁的线程
signal唤醒呗阻塞的线程
通过await方法释放的线程,必须要有一个地方阿里i存储并且需要阻塞,这样signal可以从这里唤醒,然后把这个线程放入AQS队列
流程
await
释放锁
让释放锁的线程应该被阻塞
被阻塞之后要存储到队列中
被阻塞之后要存储到队列中
重新去竞争锁--AQS
要能够处理interrupt终端响应
singal
要把被阻塞的线程先唤醒
把等待队列中的线程转移到AQS队列中
再回到await方法,抢占锁即可
实际应用
生产者消费者
流量缓冲
阻塞队列
内存模型
重排序
为了程序的性能,处理器编译器都会对程序进行重排序处理
条件
单线程环境下不能改变程序运行结果
存在数据依赖关系的不允许重排序
问题
导致数据不安全
CPU层面如何导致指令重排序的
CPU修改数据的时候需要通知其他CPU失效,然后在保存,等待其他CPU返回结果的时候是阻塞的
为了优化这个问题,让CPU一直处于运行状态,采用异步机制,引入Store Buffer
为了优化这个问题,让CPU一直处于运行状态,采用异步机制,引入Store Buffer
但这样就出现的指令重排序的问题
比如a=1;b=a+1;assert b==2 false
比如a=1;b=a+1;assert b==2 false
Store Fowarding
(存储转发)是CPU中的一种性能优化技术。它的作用是在写操作(将数据存储到内存)之后和随后的读操作(从内存读取数据)之间直接转发数据。这意味着,如果一个CPU指令写入的数据紧接着就要被另一个指令读取,那么这个数据可以直接从写操作转发到读操作,而不需要先写入内存再从内存中读取。这样做可以减少延迟,提高CPU处理数据的效率。简而言之,Store Forwarding是一种在连续的写和读操作之间提高数据处理速度的方法。
invalid Queue
CPU 优化过程
顺序一致性
多线程环境下的理论参考模型,为程序提供了极强的内存可见性保证
特性
一个线程中的所有操作必须按照程序的顺序阿里执行
所有线程都只能看到一个单一的操作执行顺序,不管程序是否同步
每个操作都必须原子执行且立刻对所有线程可见
as-if-serial
操作可以被优化而重排序,但执行结果一定不会发生变化
happens-before
JMM 中的核心理论,保证内存的可见性
如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须存在happen-before的关系
如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须存在happen-before的关系
规则
传递性规则
a happens-before b
b happens-beford c
那么 a happens-before c
b happens-beford c
那么 a happens-before c
volatile变量规则
监视器锁规则
start规则
线程A的start 方法之前的操作 happens-before start方法之后的操作
join规则
线程的join方法 之前的操作 happens-before join方法之后的操作
内存屏障
Lock指令
不同CPU架构中,实现内存屏障的指令不同
#Lock
#StoreBuffer
#LoadBuffer
JMM
CPU资源利用率优化
增加高速缓存
一致性问题
总线锁
缓存锁MESI
MESI,MOSI缓存一致性协议
MESI表示缓存的四种状态
修改
独占
共享
失效
修改的时候将处于共享状态的数据处于失效状态
伪共享问题
当不同的处理器核心尝试访问存储在同一缓存行中的不同变量时,会导致性能低下。这是因为当一个核心修改了缓存行中的数据,其他核心中的相同缓存行会被标记为无效,迫使它们重新从主内存读取数据。
对齐填充
为了减少伪共享,可以在数据结构中引入填充,以确保每个核心使用的数据位于不同的缓存行中。这通常通过增加额外的空间(如额外的字节),将数据成员对齐到缓存行的边界,从而确保它们不会与其他数据共享同一缓存行。这种做法虽然增加了内存使用,但可以显著提高多核处理器中的数据访问效率。
Synchronized
抢占锁的本质
只能由一个线程获取,获得是锁的线程被释放了,其他线程才能获得锁,并保证共享变量的内存可见性
作用范围
实例方法
作用于当前实例加锁,进入同步代码前要获得当前实例的锁
静态方法
作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁
代码块
指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
本质是对象的生命周期
实现机制
对象头
内容
mark-word
对象标记字段占4个字节,用于存储一些列的标记位,比如:哈希值、轻量级锁的标 记位,偏向锁标记位、分代年龄等。
class Pointer
Class对象的类型指针,Jdk1.8默认开启指针压缩后为4字节,关闭指针压缩(XX:-UseCompressedOops-
)后,长度为8字节。其指向的位置是对象对应的Class对象(其对应的元数据对象)的内存地址。
)后,长度为8字节。其指向的位置是对象对应的Class对象(其对应的元数据对象)的内存地址。
对象实际数据
包括对象的所有成员变量,大小由各个成员变量决定,比如:byte占1个字节8比 特位、int占4个字节32比特位。
对齐
最后这段空间补全并非必须,仅仅为了起到占位符的作用。由于HotSpot虚拟机的内存管理系统要求对象起始地址必须是8字节的整数倍,所以对象头正好是8字节的倍数。因此当对象实例数据部分没有对齐的话,就需要通过对齐填充来补全。
mark word标记字段
klass pointer类型指针
klass pointer类型指针
hashcode,gc分代年龄,锁状态标志,锁类型,偏向锁ID,偏向时间戳
monitor
owner
初始时为null,表示当前没有任何线程拥有该monitor record,当线程成功拥有该锁后保存线程唯一标识,当锁呗释放时又置位null
锁升级
流程
无锁
指并没有对资源进行加锁,既没有加Synchronized等关键字进行加锁,所有线程都能访问并修改同一个资源,但同时只有一个线程能修改成功。
偏向锁
可以设置无延迟时间启动:-XX:BiasedLockingStartupDelay=0
也可以不启动偏向锁:-XX:-UseBiasedLocking = false
也可以不启动偏向锁:-XX:-UseBiasedLocking = false
指当一段同步代码一直被同一线程访问,即不存在多个线程的竞争时,那么该线程在后续访问时便会自动获得锁,从而降低锁带来的消耗,提高性能。
使用Synchronized对代码块进行加锁,初次执行到加锁代码块时,当前线程判断到当前锁对象处于无锁状态,
则通过CAS将对象头中是否偏向锁的标志位改为1,并记录当前偏向锁的线程ID,以及偏向时间戳。锁对象变成偏向锁
则通过CAS将对象头中是否偏向锁的标志位改为1,并记录当前偏向锁的线程ID,以及偏向时间戳。锁对象变成偏向锁
当该线程再次执行到加锁代码块时,线程会判断对象头Mark Word中偏向锁的线程ID是否就是自己,如果是则正常往下执行
自旋锁
该线程等待一段时间,不会被立即挂起,看持有锁的线程是否会很快释放锁(循环方式)
自旋次数控制 -XX:preBlockSpin
存在理论:线程频繁挂起,唤醒是负担较重,可以认为每个线程站有锁的事件很短,线程挂起再唤醒得不偿失
缺点:自旋次数无法确定
适应性自旋锁
自旋的次数不再是固定的,由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定
自旋成功,可以增加自旋次数,如果经常失败,次数会减少
轻量级锁
自旋避免线程阻塞
指当锁是偏向锁,且被占用时,却被另一个线程访问,此时偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,线程不会阻塞,从而提高性能。轻量级锁用于线程不用等待很久就能成功获取锁的情况。
当出现以下情况时,轻量级锁将会升级为重量级锁:
线程自旋一定次数(默认10次)
一个线程在持有锁,一个在自旋,又有第三个线程尝试获取锁时
线程自旋一定次数(默认10次)
一个线程在持有锁,一个在自旋,又有第三个线程尝试获取锁时
1.6之后自适应自旋
适应性意味着自旋次数不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者状态来决定
自旋成功,则可以增加自旋次数,如果获取锁经常失败,那么自旋次数会减少
重量级锁
用户态到内核态的交换
没有获得锁的线程会阻塞,再被唤醒
没有获得锁的线程会阻塞,再被唤醒
流程
默认情况下是偏向锁是开启状态,偏向的线程ID是0,偏向一个Anonymous BiasedLock
如果有线程去抢占锁,那么这个时候线程会先去抢占偏向锁,也就是把markword的线程ID改为当前抢占锁的线程ID的过程
如果有线程竞争,这个时候会撤销偏向锁,升级到轻量级锁,线程在自己的线程栈帧中会创建一个 LockRecord,用CAS操作把markword设置为指向自己这个线程的LR的指针,设置成功后表示抢 占到锁。
如果竞争加剧,比如有线程超过10次自旋(-XX:PreBlockSpin参数配置),或者自旋线程数超过 CPU核心数的一般,在1.6之后,加入了自适应自旋Adapative Self Spinning. JVM会根据上次竞争 的情况来自动控制自旋的时间。升级到重量级锁,向操作系统申请资源, Linux Mutex,然后线程被挂起进入到等待队列。
锁膨胀
当多个线程尝试访问同一个资源时,Java虚拟机(JVM)会将最初的轻量级锁(如偏向锁)升级为更重的锁类型(如重量级锁)。这种升级过程被称为锁膨胀。目的是在不同线程竞争激烈时提供更稳定的锁行为,以减少线程之间的竞争。
锁粗化
将多个连续的加锁,解锁操作连接在一起,扩展成一个范围更大的锁,例如for循环内部获得锁
锁消除
JVM通过分析代码,在运行时确定某些锁对象不可能被不同线程同时访问。在这种情况下,JVM会去除这些不必要的锁操作,优化性能。锁消除是一种代码优化手段,通过消除不必要的锁操作来提高程序执行效率。
Volatile
特性
可见性:对一个volatile的读总可以看到对这个变量最终的写
原子性:volatile对单个读/写具有原子性,但是复合操作i++除外
实现机制
内存屏障
内存语义
当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值立即刷新到主内存中
当读一个volatile变量时,JMM会把该线程对应的本地内存设置为无效,直接从主内存中读取共享变量
操作系统语义
主存,高速缓存一致
MESI协议
总线LOCK加锁
内存模型
重排序
happens-before
DCL单例模式
CAS
当前值
预期值
更新值
compareAndSwap(old,expect,update)
缺点
循环时间过长
只能保证一个共享变量原子操作
ABA问题
版本号解决
AtomicStampedReference
AQS
流程
AbstractQueuedSynchronizer, JUC核心组件同步器
采用模版方法模式,实现大量通用方法,子类通过继承实现抽象方法来管理同步状态
采用模版方法模式,实现大量通用方法,子类通过继承实现抽象方法来管理同步状态
CHL同步队列
FIFO双向队列,AQS依赖它来解决同步状态的管理问题
首节点唤醒,等待队列加入CLH同步队列的尾部
同步状态获取与释放
独占式
获取锁
acquire获取同步状态
acquireInterruptibly响应中断
tryAcquireNanos超时获取
释放锁
release
共享式
获取锁
acquireShared
释放锁
releaseShared
线程阻塞和唤醒
当有线程获取锁了,其他再次获取时需要阻塞,当线程释放锁后,AQS负责唤醒线程
LockSupport
用来创建锁和其他同步列的基本线程阻塞原语
每个使用LockSupport的线程都会与一个许可关联,如果该许可可用,并且可在进程中使用,则调用park将立即返回
否则可能阻塞,如果许可尚不可用,则可以调用unpark使其可用
否则可能阻塞,如果许可尚不可用,则可以调用unpark使其可用
ReentrantLock
可重入,公平&非公平,阻塞&不阻塞,互斥锁,底层采用AQS实现
实现原理
设计猜想
有一个全局变量,标记来实现互斥
抢到了,怎么处理
没抢到,怎么处理
需要等待(让处于排队中的线程,如果没有抢占到锁,则先阻塞,这样可以释放CPU资源)-- wait/notify(无法唤醒指定的某个线程) park/unpark condition
需要排队(允许有N个线程被阻塞,此时线程处于活跃状态)--通过一个数据结构,把排队的线程储存起来
释放锁,怎么处理
唤醒线程
公平性
公平
非公平
基于AQS实现的
代码
ReentrantReadWriteLock
读写锁,两把锁
读锁:共享锁
写锁:排他锁
读锁:共享锁
写锁:排他锁
公平&非公平,可重入
锁降级
遵循获取写锁,获取读锁在释放写锁的次序,写锁能够降级为读锁
Condition
Lock提供条件Condition,对线程的等待,唤醒操作更加详细灵活
内部维护一个Condition队列,当前线程调用await,会以当前线程构造成一个节点,并将节点加入队列尾部
并发工具类
CountDownLatch
完成一组正在其他线程中执行的操作之前,允许一个或多个线程一直等待,
用给定的计数器,初始化CountDownLatch,由于调用countDown()方法,所以在当前计数到达零之前,await方法会一直阻塞。
之后,会释放所有等待的线程,await的所有后续调用都将立即返回
这种现象只 出现一次,计数不可以被重置
用给定的计数器,初始化CountDownLatch,由于调用countDown()方法,所以在当前计数到达零之前,await方法会一直阻塞。
之后,会释放所有等待的线程,await的所有后续调用都将立即返回
这种现象只 出现一次,计数不可以被重置
共享锁,允许多个线程同时抢占锁,等到计数器归零,同时唤醒
应用场景:计数器
与CyclicBarrier的区别
CountDownLatch的作用是允许1或N个线程等待其他线程完成执行,而CyclicBarrier则是允许N个线程相互等待
CountDownLatch计数器不能重置,CiclicBarrier可以被重置,所以是循环的Barrier
Semaphore
信号灯,一个控制访问多个共享资源的计数器
应用场景:限流器,限制资源的访问
基于共享锁
CyclicBarrier
可重复的栅栏
允许一组线程互相等待,直到到达某个公共屏障点
让一组线程到达一个屏障时被祖泽,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活
相当于多个线程通过CountDownLatch的await,然后另外一个线程使用countDown来唤醒
让一组线程到达一个屏障时被祖泽,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活
相当于多个线程通过CountDownLatch的await,然后另外一个线程使用countDown来唤醒
应用场景:多线程结果合并的操作,计算数据
Exchanger
可以在对列中对元素进行配对和交换的线程的同步点
允许在并发任务之间交换数据。Exchanger类允许在两个线程之间定义同步点,当两个线程都到达同步点时,交换数据结构
因此第一个线程的数据结构进入第二个线程中,第二个线程的数据结构进入第一个线程中
因此第一个线程的数据结构进入第二个线程中,第二个线程的数据结构进入第一个线程中
ThreadLocal
原理
线程隔离,在当前线程范围内建立副本,实现线程安全性
get/set会调用replaceStaleEntry方法,清理无效数据
清理失效key并解决hash冲突
向前有脏Entry向后有可覆盖Entry
向前没有脏Entry向后有可覆盖Entry
向前有脏Entry向后没有可覆盖Entry
向前没有脏Entry向后没有可覆盖Entry
解决多线程环境下成员便利店问题。为每一个线程创建一个单独的副本,每个线程独立改变自己的变量副本,不会影响其他线程
四个方法
get 返回此线程局部变量的当前线程副本中的值
initailValue 返回此线程局部变量的当前线程的初始值
remove 移除此线程局部变量的当前线程的值
set 将此线程局部变量的当前线程副本zhon工单值设置为指定值
TreadLocalMap
实现线程隔离机制的关键
每个Tread内部都有哦一个TreadLocal.TreadLocalMap类型的成员变量,用来存储实际ThreadLocal变量副本
Key是当前TreadLocal对象,value是变量副本
内存泄露
因为key是弱引用,value是强引用,无法回收,所以每次使用完成都要remove
Fork/Join
任务的拆分与结果的合并,分治思想,fork拆分,join合并
API
recursiveAction 没有返回值
recursiveTask 有返回值
CounterCompleter 有回调
工作队列
workQueue双端队列,本线程从尾部取任务,其他线程从头部偷任务
核心类
ForkJoinPool 执行任务的线程池
ForkJoinTask 表示任务,用于ForkJoinPool的任务的抽象
ForkJoinWorkerThread 执行任务的工作线程
线程池
线程池设计原理
如何复用
让线程一直处于运行状态
如何执行
共享内存,可以在线程间传参
没有任务阻塞,有任务执行
结论
通过阻塞队列来实现线程的复用
核心:池化技术,复用
优点
降低资源小号
提高响应速度
提高线程的可管理性
Executors
newFixedThreadPool
固定线程数
核心和最大线程数一致
无界队列 LinkedBlockingQueue
最大核心数,存活时间,拒绝策略都无效
newSingleThreadExecutor
根据需要创建新线程
核心为0,最大为Integer.MAX_VALUE
SynchronousQueue为队列
主线程提交任务速度过快,会不断创建线程,耗尽CPU和内存
newCachedThreadPool
单线程,核心和最大线程数为1
无界队列 LinkedBlockingQueue
newScheduledThreadPool
给定的延迟之后运行任务,或者定期执行任务
DelayQueue为队列,内部封装PriorityQueue,会对ScheduledFutureTask排序
newWorkStealingPool
fork/join
参数
核心线程数
最大线程数
存活时间
存活时间单位
阻塞队列
ArrayBlockingQueue
LinkedBlockingQueue
SynchronousQueue
PriorityBlockingQueue
拒绝策略
AbortPolicy 直接抛出异常,默认
CallerRunsPolicy 用调用者所在线程来执行任务
DiscardOledstPolicy 丢弃阻塞队列中最前的任务,并执行当前任务
DiscarkPolicy 直接丢弃任务
线程工厂
线程数量设置
IO密集型 2 cpu core +1
CPU密集型 cpu core +1
1.8新特性
Future/Callable/FutureTask
可以获取线程返回值
实现
获取结果,抛出异常,查询任务是否完成,取消执行的任务
CompletableFuture
通过Future同步等待获取结果
实现了CompletionStage和Future接口,增强异步回调获取结果
实现了CompletionStage和Future接口,增强异步回调获取结果
构造方法
supplyAsync(runnable,executor)
有返回值
supplyAsync(runnable)
runASync(runnable,executor)
没有返回值,异步执行一个任务,但是可以自定义线程池
runAsync(runnable)
异步执行一个任务,默认用ForkjoinPool.commonPool()构建一个CompletableFuture
CompletionStage
纯消费类型的方法
有返回值的方法
不消费也不返回
组合类型
数状结构延续任务的执行
阻塞队列
线性表,FIFO,支持阻塞插入和取出
添加元素
add 满了则抛出异常
offer (timeout) 成功返回true,否则返回false
put 满了则阻塞
移除元素
element 空了则抛出异常
peek (timeout) 成功返回true,否则返回false
poll 空了则阻塞
ArrayBlockingQueue
基于数组结构,FIFO,有界阻塞队列
容量大小不可改变
不保证公平性
基于ReentrantLock + Condition实现
LinkedBlockingQueue
基于链表结构, FIFO,无界阻塞队列
LinkedBlockingDeque
双向链表,容量可空,默认是Integer.MAX_VALUE
fork/join工作窃取
PriorityBlockingQueue
基于优先级队列,无界阻塞队列
默认情况下是元素的自然顺序升序,可以指定Comparator来对元素排序
实现
ReentrantLock + Condition
二叉堆
最大堆
父节点的键值总大于或等于任何一个子节点的键值
最小堆
父节点的键值总小于或等于任何一个子节点的键值
添加操作是不断上冒,删除操作是不断下冒
Delayueue
允许延时执行的无界阻塞队列
应用:缓存,任务超时处理
ReentrantLock + Condition + PriorityQueue实现的
SynchronousQueue
没有任何存储结构,没有容量
应用
newCachedThreadPool
交换工作
生产者嗯哼消费者同步传递信息,事件或任务
LinkedTransferQueue
链表无边界阻塞队列
相当于 ConcurrentLinkedQueue,SynchronousQueue公平模式下,无界的LinkedBlockingQueues的超集
预占模式
有就拿走,没有就占着位置直到拿走或超时或中断
atomic
基本类型
原子的方式更新基本类型
AtomicBoolean
AtomicInteger
AtomicLong
数组
原子的更新数组里的某个元素
AtomicIntegerArray
AtomicLongArray
AtomicReferenceArray
引用类型
如果需要原子的更新多个变量,使用原子更新引用类型
AtomicReference 引用类型
AtomicReferneceFieldUpdater 引用类型里的字段
AtomicMarkableReference 带有标记位的引用类型
字段
如果只需要某个类的某个字段,使用原子更新字段列
AtomicIntegerFieldUpdater 整型字段更新器
AtomicLongFieldUpdater 长整型字段更新器
AtomicStampedReference 带有版本号的引用类型
并发集合
ConcurrentHashMap
CAS+Synchronized 来保证并发更新的安全,底层采用数组+链表/红黑树的存储结构
重要内部类
Node
key-value键值对
TreeNode
红黑树节点
TreeBin
就相当于一颗红黑树,其构造方法其实就是构造红黑树的过程
ForwardingNode
辅助节点,用于ConcurrentHashMap扩容操作
sizeCtl
控制标识符,用来控制table初始化和扩容操作的
含义
负数代表正在进行初始化或扩容操作
-1代表正在初始化
-N 表示有N-1个线程正在进行扩容操作
正数或0代表hash表还没有被初始化,这个数值表示初始化或下一次进行扩容的大小
重要操作
init Table
ConcurrentHashMap初始化方法
只能有一个线程参与初始化过程,其他线程必须挂起
构造函数不做初始化过程,初始化真正是在put操作触发
步骤
sizeCtl<0表示正在进行初始化,线程挂起
线程获取初始化资格(CAS(SIZECTL,SC,-1))进行初始化过程
(初始化步骤完成后,设置sizeCtl=0.75*n(下一次扩容值),表示下一次扩容的大小
put
核心思想
根据hash值计算节点插入在table的位置,如果该位置为空,则直接插入,否则插入到链表或者树中
真实情况较为复杂
步骤
table为null,线程进入初始化步骤,如果有其他线程正在初始化,该线程挂起
如果插入的当前i位置 为null,说明该位置是第一次插入,利用CAS插入节点即可,插入成功,则调用addCcount判断是否需要扩容。
若插入失败,则继续匹配(自旋)
若插入失败,则继续匹配(自旋)
若该节点的hash==MOVED(-1),表示有线程正在进行扩容,则进入扩容进程中
其余情况就是按照链表或者红黑树结构插入节点,但是这个过程需要加锁(synchronized)
get
步骤
table ==null ; return null
从链表/红黑树节点获取
扩容
多线程扩容
步骤
构建一个nextTable,其大小为原来大小的两倍,这个步骤是在单线程环境下完成的
将原来table里面的内容复制到nextTable,这个步骤是分许多线程操作
链表转换为红黑树过程
所在链表的元素个数达到了阈值 8,则将链表转换为红黑树
红黑树算Ậ篼吣羟
1.8与1.7区别
1.7
JDK1.7版本的ReentrantLock+Segment+HashEntry
1.8
去掉segment,CAS加锁,引入红黑树,查找时间复杂度O(n) -> O(logn)
JDK1.8版本中synchronized+CAS+HashEntry+红黑树
1.8 lambda方法
computerIfAbsent
computerIfPresent
computer 上面两者结合
merge
size方法
如何统计个数
如果竞争不激烈,直接用cas(basecount+1)
如果竞争不激烈,用数组来计数
basecount + 遍历数据即为结果
ConcurrentLinkedQueue
基于链接节点的无边界的线程安全队列,采用FIFO原则对元系进行排序,内部采用CAS算法实现
不变性
在入队的最后一个元素的next为null
队列中所有未删除的节点的item都不能为null且都能从head节点遍历到
对于要删除的节点,不是直接将其设置为null,而是先将其item域设置为null,(迭代器会跳过item为null的节点)
允许head和tail更新滞后。这是什么意思呢?意思就说是head,tail不总是指向第一个元素和最后一个元素(后面阐述)
head的不变性和可变性
tail的不变性和可变性
精妙之处:利用CAS来完成数据操作,同时允许队列的不一致性,弱一致性表现淋漓尽致
ConcurrentskipListMap
第三种key-value数据结构:SkipList(跳表)
SkipList
平衡二叉树结构
Skip list让已排序的数据分布在多层链表中,以0-1随机数决定一个数据的向上攀升与否,通过“空间来换取时间”的一个算法
在每个节点中增加了向前的指针,在插入、删除、查找时可以忽略一些不可能涉及到的结点,从而提高了效率
在每个节点中增加了向前的指针,在插入、删除、查找时可以忽略一些不可能涉及到的结点,从而提高了效率
特性
由很多层结构组成,level是通过一定的概率随机产生的
每一层都是一个有序的链表,默认是升序,也可以根据创建映射时所提供的Comparator进行排序,具体取决于使用的构造方法
最底层(Level 1)的链表包含所有元素
如果一个元素出现在Leveli的链表中,则它在Leveli之下的链表也都会出
每个节点包含两个指针,一个指向同一链表中的下一个元素,一个指向下面一层的元素
查找、删除、添加
ConcurrentSkipListSet
内部采用ConcurrentSkipListMap实现
0 条评论
下一页
为你推荐
查看更多