图解Java线程池原理

什么是线程池?

为了避免频繁重复的创建和销毁线程,我们可以让这些线程进行复用,在线程池中,总会有活跃的线程在占用,但是线程池中也会存在没有占用的线程,这些线程处于空闲状态,当有任务的时候会从池子里面拿去一个线程来进行使用,当完成工作后,并没有销毁线程,而是将将线程放回到池子中去。

线程池主要解决两个问题:
一是当执行大量异步任务时线程池能够提供很好的性能。
二是线程池提供了一种资源限制和管理的手段,比如可以限制现成的个数,动态新增线程等。
​ -《Java并发编程之美》

上面内容出自《Java并发编程之美》这本书,第一个问题上面已经提到过,线程的频繁创建和销毁是很损耗性能的,但是线程池中的线程是可以复用的,可以较好的提升性能问题,线程池内部是采用了阻塞队列来维护Runnable对象。

(想自学习编程的小伙伴请搜索圈T社区,更多行业相关资讯更有行业相关免费视频教程。完全免费哦!)

原理分析

JDK为我们封装了一套操作多线程的框架Executors,帮助我们可以更好的控制线程池,Executors下提供了一些线程池的工厂方法:

  • newFixedThreadPool:返回固定长度的线程池,线程池中的线程数量是固定的。
  • newCacheThreadPool:该方法返回一个根据实际情况来进行调整线程数量的线程池,空余线程存活时间是60s
  • newSingleThreadExecutor:该方法返回一个只有一个线程的线程池。
  • newSingleThreadScheduledExecutor:该方法返回一个SchemeExecutorService对象,线程池大小为1,SchemeExecutorService接口在ThreadPoolExecutor类和 ExecutorService接口之上的扩展,在给定时间执行某任务。
  • newSchemeThreadPool:该方法返回一个SchemeExecutorService对象,可指定线程池线程数量。

对于核心的线程池来说,它内部都是使用了ThreadPoolExecutor对象来实现的,只不过内部参数信息不一样,我们先来看两个例子:nexFixedThreadPoolnewSingleThreadExecutor如下所示:

public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue(),threadFactory);
}public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue()));
}

由上面的线程池的创建过程可以看到它们都是ThreadPoolExecutor的封装,接下来我们来看一下ThreadPoolExecutor的参数说明:

参数名称参数描述
corePoolSize指定线程池线程的数量
maximumPoolSize指定线程池中线程的最大数量
keepAliveTime当线程池线程的数量超过corePoolSize的时候,多余的空闲线程存活的时间,如果超过了corePoolSize,在keepAliveTime的时间之后,销毁线程
unitkeepAliveTime的单位
workQueue工作队列,将被提交但尚未执行的任务缓存起来
threadFactory线程工厂,用于创建线程,不指定为默认线程工厂DefaultThreadFactory
handler拒绝策略

其中workQueue代表的是提交但未执行的队列,它是BlockingQueue接口的对象,用于存放Runable对象,主要分为以下几种类型:

  • 直接提交的队列:SynchronousQueue队列,它是一个没有容量的队列,前面我有对其进行讲解,当线程池进行入队offer操作的时候,本身是无容量的,所以直接返回false,并没有保存下来,而是直接提交给线程来进行执行,如果没有空余的线程则执行拒绝策略。

  • 有界的任务队列:可以使用ArrayBlockingQueue队列,因为它内部是基于数组来进行实现的,初始化时必须指定容量参数,当使用有界任务队列时,当有任务进行提交时,线程池的线程数量小于corePoolSize则创建新的线程来执行任务,当线程池的线程数量大于corePoolSize的时候,则将提交的任务放入到队列中,当提交的任务塞满队列后,如果线程池的线程数量没有超过maximumPoolSize,则创建新的线程执行任务,如果超过了maximumPoolSize则执行拒绝策略。

  • 无界的任务队列:可以使用LinkedBlockingQueue队列,它内部是基于链表的形式,默认队列的长度是Integer.MAX_VALUE,也可以指定队列的长度,当队列满时进行阻塞操作,当然线程池中采用的是offer方法并不会阻塞线程,当队列满时则返回false,入队成功则则返回true,当使用LinkedBlockingQueue队列时,有任务提交到线程池时,如果线程池的数量小于corePoolSize,线程池会产生新的线程来执行任务,当线程池的线程数量大于corePoolSize时,则将提交的任务放入到队列中,等待执行任务的线程执行完之后进行消费队列中的任务,若后续仍有新的任务提交,而没有空闲的线程时,它会不断往队列中入队提交的任务,直到资源耗尽。

  • 优先任务队列:t有限任务队列是带有执行优先级的队列,他可以使用PriorityBlockingQueue队列,可以控制任务的执行先后顺序,它是一个无界队列,该队列可以根据任务自身的优先级顺序先后执行,在确保性能的同时,也能有很好的质量保证。

