程序员成长路上有着不同的阶段,只要你翻过了当时那个阶段,那么你将会有了不一样的收获。很多时候,我们在刚开始面对它们的时候,还看不清,看不透,云里雾里,让人觉得它们很高深。等我们正在的了解它们了之后就觉一切都是那么简单、自然。
再努力一下,一切将会不一样!—— 鲁迅
前言
多线程开发就是这样的一座山,需要我们去克服。说到多线程,大部分新手(作者自己),在面试中谈到多线程就慌了,因为自己在实际工作中真的很少碰到,而且我们多数时候都是在做传统的单体项目开发,说真的,很少会碰到用到多线程的,用都没用过面试的时候让我们怎么说。为了让大家对多线程有个大致了解,现在让作者我跟大家瞎扯几句,作者很少写文章,写得不太通顺的地方,大家多(wang)多(si)谅(li)解(pen) 。
既然要讲多线程,就不得不说下线程、线程池了~
线程
在Java中你会怎么创建一个线程吗?
很简单啊,比如说:
Thread t = new Thread(new Runnable() {
@Override
public void run() {
// 处理逻辑代码
process();
}
});
t.start();
恩,没错,这样确实可以简单的创建出一个线程对象。
我们知道一个线程的创建于销毁是需要消耗资源的,大家来思考一个问题:假设我们项目中要经常用到多个线程去处理业务的话,每次都是用完就销毁,这也未免太浪费了些吧,毕竟线程只是帮我们执行相应的任务,完全可以继续复用它呀,让它接着处理其他任务。那么在Java中有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?
如果之前大家有看过《阿里巴巴 Java 手册》的话,那么应该知道有那么一条:
可见线程池
就是我们的答案!
线程池的作用
简单来说使用线程池有以下几个目的:
-
线程是稀缺资源,不能频繁的创建。
-
解耦作用,线程的创建于执行完全分开,方便维护。
-
应当将其放入一个池子中,可以给其他任务进行复用。
线程池原理
简单来说就是把宝贵的资源管理起来,每次用的时候再去取,用完放回,让其他人也可以复用。
那在Java中我们该怎么实现呢?
在Java中线程池的核心类是ThreadPoolExecutor
,并在此类的基础上封装了几种常用线程池:
-
Executors.newCachedThreadPool()
:无限线程池。 -
Executors.newFixedThreadPool(nThreads)
:创建固定大小的线程池。 -
Executors.newSingleThreadExecutor()
:创建单个线程的线程池。
我们看下他们是怎么实现的:
// 无限线程池
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
// 创建固定大小的线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
// 创建单个线程的线程池
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
看上面源代码,我们知道它们都是基于ThreadPoolExecutor
实现的,那我们就来看看它们是给ThreadPoolExecutor
传了什么参数才导致它们可以实现不同功能的线程池的呢?
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
由上面的参数解释,我们可以知道,线程池是通过设置corePoolSize
(最小线程数)和maximumPoolSize
(最大线程数)来确定线程池的大小范围,让线程池在我们定义范围内扩容、减容。线程池的扩容是要判断当前所需的线程数是否超过核心线程数
、阻塞队列也满了
并且当前线程数小于最大线程数
,都符合了,才会扩容。我们可以看下面的流程图来理解:
-
corePoolSize
核心线程数,为线程池的基本大小。 -
maximumPoolSize
为线程池最大线程大小。 -
keepAliveTime
和unit
则是线程空闲后的存活时间。 -
workQueue
用于存放任务的阻塞队列。 -
threadFactory
线程工厂,主要用来创建线程。 -
handler
当队列和最大线程池都满了之后的饱和策略。-
ThreadPoolExecutor.AbortPolicy
丢弃任务并抛出RejectedExecutionException异常 -
ThreadPoolExecutor.DiscardPolicy
也是丢弃任务,但是不抛出异常 -
ThreadPoolExecutor.DiscardOldestPolicy
丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程) -
ThreadPoolExecutor.CallerRunsPolicy
由调用线程处理该任务
-
虽然,Executors
给我们封装好了上面几个构建线程池的方法,但是,并不建议直接使用!
为什么呢?
让我们来看看阿里巴巴的开发手册:
让我们看下源码是不是真的这样:
-
Executors 源码
// Executors.class
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));// 这里采用了无参构造方法
}
-
LinkedBlockingQueue源码
// LinkedBlockingQueue.class
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE); // 无参构造方法,直接设置为Integer.MAX_VALUE大小
}
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
last = head = new Node<E>(null);
}
果然如此~
所以还是按照我们自己业务上需求自定义配置属于自己的线程池吧!
好了,暂时先讲到这啦~
大家觉得不错帮忙点个在看哈~
参考:
https://blog.csdn.net/pange1991/article/details/53860651
《阿里巴巴Java开发手册》