volatile 线程可见性、禁止指令重排
如何保证指令重排?
内存屏障分为os与jvm两种实现
os 1.内存屏障:sfence/mfence等系统原语
2.锁总线 (多个cpu读写内存的时候,通过lock指令锁住总线,保证只有一个cpu访问内存)
jvm 1. java code 上volatile关键字
2.bytecode 也是volatile关键字
3.jvm 层面 (jsr要求实现内存屏障 在读写volatile变量前后加屏障)
4.hotspot实现 (查看OrderAccess::fence lock总线,因为这种方法所有的cpu都支持)
1 cup(ALU+REG) > 3 cache(L1/L2/ L3共享) > 100 memory > 100w disk
超线程 1个ALU对应两组pc+alu
ThreadLocal
强软弱虚引用
强引用: 不使用的变量、对象会被回收
软引用:SoftReference 堆内存不足时回收
弱引用:WeakReference 使用一次就被回收
虚引用:PhantomReference 引用不到,管理堆外内存(jvm专门的线程, DirectByteBuffer)
应用nio以及netty zero-zopy
线程切换
分为内核线程和应用线程;
java的Thread与内核线程是1:1,
而Go的纤程与内核线程是m:n(m远大于n,且纤程之间可以同步数据);
java扩展的纤程框架:Quasar、Kilim
锁的概念
多个线程访问同一个资源的时候,这个资源必须上锁才能正常访问;
锁对象可以随意指定(object或者class)
不持有锁的线程怎么办,CAS、等待队列(需要经过os调度,所以是重量级锁)、
锁的效率:看等待的线程多少,等待的人多时cas比较消耗cpu;还不如重量级锁的效率高
锁的四种状态、
多线程执行count++
synchronized 锁住代码段
juc atomicInteger –> UnSafe.compareAndSwap() CAS /自旋锁
cas: 循环执行、写入的时候检查是否是读出来的数据
ABA问题:加版本号或时间戳解决
CAS修改值的时候的原子性问题 Atomic::cmpxchg –> asm
synchronized锁升级过程
偏向锁:在锁对象上加上线程id
轻量级锁(自旋锁):有线程竞争的时候,偏向锁升级为轻量级锁
重量级锁:线程重度竞争时,加入os线程调度队列
jol内存布局(8Byte的倍数)
markword
class pointer
instance data
padding data
缓存行 cacheline
1 cl = 64 Byte = 8 long
缓存行对齐 disruptor
juc
ReentrantLock
与synchronized一样,属于互斥锁
ReentrantLock lock = new ReentrantLock ();//默认非公平锁
lock.lock();//加锁
try{
//...
}finally{
lock.unlock();//释放锁
}
lock方法
FairSync :公平锁 执行lock方法时先看是否有锁,已经加锁就先排队
NonefairSync:非公平锁 先尝试获取锁,获取不到锁再加锁acquire()方法尝试获取锁,
AQS AbstractQueuedSynchronizer (CAS加volatile实现)
双向链表(queue) Node(prev/next/thread)
volatile status=1就排队
acquire方法
1. 使用Unsafe.cas设置status值
2. 调用tryAcquire加锁,
可重入锁:一个线程在已经给方法加锁的情况下,还可以再次进去这个方法
tryAcquire方法
1.非公平锁 state==0判断 、获取锁并添加线程标识
2.公平锁 先判断队列是否有线程排队、state==0判断 、获取锁并添加线程标识
addWaiter()方法
获取锁失败的线程、封装为Node、排队到AQS队列
acquireQueued()方法
1.检查当前node是否排在第一个(不算head),再次执行tryAcquire
2.不为头节点的下一个节点,尝试挂起
Node
waieStatus 1取消 0正常 -1挂起
unlock() 释放锁 release()
线程池
ThreadPoolExecutor
应用方法
为啥要用线程池?
Executors提供封装好的线程池、实践中无法满足要求
7个参数
corePoolsize //核心线程数
maxinumPoilSize //最大线程数
keepAliveTime/unit 非核心线程空闲时间/单位
workQueue //阻塞队列
threadFactory //线程工厂(配置线程名)
rejectExecHandler //拒绝策略(阻塞队列满了) 几种策略abort/caller/discardOld/discard
执行流程
任务提交到线程池后,线程池的处理流程
execute() : 工作线程是否小于核心线程(是的话直接创建新线程)、判断工作队列是否满(否直接排队)、工作线程数是否达到最大线程数(创建非核心线程并执行当前任务)、走拒绝策略
ThreadPoolExecutor状态
4.1 AtomicInteger ctl (1.高三位标识线程池当前状态 2.低29位表示工作线程个数)
4.2 RUNNING(正常执行、处理任务)
SHUTDOWN(不接受新任务)
STOP(不接受任务、中断正在执行的任务、不处理队列任务)
TIDYING(过渡状态,需要重写terminated方法)
TERMINATED 销毁状态
shutdown()与shutdownNow()的区别:shutdown调用后,线程池不再接收新任务,将正在执行的以及排队的执行完;shutdownNow调用后,会直接进入过渡状态(啥也不干了)等待下一步被销毁。
execute()
addWorker(command)
workQueue.offer(command)
addWorker(null, false) 为了处理工作线程数为0,但队列中仍有任务排队;怎么处理的
addWorker() 添加工作线程
6.1先判断线程池状态及工作线程数量
只要不是RUNNING状态,就不处理新任务;
如果是SHOTDOWN并且addWorker(null, false),
6.2添加并启动工作线程
加锁后进行状态判断(防止其他线程showdown)
Worker对象( extends AbstractQueuedSynchronizer implements Runnable)
AbstractQueuedSynchronizer 线程中断
Runnable 存储执行的任务
thread 绑定工作线程
Runnable firstTask 需要执行的任务(execute传入的runnable对象)
//中断线程不是立即让线程停止,而是给thread的中断标识设置为true
runWorker() 执行任务(当前工作线程来执行)
8.1获取传入的runnable对象并执行
8.2runnable对象为空,从工作队列里取一个并执行
getTask() 从工作队列中获取任务
SHUTDOWN或STOP且workqueue为空,干掉当前线程
干掉超时的非工作线程、
阻塞一段时间从workqueue中获取工作线程(没拿到的话重新开始)
processWorkerExit()
钩子函数抛出异常才会减去工作线程数量
加锁->统计已执行的任务数量 -> 移除任务