作为Java 搬砖人员,免不了要接触线程与线程池,今天就来聊聊线程与JDK里的线程池
进入今天内容前,我们先思考下这么个问题:线程池里的线程是如何维持生命,不被GC掉?
what is thread
thread的使用
thread的几种状态
JDK线程池的使用
线程池里的线程
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 */
}
}
}
/**
* 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() ;
}
}
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)
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) ;
}
}
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;
}
}
}
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线程,是通过队列的阻塞操作与不断的任务处理来维持其生命的
当然除去异常退出外