一、线程的状态?
1、新建状态:new一个线程,没还有start2、运行状态(就绪和运行):调用线程的.start办法 1)就绪,调用了start办法,CPU没有调配工夫片 2)运行,调用了start办法,CPU正在调度3、阻塞状态:当竞争synchronized锁时,没拿到,线程挂起4、期待状态:join,wait,(LockSupport)park办法5、超时期待状态:Thread.sleep(long),wait(long),join(long),parkNanos(……)6、死亡状态:run办法完结,或者在run办法中抛出异样没有
二、线程池外围参数
Java中提供了基于Executors构建线程池的形式
间接应用Executors构建会造成对线程池的管制力度很粗
必须以手动的形式构建线程池
public ThreadPoolExecutor(int corePoolSize, // 外围线程、 int maximumPoolSize, // 最大线程有存活工夫 long keepAliveTime, // 生存工夫、 TimeUnit unit, // 单位、 BlockingQueue<Runnable> workQueue, //工作队列、 ThreadFactory threadFactory, // 线程工厂、为了设置线程的名称,不便前面做调试 RejectedExecutionHandler handler) {} // 回绝策略
三、线程池的执行流程
提交工作到线程池中,让线程池中的线程去执行工作
1、提交工作到线程池后
- 如果有闲暇的外围线程,间接执行
- 如果没有闲暇的外围线程,尝试创立外围线程,去执行工作
2、如果曾经达到了外围线程数配置
将工作扔到工作队列中排队,期待外围线程执行完其余工作再来执行我
3、 如果工作队列满了放不下工作了,构建最大线程数
4、 如果最大线程也曾经构建满了,执行回绝策略
四、线程池中的ctl属性什么用?
ctl是线程池中一个属性,实质就是int类型的数值
高3位形容线程池的状态,低29位形容工作线程的数量
线程池在执行工作时,须要屡次判断线程池状态,来的确工作是否须要执行(以哪种形式执行)
低29用表述线程池中现存的工作线程数量
五、线程池的状态?
// RUNNING-线程池在失常工作,能够解决提交的工作!!! private static final int RUNNING = -1 << COUNT_BITS; // 调用线程池的shuwdown()办法,从RUNNING -> SHUTDOWN ,不接管新的工作,然而会解决线程池外部现有的工作包含队列 private static final int SHUTDOWN = 0 << COUNT_BITS; // 调用线程池的shuwdownNow()办法,从RUNNING -> STOP ,不接管新的工作,中断正在解决的工作,不论工作队列工作 private static final int STOP = 1 << COUNT_BITS; // 过渡状态,会从SHUTDOWN和STOP转到TIDYING状态 // SHUTDOWN - 工作队列为空,工作线程为空 - TIDYING // STOP - 工作线程为空 - TIDYING private static final int TIDYING = 2 << COUNT_BITS; // 当线程池达到了TIDYING后,源码中会主动调用terminated,进入到了TERMINATED状态,线程池凉凉 private static final int TERMINATED = 3 << COUNT_BITS;
六、什么是工作线程?
在Java的线程池中,工作线程指的是Worker对象
线程池中的工作线程是用Worker对象表述的
addWorker(Runnable, true/false)增加一个Worker对象到线程池中,Runnable具体要执行的工作true:增加的是外围线程数false:增加的是最大线程数Worker其实就是线程池中的一个外部类,继承了AQS,实现了Runnableprivate final class Worker extends AbstractQueuedSynchronizer implements Runnable{}线程池执行工作,理论就是调用了Worker类中的run办法外部的runWorker办法Worker继承AQS的目标是为了增加标识来判断当前工作线程是否能够被打断!
七、工作线程存到在哪个地位?
存储在了线程池的一个HashSet里private final HashSet<Worker> workers = new HashSet<Worker>();
八、回绝策略
1:Abort:抛异样public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString()); }2:Discard:扔掉,不抛异样public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {}3:DiscardOldest:扔掉排队工夫最久的,行将执行的工作public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { e.getQueue().poll(); e.execute(r); // 再次走一遍线程池的执行流程 } }4:CallerRuns:调用者解决服务,造成调用者性能急剧下降。public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { r.run(); } }
九、如何在线程池执行工作前后做额定解决
try { // 前置加强 beforeExecute(wt, task); try { // 执行工作 task.run(); } catch (Throwable x) { thrown = x; throw new Error(x); } finally { // 后置加强 afterExecute(task, thrown); }}protected void beforeExecute(Thread t, Runnable r) { }protected void afterExecute(Runnable r, Throwable t) { }
十、如何正当的调配线程池的大小
在调配线程池容量大小时,必须要依据你的业务类型来决定CPU密集型,IO密集型,混合型CPU密集型:更多的CPU在做计算,始终在工作IO密集型:更多的时候线程在期待响应混合型:啥工作都有!1、CPU密集型:线程数少一点,举荐:CPU内核数+12、IO密集型:线程数多一些,举荐:个别CPU内核数 * 2,(线程等待时间与线程CPU工夫之比 + 1) * CPU数目3、混合型:能够将CPU密集和IO密集的操作分成两个线程池去执行即可!
十一、如果存在临界(共享)资源,如何保障线程的安全性
1、互斥锁: synchronized、Lock(ReentrantLock,ReadWriteLock)2、非阻塞锁: CAS(真的无锁嘛,底层lock cmpexchg有锁,Java层面没锁)3、不采纳任何锁: ThreadLocal,适当的状况采纳volatile也成! ThreadLocal:让多个线程将共享资源copy到本地,没有多线程操作共享资源的问题 volatile:只有不蕴含并发对共享数据进行运算,根本没问题。
十二、ThreadLocal到底是什么?
ThreadLocal的实质就是一个Map。
ThreadLocal能够将一个数据和本地线程绑定在一起。
十三、ThreadLocal的内存透露问题?
Java中四种援用
强援用:OOM也不革除
软援用:内存不足革除
弱援用:只有GC就革除
虚援用:拿不到援用,构建进去就凉凉~~
想要解决这个问题,就在应用TheadLocal结束后,进行remove操作
十四、volatile
可见性和禁止指令重排,无奈保障原子性!
为什么CPU会指令重排?
CPU会在保障happens-before的前提下,对指令进行从新排序,从而提高效率
为了实现禁止指令重排,JVM虚拟机提出了标准,内存屏障
- LoadLoad
- StoreLoad
- LoadStore
- StroeStore
hotSpot虚拟机实现的很简略,在两条指令两头,追加一个lock指定实现volatile的成果
CPU级别中多线程解决共享数据时,加锁。
lock指令会让CPU内存中的数据操作完同步到主内存。
十五、伪共享(缓存行共享)问题
CPU外部分为L1,L2,L3内存,CPU外部内存,效率比去主内存中找数据快的多!
个别的64的CPU,外部会有缓存行存储数据,一个缓存行是64byte
个别的解决形式,就是让一个业务的数据填满整个缓存行。
long l = 真正的数据。
long l1,l2,l3,l4,l5,l6,l7;
在JDK1.8中,个别采纳@Contended注解即可实现
十六、CAS
compare And Swap
CAS存在的问题:
ABA问题:追加版本号解决
如果失败次数过多,占用CPU资源:不同场景有不同的解决计划
synchronized:解决计划是自适应自旋锁,如果自旋次数过多,就挂起线程
LongAdder:自增时,如果失败,将失败的信息增加到Cell[]中
只能保障一个数据的平安:无奈像synchronized一样锁住一段代码,ReentrantLock外部就是基于CAS的形式实现了锁的成果
synchronized-ReentrantLock:看马老师和黄老师的视频,外面会有零碎解说
十七、ConcurrenthashMap
只说JDK1.8的……
ConcurrenthashMap在没有Hash抵触时,以CAS的形式尝试插入到数组中
如果有Hash抵触,这个时候回将以后数组索引地位锁住,以synchronized的模式挂到链表上面
如果数组长度达到了最开始的长度的0.75时,就要将数组长度扩充二倍,素来防止链表过长造成查问效率较低
十八、ConcurrenHashMap在并发扩容时,如何保障平安?
在计算Node中key的hash值时,会特意的将hash值失常状况的数值定义为负数
正数有非凡的含意,如果hash值为-1,代表以后节点正在扩容
ConcurrenthashMap会在扩容时,每次将老数组中的数据table.size - 1 ~ table.size - 16索引的地位挪动,而后再迁徙其余索引地位的数据,如果有线程在插入数据时,发现正在扩容,找还没有被迁徙数据的索引地位,帮忙最开始扩容的线程进行扩容,
最开始扩容A:31~16
线程B插入数据,发现正在扩容,帮你迁徙数据,15~0索引地位
每一个迁徙结束的数据,都会加上标识,代表扩容结束,放上一个ForwardingNode节点,代表扩容结束, 而且再扩容是,不会利用ConcurrentHashMap的遍历,查问和增加(发现扩容,会帮忙~)
十九、线程扩容时,会应用sizeCtl记录当初扩容时的线程数量,那么为什么1个线程扩容时,低位数值为2,2个线程扩容为?
如果sizeCtl为-1,代表ConcurrentHashMap正在初始化,-N代表正在扩容
所以不得已,要将1个线程正在扩容的标识这是为-2,-2代表有1个线程扩容
-3代表有2个线程扩容。
二十、AQS
AQS是啥?
AQS是JUC包下的一个并发基类,很多内容都基于AQS实现,如罕用的
ReentrantLock/Semaphore/CountDownLatch/线程池。
AQS构造?
CLH(双向队列)+state(int类型的变量)
基于双向队列和CAS的形式操作state,实现了各个JUC下罕用的并发内容
偏心锁:AQS队列有Node,就间接排队,不竞争锁资源
非偏心锁:啥也不论,上来间接先竞争锁资源,而后再走下面套路
本文由博客一文多发平台 OpenWrite 公布!