——好久没更新,想起来有点存货,所以拿出来发一发
简单说说线程,程序运行的最小单位!一个程序一般会同时运行好多个进程,像QQ(一个线程等待点击事件,一个线程等待消息输入等等)
那么在Java中启动一个新线程如下
new Thread().start();
一般人都知道CPU,每个核同时只能运行一个线程。
在这样一个前提下,会有两个问题:
- Java 中new是需要消耗CPU去创建,线程运行完之后就需要内存回收、线程回收都需要消耗CPU
- 当CPU从当前线程切换到另外一个线程时,需要加载重线程栈的信息需要消耗CPU时间分片
为了解决上面的问题,所以就出现了线程池
线程池——管理线程的工具
https://www.bilibili.com/video/av29951334/?p=21&t=1721
痛点,某些线程 参数业务 不受外界影响,只需要new->start,但是一次new不能做到多次start
newcachethreadpool 比较灵活,会回收线程池
newfixthreadpool 配置并发数
newsinglethreadpool 相当于单线程
newScheduleThreadPool 定时任务
ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize
核心线程数,不会被回收
maximumPoolSize
最大线程数
keepAliveTime
在默认情况下比较准确。非核心线程的线程空闲时间达到keepAliveTime的话就会被回收直至线程数为corePoolSize,开启allowCoreThreadTimeOut的话,所有线程空闲时间达到keepAliveTime的话会被回收直至线程数为0.
unit
keepAliveTime的时间单位
threadFactory
线程工程,主要定义了newThread方法,可参考Executors.defaultThreadFactory()源码
BlockingQueue
用于保存等待执行的任务的阻塞队列。可以选择以下几个阻塞队列。选型(SynchronousQueue,LinkedBlockingQueue,ArrayBlockingQueue)
RejectedExecutionHandler
超过maximumPoolSize再添加线程的策略:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
先来一段测试代码,这段代码大概逻辑是 建一个线程池,然后往里面丢线程并记录线程名+开始结束时间,添加前后的时间,以此来观察线程的执行情况
public static void main(String[] args) throws InterruptedException {
// ExecutorService service = new ThreadPoolExecutor(2,4,3000L, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>());
// ExecutorService service = new ThreadPoolExecutor(2,4,3000L, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(),Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardOldestPolicy());
// ExecutorService service = new ThreadPoolExecutor(1, 3, 3000L, TimeUnit.MILLISECONDS, new SynchronousQueue<>());
ExecutorService service = new ThreadPoolExecutor(1, 3, 3000L, TimeUnit.MILLISECONDS, new SynchronousQueue<>(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
// ExecutorService service = new ThreadPoolExecutor(2, 4, 30000L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(2), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
try {
new Thread(() -> {
System.out.println(Thread.currentThread().getName());
for (int i = 1; i <= 15; i++) {
int j = i;
try {
System.out.println("添加" + j + "开始");
service.submit(() -> {
try {
System.out.println(Thread.currentThread().getName() + ":" + j + " start=" + new Date());
Thread.sleep(1000);
// if (j < 3) Thread.sleep(10000);
System.out.println(Thread.currentThread().getName() + ":" + j + " =============end=" + new Date());
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println("添加" + j + "结束");
} catch (Exception e) {
e.printStackTrace();
System.out.println("添加" + j + "失败");
}
}
}).start();
} catch (Exception e) {
e.printStackTrace();
} finally {
// System.out.println(((ThreadPoolExecutor) service).getActiveCount());
Thread.sleep(15000);
service.shutdown();
}
}
先来看看线程池运行流程(假设所有runnable都是死循环直到switch=off)
①submit(runnable4);当核心线程数corePoolSize>当前runnable数,通过工厂new一个线程来跑runnable4
②submit(runnable5);核心线程数corePoolSize<=当前runnable数,runnable5放入BlockingQueue的task1等待线程1至4执行完成
③如果thread1中的runnable完成了,则runnable5出队列,放入核心线程的thread1中
④submit(runnable10);核心线程都在运行,BlockingQueue也满,但是maximumPoolSize>当前运行的线程数,ThreadFactory.newThread,得到thread5,跑runnable10(只是其中一种模型,也有可能跑runnable5,或者BlockingQueue其它runnable)
⑤如果thread5执行完毕,则获取下一个runnable,如果60s内没有获取到runnable,thread5被回收(销毁?)【只有thread5执行完毕,所以thread5被回收,也有可能是其它thread被回收,但是剩下的thread总数=corePoolSize;thread总数<=corePoolSize是不会回收thread的】
⑥submit(runnable14);如果thread总数=maximumPoolSize,等待runnable总数=BlockingQueue.size,这时会触发拒绝策略RejectedExecutionHandler
⑦输入switch=off,所有线程结束,60s后,非核心线程回收
在ThreadPoolExecuter中存在一个内部类Worker(核心线程和非核心线程),而Worker本身是runnable
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
//...
/** worker持续运行此thread. Null if factory fails. */
final Thread thread;
/** 要运行的第一个任务. Possibly null. 就是上面步骤①和④的runnable*/
Runnable firstTask;
/** Per-thread task counter */
volatile long completedTasks;
//...
}
这个Worker就是前面图上的thread的原形,也就是Worker里面的thread.start()后运行的是worker.run(),内部逻辑简单理解就是死循环去获取runnable再去执行runnable.run()【runnable.run()就是普通的方法调用,这样子thread的死循环就可以让线程一直活着】
添加代码worker的代码在哪?
从上面代码中点进方法提里面
service.submit(runnable)->
(AbstractExecutorService.submit)execute(ftask)->
(ThreadPoolExecuter.execute)addWorker(command, true)
看源码重点部分,new worker传入runnable作为要运行的第一个任务,而在worker中
private boolean addWorker(Runnable firstTask, boolean core) {
//...代码....
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
//...代码...
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
/**
* Creates with given first task and thread from ThreadFactory.
* @param firstTask the first task (null if none)
* 这里是将worker自身传给了属性thread,即thread.start实际会跑runWorker方法
*/
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
/** Delegates main run loop to outer runWorker */
public void run() {
runWorker(this);
}
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
//这里先运行firstTask.run,之后会从线程池队列中获取runnable
while (task != null || (task = getTask()) != null) {
w.lock();
//...代码...
try {
//...代码...
try {
task.run();
} catch (RuntimeException x) {
//...代码...
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
BlockingQueue
BlockingQueue,有几个核心方法:
(put/take)没有超时控制
(offer/poll)提供超时控制
在线程池ThreadPoolExecuter中,并没有使用到put(这个不知道为何);而offer是由runnable发起,take和poll是由worker发起
SynchronousQueue同步队列/栈,实际上它不是一个真正的队列,因为它不会为队列中元素维护存储空间。与其他队列不同的是,它维护一组线程,这些线程在等待着把元素加入或移出队列。
https://www.jianshu.com/p/b7f7eb2bc778
https://segmentfault.com/a/1190000011207824?utm_source=tag-newest(不知道哪个抄哪个的)
这里说了一点,他这个队列存的不是runnable,而是thread
https://www.cnblogs.com/dwlsxj/p/Thread.html
SynchronousQueue有两个模式,一个公平模式用队列,一个不公平模式用栈
内部继承抽象类Transferer
abstract static class Transferer<E> {
/**
* 执行put和take方法.
*
* @param e 非空时,表示这个元素要传递给消费者(提供者-put);
* 为空时, 则表示当前操作要请求消费一个数据(消费者-take)。
* offered by producer.
* @param timed 决定是否存在timeout时间。
* @param nanos 超时时长。
* @return 如果返回非空, 代表数据已经被消费或者正常提供; 如果为空,
* 则表示由于超时或中断导致失败。可通过Thread.interrupted来检查是那种。
*/
abstract E transfer(E e, boolean timed, long nanos);
}
SynchronousQueue的几个属性
/** 用于旋转控制的CPU数量 */
static final int NCPUS = Runtime.getRuntime().availableProcessors();
/**
* 在阻塞等待时间之前旋转的次数。该值是根据经验得出的-在各种处理器和OS上都可以很好地工作。根据经验,最佳值似乎不会随CPU数量(超过2个)而变化,因此只是一个常数。
* 有timedout值
*/
static final int maxTimedSpins = (NCPUS < 2) ? 0 : 32;
/**
* 在阻塞等待时间之前旋转的次数。该值是根据经验得出的-在各种处理器和OS上都可以很好地工作。根据经验,最佳值似乎不会随CPU数量(超过2个)而变化,因此只是一个常数。
* 无timedout值
*/
static final int maxUntimedSpins = maxTimedSpins * 16;
/**
* 自旋时间 纳秒,这个值定的比较随意
*/
static final long spinForTimeoutThreshold = 1000L;
SNode s = null; // constructed/reused as needed
int mode = (e == null) ? REQUEST : DATA;
for (;;) {
SNode h = head;
if (h == null || h.mode == mode) { // 空或者模式相同
if (timed && nanos <= 0) { // 瞬间超时 ThreadPoolExecuter中添加runnable调用offer(E e)导致线程池的会匹配不到就返回null,使用拒绝策略
if (h != null && h.isCancelled()) //match==this代表结点取消?
casHead(h, h.next); // 弹出取消的节点
else
return null;
} else if (casHead(h, s = snode(s, e, h, mode))) { //多加了一个s节点在栈顶,压栈
SNode m = awaitFulfill(s, timed, nanos); //自旋/阻塞等待匹配
if (m == s) { // 匹配到自身则取消节点,从栈中清理出来,不用casHead是因为有可能在等待的过程中已经不是最上的节点
clean(s);
return null;
}
if ((h = head) != null && h.next == s) //匹配成功,将顶节点出栈
casHead(h, s.next); // help s's fulfiller
return (E) ((mode == REQUEST) ? m.item : s.item); //返回data模式的item
}
} else if (!isFulfilling(h.mode)) { // 如果顶节点不是在匹配中,进行匹配。前提是不同模式,线程池中这里有可能是mode=0未匹配和mode=3匹配中(初始入栈模式肯定是mode=0,此判断后会出现data入栈,此节点mode=2|1=3)
if (h.isCancelled()) // 出栈下一次循环
casHead(h, h.next); // pop and retry
else if (casHead(h, s=snode(s, e, h, FULFILLING|mode))) { //
for (;;) { // loop until matched or waiters disappear
SNode m = s.next; // m is s's match
if (m == null) { // 入栈瞬间发现头节点已经出栈,则清楚新节点s,回到大循环
casHead(s, null); // pop fulfill node
s = null; // use new node next time
break; // restart main loop
}
SNode mn = m.next;
if (m.tryMatch(s)) { //如果节点匹配
casHead(s, mn); // 则出栈两个节点s和m,mn成为栈顶
return (E) ((mode == REQUEST) ? m.item : s.item);
} else // 否则就是栈顶已经匹配,s节点需要重新找下一个节点
s.casNext(m, mn); // help unlink
}
}
} else { // 顶节点正在匹配,找栈顶第二个节点进行匹配
SNode m = h.next; // m is h's match
if (m == null) // 没有第二个节点
casHead(h, null); // pop fulfilling node
else {
SNode mn = m.next;
if (m.tryMatch(h)) // 把正在匹配的顶二个节点出栈
casHead(h, mn); // pop both h and m
else // 栈顶已经被匹配了再找下一个
h.casNext(m, mn); // help unlink
}
}
}
RejectedExecutionHandler
AbortPolicy,DiscardPolicy,DiscardOldestPolicy,CallerRunsPolicy都实现了RejectedExecutionHandler 接口
代码以AbortPolicy,实现了rejectedExecution方法,只有一句抛出语句
public static class AbortPolicy implements RejectedExecutionHandler {
/**
* Creates an {@code AbortPolicy}.
*/
public AbortPolicy() { }
/**
* Always throws RejectedExecutionException.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
* @throws RejectedExecutionException always
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
注意:CallerRunsPolicy的核心代码是r.run()是直接在当前线程运行,而不是另起线程
另外,这里可以自行写一个策略,譬如调取某个接口通知管理员。或者记录在某个地方,等时机到重新放入线程池等等