上面讲解了关于线程池内部都是通过ThreadPoolExecutor来进行实现的,那么下面我以一个例子来进行源码分析:

public class ThreadPoolDemo1 {public static void main(String[] args) {ExecutorService executorService = new ThreadPoolExecutor(5,10,60L,TimeUnit.MILLISECONDS,new ArrayBlockingQueue<>(5), new CustomThreadFactory());for (int i = 0; i < 15; i++) {executorService.execute(() -> {try {Thread.sleep(50000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("由线程:" + Thread.currentThread().getName() + "执行任务完成");});}}
}

上面定义了一个线程池,线程池初始化的corePoolSize为5,也就是线程池中线程的数量为5,最大线程maximumThreadPoolSize为10,空余的线程存活的时间是60s,使用LinkedBlockingQueue来作为阻塞队列,这里还发现我自定义了ThreadFactory线程池工厂,这里我真是针对线程创建的时候输出线程池的名称,源码如下所示:

/*** 自定义的线程池构造工厂*/
public class CustomThreadFactory implements ThreadFactory {private static final AtomicInteger poolNumber = new AtomicInteger(1);private final ThreadGroup group;private final AtomicInteger threadNumber = new AtomicInteger(1);private final String namePrefix;public CustomThreadFactory() {SecurityManager s = System.getSecurityManager();group = (s != null) ? s.getThreadGroup() :Thread.currentThread().getThreadGroup();namePrefix = "pool-" +poolNumber.getAndIncrement() +"-thread-";}@Overridepublic Thread newThread(Runnable r) {String name = namePrefix + threadNumber.getAndIncrement();Thread t = new Thread(group, r,name,0);System.out.println("线程池创建,线程名称为:" + name);if (t.isDaemon())t.setDaemon(false);if (t.getPriority() != Thread.NORM_PRIORITY)t.setPriority(Thread.NORM_PRIORITY);return t;}}

代码和DefaultThreadFactory一样,只是在newThread新建线程的动作的时候输出了线程池的名称,方便查看线程创建的时机,上面main方法中提交了15个任务,调用了execute方法来进行提交任务,在分析execute方法之前我们先了解一下线程的状态:

//假设Integer类型是32位的二进制表示。
//高3位代表线程池的状态,低29位代表的是线程池的数量
//默认是RUNNING状态,线程池的数量为0
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//线程个数位数,表示的Integer中除去最高的3位之后剩下的位数表示线程池的个数
private static final int COUNT_BITS = Integer.SIZE - 3;
//线程池的线程的最大数量
//这里举例是32为机器,表示为00011111111111111111111111111111
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
//线程池的状态
// runState is stored in the high-order bits
//11100000000000000000000000000000
//接受新任务并且处理阻塞队列里面任务
private static final int RUNNING    = -1 << COUNT_BITS;
//00000000000000000000000000000000
//拒绝新任务但是处理阻塞队列的任务
private static final int SHUTDOWN   =  0 << COUNT_BITS;
//0010000000000000000000000000


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部