import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor.AbortPolicy; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; /** * @Auther: 范子祺 * @Date: ${DATE} ${HOUR}:${MINUTE} * @Description: */ public class Main { public static void main(String[] args){ ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor (10, 15, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100), Executors.defaultThreadFactory(), new AbortPolicy()); // 源码学习 threadPoolExecutor.execute(new Runnable() { @Override public void run() { System.out.println("test"); } }); // 上下对比, 区别,应用 threadPoolExecutor.submit(new Runnable() { @Override public void run() { System.out.println("submit" + ""); } }); ReentrantLock reentrantLock = new ReentrantLock(true); reentrantLock.lock(); try { }finally { reentrantLock.unlock(); } } }
几个问题
1 如何添加线程。 cas+ lock
2 线程如何保活。 钩子+processWorkerExit,启动新工人,可以理解为忘记错误重头再来。
3 线程如何回收。 while + decrementWorkerCount
4 自旋是如何实现的。 cas, 在增加工人数量的时候使用了
threadPoolExecutor.execute( 方法分析
1 将要执行的任务往线程池丢
2 获得线程池状态ctl, 运行状态,池内运行线程数量
3 如果小于核心线程数量, 将任务添加 workerCountOf(c) < corePoolSize
3.1 添加 核心线程数
开始for
如果线程池状态 >= shutdown- 返回false,exit for
否则进入下一个for
获得 线程池 线程数量
如果线程池线程数量 大于 线程池计算的最大容量 或者 大于核心线程数 返回false,exit for
否则,compareAndIncrementWorkerCount 添加ctl的核心线程数,数+1 使用了cas,添加成功,退出自旋,退出for
继续开始try,用传入的任务丢到worker,worker中向线程池中新增线程,并将这个任务丢给这个新创的线程了
拿到work加入池子的线程, 加锁ReentrantLock (这里用了aqs)。这里加锁的原因是:我们准备让这个新加到线程池的线程开始工作,但这个时候,我们要保证整个操作的原子性。所以加锁。
开始try 拿到线程池的状态 如果状态小于shutdown,但是这个新建的线程却已经活了,直接抛异常后面有finally解决。如果正常,则将worker添加到线程池的workers中,接着比较一下运行时候线程池最多时候的数量,记录最大值。 标记,workeraddsuccess,然后解锁,接着启动这个线程,标记workerstarted-success,最后
如果启动失败了,进入减worker方法,里面也会加锁解锁,如果返回 添加成功了
直接return,execute执行成功。
8 8 8 0001 1101 coun-bit 29
8 8 8 0000 0001 1
0010 000 8 8 8 1 << 29
0001 111 8-1 8-1 8-1 1 << 29 -1 CAPACITY
1110 000 8 8 8 ~CAPACITY
8-1 8-1 8-1 8-1 -1
说白了, 就是一个食堂,三个正式工打菜,还有三个临时工,然手呢, 正式工先干,后面来的人先排队,队伍都排不下了, 就让临时工上,临时工也上完了这时候就开始执行拒绝策略。所以拒绝的时候嗯,先在可以研究线程池的其他细节部分了。
然后里面通过钩子啊, while啊,等方式保持线程数不变一直跑。跑到阿姨下班食堂关门。
https://blog.csdn.net/m0_67698950/article/details/124042501 --- 线程池相关源码。 runnable和futuretask对比
https://blog.csdn.net/weixin_46097842/article/details/125085449 --- 有张流程图可看看
https://blog.csdn.net/dengjili/article/details/100171491 --源码中的二进制
泛型基础练习
https://blog.csdn.net/Beyondczn/article/details/107093693
b占sp
bilibili.com/video/BV1xJ411n77R?p=13&spm_id_from=pageDriver&vd_source=562955621af926e4655ded0199c26c61
什么叫适应性自旋: 自旋的时间是一个根据环境产生的动态时间,就像 适应性吃饭, 我们在家在学校在工作吃饭的时间一样。
aqs abstractqueuedsynchronizer
reentranlock
在使用阻塞等待获取锁的方式中,必须在try代码块之外,并且在加锁方法与try代码块之间没有任何可能抛出异常的方法调用,避免加锁成功后,在finally中无法解锁。
说明一:如果在lock方法与try代码块之间的方法调用抛出异常,那么无法解锁,造成其它线程无法成功获取锁。
说明二:如果lock方法在try代码块之内,可能由于其它方法抛出异常,导致在finally代码块中,unlock对未加锁的对象解锁,它会调用AQS的tryRelease方法(取决于具体实现类),抛出IllegalMonitorStateException异常。
说明三:在Lock对象的lock方法实现中可能抛出unchecked异常,产生的后果与说明二相同。 java.concurrent.LockShouldWithTryFinallyRule.rule.desc
aqs的源码---适合熟悉后跟着看
https://www.bilibili.com/video/BV1Zz4y197iF/?spm_id_from=333.337.search-card.all.click&vd_source=562955621af926e4655ded0199c26c61
aqs 公众号文章,看的我要睡觉了----需要多啃几遍
https://javadoop.com/post/AbstractQueuedSynchronizer
共享锁和独占锁