4.1 并发基础 4.2 所有锁原理 4.3 volatile原理 4.4 并发容器原理 4.5 Fork_Join原理
4.1 并发基础
CPU原理(哈哈,以后我当面试官了就问CPU原理)
核心是晶体管(一种方式找到数字且能计算)
不导电的硅+加某些东东变成半导体
5V通电代表1,不通电代表0
进程和线程区别–略
线程中start和run区别
start会创建一个新的子线程启动,run方法只是thread下一个普遍方法调用
线程实现方式
1 extends Thread重写run方法
2 implement Runable接口重写run方法,一般用2不用1,因为接口可以impl多个,extend用了就继承不了别的类了
3 使用线程池
如何处理线程返回值
1 主线程循环等待法
2 join()阻塞当前线程来等待子线程处理完毕
3 通过callable接口实现FutureTask或者线程池
4 直接线程池拿(最好)
线程6种状态
1 New 创建状态
2 Runnable 运行中
3 Waiting 无限等待
4 Timed Waiting 等待一定时间后唤醒
5 Blocked 阻塞
6 Terminated 结束
sleep和wait区别
sleep是thread类中方法,sleep是object类中方法
wait 不仅让出cpu 还会释放已经占有的同步资源锁,sleep 是仅仅让出cpu
Yield
Thread.yield()通知改线程可以让出CPU了,但是程序不一定执行
如何中断线程
直接使用stop方法会造成清理问题,可能同时释放其他锁产生的问题
应该使用interrupt()通知线程该中断了
1 如果线程被阻塞,该线程会立即退出阻塞并抛出异常
2 如果线程在正常活动状态,该线程中断标志位会设为true,但是线程仍继续运行
所以while(!Thread.currentThread().isInterrupted())不断校验中断标志位,如果true就自行停止
线程池
线程池执行流程
1 核心线程池是否满,不满就创建线程,满了走2
2 等待队列是否满,不满放入等待队列,等待队列满了走3
3 最大线程池是否满,不满创建线程,该线程池线程存活时间不能超过keepAliveTime。如果最大线程池满了走饱和策略
4 饱和策略有 拒绝新任务,调用自己线程,不处理直接丢弃,丢弃最早未处理的
FixedThreadPool 固定核心线程池,等待队列无上限会OOM
SingleThreadPool 一个核心线程池,等待队列无上限会OOM
CachedThreadPool 允许创建线程无上限会OOM
ScheduledThreadPool 线程定时作用
题外话Netty源码中reactor设计模式中的线程继承了
SingleThreadPool 和ScheduledThreadPool ,将来写Netty源码会继续写。
所以根据阿里开发手册的要求,我们自己配置线程池才是最好的。
线程通信
1 文件共享:多个线程处理同一个文件
2 变量共享:static变量
3 wait/notify/notifyAll 有顺序要求
4 park/unpark 无顺序,park多次后unpark一次也直接运行,
notify和notifyAll区别
锁池EntryList类似sleep不释放同步资源锁
等待池WaitList类似wait会释放同步资源给其他调用的
notify是随机选一个waitList中线程进入EntryList,
notifyAll是所有waitList中线程进入
4.2 所有锁原理
Java下Synchronzied锁
首先获取对象锁
1 同步代码块(synchronized(this))
2 同步非静态方法(synchronized method) public synchronized void xxx()
类锁
1 同步代码块(synchronized(类.class))
2 同步静态类(synchronized static class)
若锁同一对象,同步方法和同步方法块会互相阻塞,
但是类锁和对象锁互相不干扰
synconronzied信息保存在内存存储布局中对象头内,
该锁分无锁,偏向锁,轻量级锁,重量级锁四个状态
锁四个状态升级步骤:
当一个线程拿到锁,进入偏向锁状态,该线程再次反复请求时,只需要通过对象头中
所标记位为偏向锁且标记位的线程ID相同,则识别是同一线程可以继续使用。
当第二个线程进入时,偏向锁升级轻量级锁。其他没有抢到锁的线程通过一个锁记录
CAS形式自旋不停争抢锁
当轻量级锁下有线程自旋超过设定限制,默认大于10次或者大于CPU核数一半,升级
到重量级锁
Java同时也有锁消除,锁粗化等来优化锁性能
JIT(Just Time Compiler)
经常被使用的代码进行即时编译(热编译)直接成机器语言,
下次使用不需要再走解释器,可以直接运行
CAS
CAS思想
三个操作数: 当前内存位置V,曾经内存位置E,当前新值A
当V和E相等时,则更新当前值为A,否则不更新。
但是会存在ABA问题,曾经内存位置E变成了D但是D之后又变回E当V和E比较时候发现内存位置一样更新值成功,但是V并不知道中间发生了其他变化,会产生很多问题。
ABA解决方案
1 通过加boolean判断是否已经修改
2 添加版本号,比较版本号和V值
Reentrantlock锁
主要原理是AQS等待队列+自旋锁
自旋锁又主要通过unsafe类中的park/unpark方法(用来睡眠线程)+CAS实现
简单版本自旋锁
int votaile state
while(!CAS(state))//state为0则可以拿到锁,state为1则拿锁失败{
//拿锁失败,在while下不停自旋
park(底层是当前线程进入AQS等待队列并且释放CPU)
}
//到这里表示已经拿锁成功
业务逻辑处理
最后unlock(unpark()修改states为0给别的线程抢锁)
AQS是包含线程的双向链表
如果设置公平锁则按照链表顺序拿锁
如果设置非公平锁则大家一起自旋抢
4.3 volatile原理
JVM层面内存屏障+操作系统层面缓存行间数据一致性
缓存行
缓存行是L1,L2,L3等(操作系统层面)缓存最小单位。一个缓存行会对齐64字节。
当数据完整在同一个缓存行时,代码会执行更快,因为如果一个数据用两个不
同缓存行存放,会增加不同缓存行调度的开销。所以比如disruptor底层,一个
重要long cursor变量,前面有7个无用的long,后面有7个无用的long,这样会
确保这个cursor变量一定在同一个缓存行内,加快对这个cursor变量调用的运
行速度。
那么操作系统是如何保证数据一致性呢?
1 英特尔实现方式 MESI Cache一致性协议
缓存行有4中状态 Modified/Exclusive/shared/invalid,当CPU1中L1缓存行下x
变量状态修改值变成Modified状态,CUP2中L1缓存行下x变量的状态变成invalid,
通过硬件上的缓存锁立刻更新最新的数据
2 如果MESI缓存一致性协议不行,则锁总线(这个CPU修改时,其他CPU不允许修改)
JVM内存屏障
屏障两端指令不允许重排序,保证可序性
四种内存屏障LoadLoad,StoreStore,LoadStore,StoreLoad四种屏障
比如StoreLoad表示,必须执行完Store才能执行Load
volatile写操作
会在volatile前面加一层StoreStoreBarrier后面加一层StroeLoadBarrier
volatile读操作
会在volatile前面加一层LoadLoad后面加一层LoadStore屏障
经典例题烂大街例题为什么单例模式双重校验加锁下的变量需要加volatile?
因为如果没有volatile禁止重排序,会因为java自己重排序在并发情况下导致第二
个线程发现变量不为null,直接使用第一个线程还没有初始化完毕的半初始化对象
4.4 并发容器原理
非并发容器
collection下实现了set和list
map下实现了hashmap和sortedmap,sortedmap实现了treemap(红黑树)
ArrayList – 初始0,add后变成10,然后扩容✖1.5
HashMap – bucket数组+链表,初始容量16。当bucket占满百分之75扩容✖2,当链表数量大于8链表变成红黑树
HashTable — 初始11,扩容2n+1
并发容器
CountDownLatch (倒计时器)
CyclicBarrier(循环栅栏)
concurrentHashTable 好像没人用,锁住整个table,性能低下
concurrentHashMap
老版本是用reentrantlock实现sgement数组,
每个 segment 守护着几个HashEntry数组里的元素
新版本在synchronize锁优化后,
synchronized锁定当前链表或红黑二叉树的首节点
copyOnWriteArrayList
copyOnWriteArraySet
此部分节选自文章
当读操作会远远大于写操作情况下会用,通过创建底层数组的新副本来实现的。
当 List 需要被修改的时候,我并不修改原有内容,而是对原有数据进行一次复制,
将修改的内容写入副本。写完之后,再将修改完的副本替换原来的数据,这样就
可以保证写操作不会影响读操作了
Atomic原子类
CAS + volatile 和 native 方法来保证原子操作
非阻塞队列,性能相比阻塞队列好很多
concurrentLinkedQueue -- CAS
阻塞队列 都使用 ReentrantLock锁实现,所以可以实现公平竞争
ArrayBlockingQueue
LinkedBlockingQueue
PriorityBlockingQueue
ThreadLocal
在Netty对象池源码的实现即用到了ThreadLocal
ThreadLocal变量每个线程独立存在,每个线程内部通过一个map维护它
原理
Thread.currentThread()获取到当前线程对象后,
直接通过getMap(Thread t) 可以访问到该线程的ThreadLocalMap对象,
ThreadLocalMap的 key 就是 ThreadLocal对象,
value 就是 ThreadLocal 对象调用set方法设置的值
ThreadLocal 内存泄露问题 此部分节选自文章
ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用,而 value 是强引用。
所以,如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候,
key 会被清理掉,而 value 不会被清理掉。这样一来,ThreadLocalMap 中就
会出现key为null的Entry。假如我们不做任何措施的话,value 永远无法被GC
回收, 这个时候就可能会产生内存泄露。ThreadLocalMap实现中已经考虑了
这种情况,在调用 set()、get()、remove() 方法的时候,会清理掉 key 为 null
的记录。使用完 ThreadLocal方法后 最好手动调用remove()方法
既然有内存泄漏风险,为什么key设为弱引用?
设计成弱引用是为了更好地对ThreadLocal进行回收,ThreadLocal的value
也就是强引用置为null后,这时候Entry中的ThreadLocal就应该被回收了,
但如果key是强引用则该ThreadLocal就不能被回收,只有整个线程回收了
才会回收这个map下ThreadLocal,就会产生内存泄漏。
4.5 Fork_Join原理
ForkJoinPool是ExecutorService接口的实现,本质就是线程池
通过多个fork join并行形式处理业务处理压力
简单思路(用于一个线程处理量太大的情况)
if(工作量小){
直接执行
}else{
当前工作fork()拆分两块
调用这两部分并行执行
join()合并执行结果
返回最后结果
}
封装在ForkJoinTask子类后
RecursiveTask(返回结果)
RecursiveAction(不返回结果)
应用场景
1 后端分布式系统,拆解很多子系统,查很多系统速度慢
2 一个接口请求需要很多信息
工作原理
一个task会fork成多个小task,当别的线程空闲时,会窃取这个线程下
小task帮忙处理,从而实现并行处理,发挥多核CPU优势,加快运行速度
应用代码
~ 主程序 mian ~{
ForkJoinPool pool = new ~(10//CPO核数,ForkJoinPool.defaultForkJoinWorkerThreadFactory, null//异常handler,true//asyncMode);
ArrayList<String> urls = new ~;
urls.add(xxx); //多次添加很多很多url
HttpJsonRequest request = new ~(restTemplate,urls,0,urls.size()-1);
pool.submit(request);
JSONObject result = forkJoinTask.get();//获取最终结果
}
public class HttpJsonRequest extends RecursiveTask<JSONObject>{
int start, end;
RestTemplate restTemplate;
ArrayList<String> urls;
构造函数给上面变量赋值
//核心代码,线程池实际执行入口,实现任务拆分
@Override
protected JSONObject compute(){
int count = end - start;
if(count==0){
//立即调用,用restTemplate根据url进行业务处理
}else{
//递归拆分子任务
int mid = (start+end)/2;
HttpJSONObject request1 = new ~(restTemplate,urls,start,mid);
request1.fork();//当前线程提交到线程池内
HttpJSONObject request2 = new ~(restTemplate,urls,mid+1,end);
request2.fork();//当前线程提交到线程池内
//join整合结果
JSONObject result = new ~;
result.addAll(request1.join());
result.addAll(request2.join());
return result;
}
}
}