什么是线程复用
在Java中,我们正常创建线程执行任务,一般都是一条线程绑定一个Runnable执行任务。而Runnable实际只是一个普通接口,真正要执行,则还是利用了Thread类的run方法。这个rurn方法由native本地方法start0进行调用。我们看Thread类的run方法实现
/* What will be run. */
private Runnable target;
/**
* If this thread was constructed using a separate
* <code>Runnable</code> run object, then that
* <code>Runnable</code> object's <code>run</code> method is called;
* otherwise, this method does nothing and returns.
* <p>
* Subclasses of <code>Thread</code> should override this method.
*
* @see #start()
* @see #stop()
* @see #Thread(ThreadGroup, Runnable, String)
*/
@Override
public void run() {
if (target != null) {
target.run();
}
}
很明显,Thread类的run方法就是使用构造Thread类传入来的Runnable对象,执行Runnable的run方法。这样可以很好的将任务和Thread类解耦,如果继承Thread类再去重写run方法当然也是可以,但却耦合了,并且Java是单继承,所以继承Thread类这种方式通常不会使用,没有任何好处。
现在问题是,一个线程只能执行一个Runnable对象,那么这条线程它就是不能复用的,完成任务它就该Terminated了。如果系统任务很多,频繁创建线程带来的开销大,线程数量不可控导致系统处于一种不安全的状况,系统随时可能被大量线程搞跨,于是线程池就出现了。线程池要解决的问题就是用少量线程处理更多的任务,这样一来,线程池首先要实现的就是线程复用。不能说还是一条线程只处理一个Runnable任务,而是一条线程处理无数Runnable任务。最容易想到的方案就是将Runnable对象放到队列中,在Thread类的run方法中不断从队列中拉取任务执行,这样一来就实现了线程复用。当然,实际线程池也差不多是这么干的,下面我们详细看一下线程池实现线程复用的原理。
线程池处理任务的过程
在线程池原理解析1中有详述线程池创建线程及处理任务的过程。这里再次简单看一下流程图以方便理解下面的线程复用原理解析。
线程复用原理解析
线程处理任务过程源码解析
首先我们看看线程池是怎么使用的
import cn.hutool.core.thread.ThreadFactoryBuilder;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author kangming.ning
* @date 2023-02-24 16:27
* @since 1.0
**/
public class CustomThreadPool1 {
private static ThreadFactory threadFactory = new ThreadFactoryBuilder().setNamePrefix("线程池-").build();
private static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
Runtime.getRuntime().availableProcessors(),
Runtime.getRuntime().availableProcessors() * 2,
60L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(10),
threadFactory, new ThreadPoolExecutor.CallerRunsPolicy());
public static void main(String[] args) throws InterruptedException {
Runnable r = () -> {
System.out.println(Thread.currentThread().getName() + " is running");
};
for (int i = 0; i < 35; i++) {
Thread.sleep(1000);
threadPoolExecutor.submit(r);
}
}
}
可见,threadPoolExecutor的sumit方法就是用来提交任务的,于是,从这个方法开始分析源码,把源码的关注点放在线程复用部分。
/**
* @throws RejectedExecutionException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
第一句只是用来包装一下有返回值的任务