多线程
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<br>可以保证A 操作完后,A的执行结果堆B操作是可见的
原理:执行写操作的时候jvm会给cpu发送一条lock前缀指令
cpu会立即将这个值写回主存
会使其他cpu缓存副本失效(触发mesi)
mesi缓存一致性协议
原理:各个cpu会对总线进行嗅探,如果发现有人修改了某个缓存的数据,那么cpu会将自己本地的缓存过期掉,再次读取变量的时候,就会从主内存重新加载最新的数据
八种可见性规则<br>
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,进行扩容机制
<ol><li>在链表长度大于 8 并且 表的长度大于 64 的时候会转化红黑树,当链表长度小于6会转会链表</li></ol>
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
一个由数组结构组成的有界阻塞队列
<span style="font-size: inherit;">数组实现的有界BlockingQueue,该队列满足先入先出的特性,它是一个典型的有界缓存<br> 有一个固定大小的数组保存元素,一旦创建好了以后,容量就不能改变了。</span><br>
方法
LinkedBlockingQueue
一个由链表结构组成的有界阻塞队列。
LInkedTransferQueue
一个由链表结构组成的无界阻塞队列
DelayQueue
一个使用优先级队列实现的无界阻塞队列
LinkedBlockingDeque
一个由链表结构组成的双向阻塞队列
PriorityBlockingQueue
一个支持优先级排序的无界阻塞队列
SynchronousQueue
一个不存储元素的阻塞队列
DelayedWorkQueue
优先级队列
0 条评论
下一页