多线程
2021-10-06 17:20:29 0 举报
AI智能生成
多线程这块学的比较散,整理的有点粗糙,比如线程池这块,堵塞队列这块,还有线程创建的状态等等。很多细节都没学到。
作者其他创作
大纲/内容
多线程基础
线程的状态
子主题
volatile
概括:
线程会对volatile修饰的变量,执行写操作时会立刻把写入的值刷新到主存内,同时副本失效,同步数据
有序性
每个volatile写操作前面都加storestore屏障,禁止上面普通写和它重排,每个写后面都加入StoreLoad屏障禁止跟下面的进行重排。
每个volatile读操作前面都加LoadLoad屏障,禁止上面普通读和它重排,每个读后面都加入LoadStore屏障禁止跟下面的进行重排。
不保证原子性
如果一个变量被volatile修饰了,那么每次读取变量的时候是最新的,但是自增操作这样非原子操作就不会保证原子性
解决:加锁,使用原子类
volatile为为什么无法保证原子性?
可见性
happen-before原则
A和B线程执行:如果A Happen-Before B
可以保证A 操作完后,A的执行结果堆B操作是可见的
可以保证A 操作完后,A的执行结果堆B操作是可见的
原理:执行写操作的时候jvm会给cpu发送一条lock前缀指令
cpu会立即将这个值写回主存
会使其他cpu缓存副本失效(触发mesi)
mesi缓存一致性协议
原理:各个cpu会对总线进行嗅探,如果发现有人修改了某个缓存的数据,那么cpu会将自己本地的缓存过期掉,再次读取变量的时候,就会从主内存重新加载最新的数据
八种可见性规则
synchronized
有序性
原子性
指令重排的问题不能解决
修饰的位置
对象的普通方法 锁定方法所在对象
代码块 synchronized(this) 锁定方法所在对象
代码块 synchronized(对象) 锁定指定对象
代码块 synchronized(对象.class) 锁定类
类方法 static synchronized 锁定类
锁变化
自旋锁
当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。
锁消除
分析对象是不是只能被一个线程来加锁,如果没有其他线程来竞争锁,这个编译器就不自动加锁和解锁了
锁粗化
如果有连续多次加锁释放锁的代码,会给合并为一个锁,就是锁粗化,把一个锁搞粗化了,避免频繁加锁释放锁、
偏向锁
如果发现大概率只有一个线程执行这个锁,会为这个锁维护一个偏向锁,后面的加锁和释放锁,都会基于偏向锁,不需要cas,也可能有其他线程进行锁竞争,不过概率很小。
轻量级锁
如果偏向锁没能成功实现,因为不同线程同步竞争太频繁了,此时就会尝试采用轻量级的方式加锁,就是将对象头的markword里有一个轻量级锁的指针,尝试指向持有锁的线程,然后判断是不是自己加的锁。
重量级锁
当系统检查到锁是重量级锁之后,会把等待想要获得锁的线程进行阻塞,被阻塞的线程不会消耗cpu。但是阻塞或者唤醒一个线程时,都需要操作系统来帮忙,这就需要从用户态转换到内核态,而转换状态是需要消耗很多时间的,有可能比用户执行代码的时间还要长。
cas(V,A,B)
CAS原理
cas机制使用了三种操作:内存地址v,旧的预期值,要修改的新值B,更新一个变量的时候,只有当变量的预期值和内存地址v的实际值相同时,才会将内存地址修改成b。
缺点:
cpu开销过大,并发高的时候,反复尝试更新一个值的变量,多线程反复尝试更新一个值的变量,可能有很多线程不停的自旋。
不能保证代码块的原子性,需要保证三个变量共同进行原子性更新,就不得不使用synchronized
concurrentHashMap
扩容机制
子主题
put方法
进行高低位与运算,后进行put,如果key的hashcode相同,进行eq操作,如果eq一样,就不变
eq不一样,说明不是相同对象,挂在相同hash下的链表中,当链表长度大于8,而数组总长度没达到64,进行扩容机制
- 在链表长度大于 8 并且 表的长度大于 64 的时候会转化红黑树,当链表长度小于6会转会链表
get方法
volatile
size方法
aqs抽象队列同步器
ReentrantLock和aqs之间的关系,reentrantLock内部包含了一个aqs,这个aqs就是实现加锁和释放锁关键核心组件。
实现原理
1.aqs核心是state,int类型表示加锁状态,初始值0
2.aqs内部还有变量,用来记录加锁的是哪个线程,初始化状态,null
3.线程1跑过来调用reentrantlock的lock方法尝试加锁,这个加锁的过程,就是cas操作state的值从0编程1,设置当前加锁的线程是1
4.线程2一看state的值不是0,说明有人加过锁了,线程看是不是自己加的锁,不是加锁失败,会将自己放入等待队列中,如果是自己之前加的锁,将aqs里面的state加1.
4.线程1执行完自己的业务逻辑代码后,就会释放锁,将aqs的state减一,如果state是0,就彻底释放锁,将加锁线程变量也设置null,等待队列线程尝试加锁。
线程池
java内存模型
threadLocal
作用:
threadlocal为每个线程变量都创建了一个变量副本,线程之间数据隔离
原理
ThreadLocal 原理:每个线程的内部都维护了一个 ThreadLocalMap,它是一个 Map(key,value)数据格式,key 是一个弱引用,也就是 ThreadLocal 本身,而 value 存的是线程变量的值。
方法
get()
把当前线程作为map的key去拿值
map不为空说明有值,返回
set(value)
把当前线程作为map的key,如果设置过值,就进行修改,没值就进行设置
内部是threadmap-创建的是entry,key-value形式,key是threadlocal本身,value是设置的值,(弱引用)
remove
引发的相关问题
内存泄露问题
ThreadLocal 在 ThreadLocalMap 中是以一个弱引用身份被 Entry 中的 Key 引用的,因此如果 ThreadLocal 没有外部强引用来引用它,那么 ThreadLocal 会在下次 JVM 垃圾收集时被回收。这个时候 Entry 中的 key 已经被回收,但是 value 又是一强引用不会被垃圾收集器回收,这样 ThreadLocal 的线程如果一直持续运行,value 就一直得不到回收,这样就会发生内存泄露。
队列
ArrayBlockingQueue
一个由数组结构组成的有界阻塞队列
数组实现的有界BlockingQueue,该队列满足先入先出的特性,它是一个典型的有界缓存
有一个固定大小的数组保存元素,一旦创建好了以后,容量就不能改变了。
有一个固定大小的数组保存元素,一旦创建好了以后,容量就不能改变了。
方法
LinkedBlockingQueue
一个由链表结构组成的有界阻塞队列。
LInkedTransferQueue
一个由链表结构组成的无界阻塞队列
DelayQueue
一个使用优先级队列实现的无界阻塞队列
LinkedBlockingDeque
一个由链表结构组成的双向阻塞队列
PriorityBlockingQueue
一个支持优先级排序的无界阻塞队列
SynchronousQueue
一个不存储元素的阻塞队列
DelayedWorkQueue
优先级队列
0 条评论
下一页