作为Java 搬砖人员,免不了要接触线程与线程池,今天就来聊聊线程与JDK里的线程池
进入今天内容前,我们先思考下这么个问题:线程池里的线程是如何维持生命,不被GC掉?
what is thread
thread的使用
thread的几种状态
JDK线程池的使用
线程池里的线程
线程(英语:
thread)是操作系统能够进行运算调度的最小单位
一条线程指的是
进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务
by wikipedia
一般有两种方式来使用线程处理任务
1.继承Thread,直接重写run方法,然后实例化Thread来启动线程
2.实现Runnable接口,实现run方法,然后把Runnable作为参数入参实例化Thread来启动线程
启动线程使用start方法,而不是run方法
直接使用run方法相当于普通的对象方法调用,而没有创建线程来运行run方法
可以对比下Thread的start方法和run方法源码,就可以发现猫腻
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group‘s list of threads
* and the group‘s unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
public void run() {
if (target != null) {
target.run();
}
}
start方法里会调用JVM本地方法start0 ,创建线程,后续将会由JVM来发起线程调度,去具体执行run方法
而Thread的run方法就是对传入的Runnable对象的方法调用而已
线程自身有4种状态(Java编程思想)
1.new。线程创建后会短暂处于此状态
2.Runnable。在此状态下,只要调度器把时间片分配给线程,线程就可以运行
3.Blocked。线程能够运行,但有某个条件阻止它的运行
4.Dead。处于死亡或终止状态的线程将不可再是可调度的,并且再也不会得到CPU时间,它的任务已经结束,或不再是可运行的
也就是说,处于Dead的线程,接下来就等着被GC了
所以,如果能够让线程不断的在Runnable、Blocked 运行状态间持续转换,就可以一直维持线程的“生命”了
下面来聊下线程池,大概说下线程池的使用
JDK线程池中一个构造方法
关键的5个参数为corePoolSize、maximumPoolSize、workQueue、threadFactory、handler
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
通过线程池的处理流程分析,我们就能很好的理解这几个参数的意义
1.当我们往线程池添加任务时,开始poolsize=0,需要创建线程进行任务处理
1.1 不断的添加任务,也不断的创建线程,直到poolsize = corePoolSize
2.当添加第 corePoolSize+1 个任务时,任务会被放到queue里,等待空闲线程进行处理
2.1 一般我们会使用有界的队列,不停的添加任务到队列,直到队列满;
使用无界队列就看内存什么时候耗尽了,这是一个非常危险的行为
3.队列满的情况下,继续添加任务,这时,会重新开始创建线程进行任务处理
3.1不断添加任务,直到poolsize = maximumPoolSize;这时停止线程创建
4.当线程满负荷运行,依然有新任务过来,这时就需要拒绝策略了;
一般有几种拒绝策略,如直接丢弃、由caller直接执行任务、抛出拒绝exception等
通过threadFactory我们可以自定义thread的创建,比如重定义下线程的名字,方便识别
了解了线程池的大概处理流程,就基本知道应该怎么使用线程池了;无非就具体参数值的设定,根据实际场景选择合适的参数组合
下面来具体分析下线程池里的线程
再回头开头提的那个问题:线程池里的线程是如何维持生命,不被GC掉?
线程的执行方法本身非常好理解,不断的获取task,然后执行task
看上去是个while循环,只要有task,线程就能不断执行了
而如果没有task,线程就会完成使命,等待被回收了
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
下面看下具体的获取task的方法:getTask()
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
getTask方法里是个死循环
A boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
B Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
主要看这两行代码
A 首先是判断是否需要考虑超时
B 然后根据是否超时 来选择 从队列获取任务的方式
超时判断主要考虑 wc > corePoolSize 这个,即poolsize 与 corePoolSize比较
意思就是说,如果poolsize > corePoolSize ,说明当前线程可能是多余的空闲线程
只会等待一定的间隔从队列获取任务数据
否则就会直接调用队列的非超时获取方法,就会一直阻塞,直到有数据为止
阻塞操作主要是加锁操作的阻塞
因此,线程池里的corePool线程,是通过队列的阻塞操作与不断的任务处理来维持其生命的
当然除去异常退出外