并发
2021-02-23 14:22:29 0 举报
AI智能生成
java面试_并发
作者其他创作
大纲/内容
内存模型
多线程情况下,每个线程有自己的工作内存,每开一个线程,会从主内存中读取一遍变量,缓存到工作内存,
除非对变量修改,否则不会修改缓存中的数据;当数据被修改后,数据会刷新到主内存;
除非对变量修改,否则不会修改缓存中的数据;当数据被修改后,数据会刷新到主内存;
线程
ThreadLocal
使用场景
数据库事务:给个每个线程(请求)一个connection,这样我们关闭自动提交事务,就可以实现手动提交事务了
设置请求id等
设置请求id等
原理
每个Thread对象都维护了一个ThreadLocalMap,里面存的就是当前线程个性化的数据,实现数据隔离的思想;那ThreadLocal相当于在一个工具包,可以对外提供每个Thread中的ThreadLocalMap中存储的东西,ThreadLocalMap也是Map,key值存的是ThreadLocal对象的引用,我们在使用ThreadLocal的时候,一般只new一个static的ThreadLocal,所以,多线程情况下,每个线程的ThreadLocalMap中key都一样,都是ThreadLocal的引用,value就属于个性化的东西了,原理大概就是这样。
理解
两大优点:相当于一个自己的变量云存储
1、传递数据,保存每个线程保存的数据,需要的时候可以直接获取,避免了参数传递的麻烦
2、线程隔离:每个线程中中绑定的数据相互隔离,
1、传递数据,保存每个线程保存的数据,需要的时候可以直接获取,避免了参数传递的麻烦
2、线程隔离:每个线程中中绑定的数据相互隔离,
弱引用和内存泄漏
概念:
内存溢出:内存的空间不够了
内存泄漏:堆内存的空间因为某种原因无法释放,最终导致内存溢出
弱引用:垃圾回收发现弱引用的对象,不管空间是否足够,都会回收空间
内存溢出:内存的空间不够了
内存泄漏:堆内存的空间因为某种原因无法释放,最终导致内存溢出
弱引用:垃圾回收发现弱引用的对象,不管空间是否足够,都会回收空间
理解:
首先为什么会产生内训泄漏:加入我们已经使用完了ThreadLocal,但是线程依然在跑,这个时候,栈到堆的引用断开了,理论上,堆里面的ThreadLocal应该被GC,但是,ThreadLocalMap中,引用了ThreadLocal,因为key是ThreadLocal,所以导致没有用的TreadLocal无法被回收,造成ThreadLocal的内存泄漏。
那弱引用可以解决问题吗:答案是不能从根本上解决问题,弱引用代表ThreadLocal实例可以被回收,Entry中的key指向null了;但是,ThreadLocalMap作为Thread的成员变量,无法被回收,造成Entry中value的内存泄漏。jdk呢对这里做了优化,如果key指向为null,没有具体的ThreadLocal了,那么value也会置空,这样最大限度的避免了没有用的内存无法被回收的尴尬。
根本解决方法是:ThreadLocal用完之后,调用一下remove方法,这样,Entry里value就为空了;
首先为什么会产生内训泄漏:加入我们已经使用完了ThreadLocal,但是线程依然在跑,这个时候,栈到堆的引用断开了,理论上,堆里面的ThreadLocal应该被GC,但是,ThreadLocalMap中,引用了ThreadLocal,因为key是ThreadLocal,所以导致没有用的TreadLocal无法被回收,造成ThreadLocal的内存泄漏。
那弱引用可以解决问题吗:答案是不能从根本上解决问题,弱引用代表ThreadLocal实例可以被回收,Entry中的key指向null了;但是,ThreadLocalMap作为Thread的成员变量,无法被回收,造成Entry中value的内存泄漏。jdk呢对这里做了优化,如果key指向为null,没有具体的ThreadLocal了,那么value也会置空,这样最大限度的避免了没有用的内存无法被回收的尴尬。
根本解决方法是:ThreadLocal用完之后,调用一下remove方法,这样,Entry里value就为空了;
hash冲突
线性探测法
Entry[] table可以看成一个环形数组
Entry[] table可以看成一个环形数组
线程池
tomcat线程池
springboot线程池
多线程同步有哪些呢
synchronized
lock
wait/notify
await/signalAll
countDownLatch
cyclibair
samephore
lock
wait/notify
await/signalAll
countDownLatch
cyclibair
samephore
juc
AQS
原理
CountDownLatch
场景:
计数器。主线程等(.await())所有子线程把总数消耗完成(.countDown())后,主线程开始继续走
计数器。主线程等(.await())所有子线程把总数消耗完成(.countDown())后,主线程开始继续走
CyclicBarrier
场景:
运动员上跑道,等所有人都准备好(.await())好后,裁判发令(重启一个线程干一件事情)
运动员上跑道,等所有人都准备好(.await())好后,裁判发令(重启一个线程干一件事情)
Samephore
场景:
停车位上停车,
停车位上停车,
要保证
原子性
可见性
有序性
可见性
有序性
synchronized
原子性
悲观锁,同一时间只有一个线程在执行共享资源,能够保证原子性
可见性
加锁前,清空工作内存中的共享变量的值,从主内存中冲洗取值;
释放锁前,将工作内存中的数据刷新到主内存
释放锁前,将工作内存中的数据刷新到主内存
有序性
单线程,不会因为指令重排导致出现问题
加锁原理
为什么叫重量级锁呢
加锁的时候,会调用操作系统,相当于同步在操作系统完成,导致操作系统内核态和用户态来回切换,导致速度变慢
加锁的时候,会调用操作系统,相当于同步在操作系统完成,导致操作系统内核态和用户态来回切换,导致速度变慢
子主题
volatile
原子性
不保证
可见性
可见性原理:lock addl指令+缓存一致性协议 + 内存屏障;
在原来写操作的基础上,加了volatile关键字的变量,在写的时候,jvm会给cpu加一个lock指令,cpu在写完数据之后,会立即将数据刷新到主内存,
同时因为缓存一致性,其他线程会嗅探自己的变量有没有被修改,如果被修改,会将数据设置为无效,这样在读数据的时候,就会从主内存中加载最新的数据,保证了数据的可见性
在原来写操作的基础上,加了volatile关键字的变量,在写的时候,jvm会给cpu加一个lock指令,cpu在写完数据之后,会立即将数据刷新到主内存,
同时因为缓存一致性,其他线程会嗅探自己的变量有没有被修改,如果被修改,会将数据设置为无效,这样在读数据的时候,就会从主内存中加载最新的数据,保证了数据的可见性
有序性
有序性原理:内存屏障
写数据:storestore + 修改数据 +storeload +flush到祝内存
读数据: 从主内存读取数据(如果失效) + loadload + 读数据 + loadstore
写数据:storestore + 修改数据 +storeload +flush到祝内存
读数据: 从主内存读取数据(如果失效) + loadload + 读数据 + loadstore
lock
原子性
子主题
有序性
加锁原理
为什么叫轻量级锁呢
同步在jvm级别就去解决
同步在jvm级别就去解决
https://zhuanlan.zhihu.com/p/80929454?from_voters_page=true
理解
reentrantlock呢,首先他是为了解决线程同步问题,之前有synchronized,但是这个synchronized1.6之前属于重量级锁,原因是他涉及内核态和用户态的来回切换,所以,Doug le 想,我们可以在java层就解决线程的同步的问题,不再去麻烦系统,于是有了reentrantlock。
那aqs/lock无非围绕着 自旋+park+cas实现的;
那aqs/lock无非围绕着 自旋+park+cas实现的;
源码
重入:
相同的线程进来后,如果当前的status!=0,那么还会判断这个线程是不是重新进来的,如果是则把status+1返回,表示重入了一次
如果锁被占用,并且当前线程不是重入,返回false,表示没上锁
tryAcquire()
相同的线程进来后,如果当前的status!=0,那么还会判断这个线程是不是重新进来的,如果是则把status+1返回,表示重入了一次
如果锁被占用,并且当前线程不是重入,返回false,表示没上锁
tryAcquire()
入队:
队列里面的队头永远是空,空是指里面的thread为空
队列里面的队头永远是空,空是指里面的thread为空
0 条评论
下一页