多线程复习

ABA问题

CAS进行操作的关键是通过值没有发生变化来作为"没有其他线程穿插执行"判定依据

但这种问题不够严谨,近端情况下,有另一个线程穿插进来,把值从A->B->A

ABA问题如果真的出现了,其实大部分情况下也不会产生bug,虽然另一个线程穿插执行,由于值又改回去了,此时逻辑上也不一定会产生bug

只要让判定的数值按照一个方向增长即可,有增有减就有可能出现ABA

但是针对账户余额这样的概念,本身就应该要能增能减,可以引入一个额外的变量"版本号"

约定每次修改都要让版本号自增

synchronized原理

1.锁升级

无锁->偏向锁(此时不是真的加锁,只是做了一个标记)->自旋锁->重量级锁

偏向锁不是真的加锁,当锁竞争出现的时候,偏向锁就会升级成轻量级锁,

这样就可以能够保证效率,又能保证线程安全,核心思想和"懒汉模式"一样

能不加锁就不加锁,加锁意味着开销

2.锁消除

编译器会针对当前你写的加锁的代码做出判定,如果编译器觉得这个场景不需要加锁,

此时就会把你写的synchronized给优化掉

3.锁粗化

synchronized里头代码越多就认为锁的粒度越粗

将连续多次对同一个对象加锁、解锁的操作,合并为一次范围更大的加锁操作,从而减少锁的获取和释放次数,提升性能。

Reentrantlock也是一个可重入锁,使用上和synchronized类似,但是需要手动解锁

优势:1.加锁时有两种方式lock和trylock

2.提供了公平锁的实现

3.提供了更强大的等待通知,搭配了Condition类,实现等待通知的

Semaphore(信号量)

开发中如果遇到需要申请资源的场景,就可以使用信号量来实现了,

锁本质上就是资源为一的信号量

加锁操作,P操作,1->0

解锁操作,V操作,0->1

CountDownLatch

这个东西主要适用于多个线程来完成一系列任务的时候,用来衡量任务的进度是否完成

比如需要把一个大的任务,拆成多个小的任务,让这些任务并发的去执行

就可以使用使用countDownLatch来判定这些任务是否全都完成

主要有两个方法

  1. await调用的时候就会阻塞,就会等待其他线程完成任务,所有线程都完成了任务以后,此时这个await才会继续往下走
  2. countDown告诉countDownLatch,我当前这个子任务已经完成了

copyOnWriteArrayList写实拷贝

比如两个线程使用同一份ArrayList,如果两个线程读就直接读

如果某个线程需要修改,就会把ArratList复制出一份副本,修改线程的话就修改这个副本,

与此同时另一个线程仍然可以读取数据,一旦修改完成就会使用修改好的这份数据,替代掉原来的数据

缺点:这个ArrayList不能太大(拷贝成本高),更适合多个线程读,一个线程去修改

应用场景:服务器的配置更新

ConcunrrentHashMap

ConcunrrentHashMap最核心的改进就是把一个全局的大锁改进成了每个链表独立的小锁,这样做,大幅度降低了锁冲突的概率。就是把每个链表的头节点作为锁对象,而java 8以前是基于分段式锁实现的

充分利用了CAS特性,把一些不必要加锁的环节给省略加锁了,比如需要用变量记录hash表中的元素个数,此时就可以使用原子操作(CAS)修改元素个数

针对读操作没有加锁,读和读,读和写之间都不会有锁竞争

针对扩容操作做出了单独的优化,原本HashMap扩容时需要把所有的元素都拷贝一遍,而ConcunrrentHashMap则是把扩容中一次搬运操作分成多次,每次只搬运一部分数据,避免这单次操作过于卡顿

相关推荐
金銀銅鐵6 分钟前
[java] 编译之后的记录类(Record Classes)长什么样子(上)
java·jvm·后端
c++之路16 分钟前
C++信号处理
开发语言·c++·信号处理
野生技术架构师2 小时前
金三银四面试总结篇,汇总 Java 面试突击班后的面试小册
java·面试·职场和发展
Legendary_0082 小时前
LDR6500:USB‑C DRP PD协议芯片技术详解与应用实践
c语言·开发语言
小袁拒绝摆烂2 小时前
多表关联大平层转JSON树形结构
java·json
2301_800976932 小时前
正则表达式
开发语言·python·正则表达式
故事还在继续吗2 小时前
C++20关键特性
开发语言·c++·c++20
青少儿编程课堂3 小时前
2026青少儿信息素养大赛备赛指南!Python/Scratch/C++备考要点
开发语言·c++·python
ja哇3 小时前
大厂面试高频八股
java·面试·职场和发展