多线程的状态
各类锁
synchronized
升级过程
偏向锁
jvm启动4秒后开启,因为jvm启动有大量的锁竞争,锁的对象头会记录进来的线程id,若使用为同一线程,则直接放行,若存在竞争则升级为轻量级锁。
轻量级锁
出现竞争时,采用cas方式将对象头复制到线程的栈桢中,若失败则while重试,再次获取锁,达到一定次数升级为重量级锁。
重量级锁
出现竞争时,线程直接阻塞,等待cpu二次调度唤醒,调用了操作系统层面的指令。
实现原理
采用对象的monitor进行进入和退出操作实现,monitor包含
_owner:指向当前锁的持有者线程。
_count:锁进入次数。
_EntryList:锁等待的线程队列。
_WaitSet:调用wait等待队列。
线程进入monitor时,判断_count是否为0,为0直接进入,否则判断_owner是否是自己,是自己直接重入,否则进入等地队列。
锁离开时_count减一,减至0是,是否_owner。
不管是synchronized、ReentrantLock或者是redisson的rlock实现原理差不多。
在等待队列方面,ReentrantLock采用了java的aps队列和cas尝试获取锁,rlock采用了本地jvm的队列。
在线程同步唤醒方面,ReentrantLock采用了unpark和park,rlock采用了基于消息发布订阅的unpark和park。
ReentrantLock
java api 级别锁
lock unlock 配合Condition的await和signal达到和synchronized不同的定点唤醒。
Rlock
在redis中,大key为锁的字符串,hashkey为线程的hash值,v为重入次数。阻塞队列使用Java api层面的队列,同步唤醒采用基于消息发布订阅的park和unpark.
Semaphore
限流,20线程同时到acquire,10会阻塞,等release。
new Semaphore(10);
acquire -1
处理30秒。
release +1
CountDownLatch
多线程事务,做完以后触发调用
new CountDownLatch(3);
new Thread( 一堆事情 countDown)
主线程await,等待3个线程完毕进行执行。
CyclicBarrier
CyclicBarrier cyclicBarrier = new CyclicBarrier(3, () -> {
System.out.println("All " + parties + " threads have reached the barrier!");
});
new Thread( 一堆事情 await)
等3个线程都await之后,回调CyclicBarrier的方法,同时自动填充CyclicBarrier循环使用。
ReentrantReadWriteLock
readLock 返回读锁、writeLock 返回写锁。
读读只有共享。
AtomicLong AtomicReference (放置自建对象) Cas操作。
AtomicStampedReference 带版本的cas操作,解决aba问题。
LongAdder,分很小的单元进行cas,汇总时累加。
异步编排编码
CompletableFuture.supplyAsync 和runAsync,配合同步阻塞的get进行结果获取。将长任务,并行处理,结果需要相互依赖的任务,串行等待。
线程池
corePoolSize(核心线程数):线程池中保持存活的最小线程数。
maximumPoolSize(最大线程数):线程池允许的最大线程数。当队列满了,并且已创建的线程数小于最大线程数时,会创建新的线程来处理任务。
keepAliveTime(线程空闲时间):当线程数大于核心线程数时,多余的空闲线程在终止前等待新任务的最长时间。
unit(时间单位):keepAliveTime参数的时间单位,如TimeUnit.SECONDS。
workQueue(工作队列):用于存放待执行任务的阻塞队列。常用的队列类型有LinkedBlockingQueue(无界队列),ArrayBlockingQueue(有界队列,一般用这个指定队列)。
threadFactory(线程工厂):用于创建新线程。通过自定义线程工厂,可以设定线程名(一般都会设置)、线程组、优先级、守护线程状态等。
handler(拒绝策略):当队列和线程池都满了,说明线程池处于饱和状态,这时如果再提交任务,就需要拒绝处理。线程池提供了四种拒绝策略:AbortPolicy(直接抛出异常),或者自定义策略接受下来任务往mq放,CallerRunsPolicy(主线程执行)。
tomcat 和 java原生的线程池最大区别在于,tomcat 当核心线程数满了以后会直接启动最大线程数处理,达到最大以后在放入阻塞队列。
保证充分利用cpu资源,避免频繁切换,避免线程过大带来浪费。
cpu密集型 核心线程数为cpu核数
io密集型 cpu核数的两倍
ThreadLocal
线程池引用时,每个线程都有自己的一个ThreadLocalMap,key为静态ThreadLocal的弱引用(gc时就去清除),value为set对象的强引用。这便导致了线程池线程的强引用threadlocalmap至set设置的对象这条引用链不断,可能导致取到上一份对象或者该对象一直存在gc不掉。
使用线程私有的堆内存的TLAB实现的。
- 应用代码中不再持有 ThreadLocal 的强引用
- ThreadLocal 实例仅剩弱引用(来自 Entry 的 key)
- GC 时 ThreadLocal 实例被回收,Entry 的 key 变为 null
- 但 Entry 本身和 value 仍然被 ThreadLocalMap 引用
- 如果线程不终止(如线程池场景),value 永远无法释放
为什么要用弱引用
减少threadlocal对象的泄露风险。
当应用代码结束引用时,map仍旧引用,强引用减少不掉。
若为static修饰的threadlocal,多次set确实会替换原有V,但是依旧释放不了V