1、线程池
在Jetty 8 中实现了两个线程池,ExcutorThreadPool和QueuedThreadPool。其中ExcutorThreadPool是对java已有线程池的封装,而QueuedThreadPool是对Executor的实现。下面来看一下这两个线程池的定义。
/* ------------------------------------------------------------ */
/**
* Jetty ThreadPool using java 5 ThreadPoolExecutor
* This class wraps a {@link ExecutorService} as a {@link ThreadPool} and
* {@link LifeCycle} interfaces so that it may be used by the Jetty <code>org.eclipse.jetty.server.Server</code>
*/
public class ExecutorThreadPool extends AbstractLifeCycle implements ThreadPool, LifeCycle
public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPool, Executor, Dumpable
2、QueuedThreadPool的实现
2.1 、属性
// 已经启动的线程数目
private final AtomicInteger _threadsStarted = new AtomicInteger();
// 空闲的线程数目
private final AtomicInteger _threadsIdle = new AtomicInteger();
//
private final AtomicLong _lastShrink = new AtomicLong();
// 所有启动的线程的集合
private final ConcurrentLinkedQueue<Thread> _threads=new ConcurrentLinkedQueue<Thread>();
// 共享变量
private final Object _joinLock = new Object();
// 阻塞队列,用于存放需要执行的job
private BlockingQueue<Runnable> _jobs;
// _name
private String _name;
// 最大空闲时间,这里应该可以理解为线程池中有空闲的线程的最大的持续时间
private int _maxIdleTimeMs=60000;
// 最大线程数
private int _maxThreads=254;
// 最下线程数
private int _minThreads=8;
// 最大排队
private int _maxQueued=-1;
// 线程优先级
private int _priority=Thread.NORM_PRIORITY;
// 是否是守护线程
private boolean _daemon=false;
// 最大stop时间
private int _maxStopTime=100;
private boolean _detailedDump=false;
2.2、构造函数
/* ------------------------------------------------------------------- */
/** Construct
*/
public QueuedThreadPool()
{
// 设置线程名称以qtp开头
_name="qtp"+super.hashCode();
}
/* ------------------------------------------------------------------- */
/** Construct
*/
public QueuedThreadPool(int maxThreads)
{
this();
// 设置最大线程数
setMaxThreads(maxThreads);
}
/* ------------------------------------------------------------------- */
/** Construct
*/
public QueuedThreadPool(BlockingQueue<Runnable> jobQ)
{
this();
// 设置阻塞队列
_jobs=jobQ;
// 清除阻塞队列
_jobs.clear();
}
2.3、启动线程池
实际启动线程是通过doStart()函数。
@Override
protected void doStart() throws Exception
{
// 调用父类doStart()
super.doStart();
// 设置_threadsStarted为0,则表示线程从0开始编号
_threadsStarted.set(0);
// 如果_jobs为空,则为线程池添加默认队列。
if (_jobs==null)
{
// 如果_maxQueued大于0,则创建ArrayBlockingQueue,大小为_maxQueued
// 如果_maxQueued小于等于0,则创建BlockingArrayQueue,大小为_minThreads,可增长为_minThreads
_jobs=_maxQueued>0 ?new ArrayBlockingQueue<Runnable>(_maxQueued)
:new BlockingArrayQueue<Runnable>(_minThreads,_minThreads);
}
// 获取线程编号初始值
int threads=_threadsStarted.get();
// 启动线程数为_minThreads,默认为8
while (isRunning() && threads<_minThreads)
{
// 启动线程,编号为threads
startThread(threads);
threads=_threadsStarted.get();
}
}
通过上面的代码可以看到,默认情况下会启动8个线程。我们来验证下:
1)打开CMD,输入命令jconsole。看到如下界面,
2)选择HelloWorld,然后在界面上选择线程。
可以看到以qtp开头的线程有8个,正好与上面说到的默认的最小线程数一致。同时可以看到,已经有两个线程已经被使用,一个Selector,一个Acceptor。
然后看一下startThread()是如何在当前框架中启动一个线程的。
/* ------------------------------------------------------------ */
private boolean startThread(int threads)
{
// 设置下一个线程的id
final int next=threads+1;
// 确保threads和_threadsStarted一致
if (!_threadsStarted.compareAndSet(threads,next))
return false;
boolean started=false;
try
{
// 线程初始化
Thread thread=newThread(_runnable);
thread.setDaemon(_daemon);
thread.setPriority(_priority);
thread.setName(_name+"-"+thread.getId());
// 将线程放置到_threads中
_threads.add(thread);
// 线程启动
thread.start();
started=true;
}
finally
{
if (!started)
// 线程启动失败,则_threadsStarted - 1
_threadsStarted.decrementAndGet();
}
return started;
}
所做的事情十分简单,首先确保线程编号正确,然后更新_threadsStarted,再然后启动线程,将线程放到_threads中统一管理。
2.4、执行任务
任务的分发,主要负责的是将任务添加到等待队列中去。如果当前线程数,无法满足当前的任务,则创建新的线程。
/* ------------------------------------------------------------ */
public boolean dispatch(Runnable job)
{
if (isRunning())
{
// 获取jobs目前的数目
final int jobQ = _jobs.size();
// 获取目前闲置的线程数
final int idle = getIdleThreads();
// job插入到_jobs中
if(_jobs.offer(job))
{
// If we had no idle threads or the jobQ is greater than the idle threads
if (idle==0 || jobQ>idle)
{
// 如果threads数目小于_maxThreads,添加新的线程
int threads=_threadsStarted.get();
if (threads<_maxThreads)
startThread(threads);
}
return true;
}
}
LOG.debug("Dispatched {} to stopped {}",job,this);
return false;
}
而任务的真正执行是在已经定义好的线程中,来看看线程的内容,
private Runnable _runnable = new Runnable()
{
public void run()
{
boolean shrink=false;
try
{
// 获取job
Runnable job=_jobs.poll();
while (isRunning())
{
// Job loop
while (job!=null && isRunning())
{
// 执行任务
runJob(job);
job=_jobs.poll();
}
// Idle loop
try
{
// 空闲线程 + 1
_threadsIdle.incrementAndGet();
// 空闲loop
while (isRunning() && job==null)
{
// 当最大空闲事件 < 0时,直接查看是否有job任务可以执行
if (_maxIdleTimeMs<=0)
job=_jobs.take();
// 否则
else
{
// maybe we should shrink?
final int size=_threadsStarted.get();
// 如果当前的线程数大于最小线程数,则有可能会终止其中部分线程。
if (size>_minThreads)
{
// 上次销毁线程时间,该值会在线程被销毁的时候更改
long last=_lastShrink.get();
// 获取当前时间
long now=System.currentTimeMillis();
// 上次是0,或者now-last大于最大空闲
if (last==0 || (now-last)>_maxIdleTimeMs)
{
// 修改last,同时修改线程数目
shrink=_lastShrink.compareAndSet(last,now) &&
_threadsStarted.compareAndSet(size,size-1);
if (shrink)
return;
}
}
// 获取job,如果没有会等待_maxIdleTimeMs ms
job=idleJobPoll();
}
}
}
finally
{
_threadsIdle.decrementAndGet();
}
}
}
catch(InterruptedException e)
{
LOG.ignore(e);
}
catch(Exception e)
{
LOG.warn(e);
}
finally
{
if (!shrink)
_threadsStarted.decrementAndGet();
_threads.remove(Thread.currentThread());
}
}
};
工作流程还是比较清晰的,主要是执行任务,线程等待,然后线程销毁,还有各个共享变量的更新。其实就是不停的在等待的queue中取出job,执行。当线程池中有空闲线程,并且这种情况持续时间达到一定地步(_maxIdleTimeMs),并且当前线程数已经超过了定义好的最小线程数,就终止多余的线程,使线程数达到最小(_minThreads)。这么做是为了减少资源的消耗,因为空闲线程是需要不停的循环,等待job的到来,而这个是需要消耗CPU时间的。
查看已经启动的线程,可以发现上面的8个线程中除了selector和acceptor线程的状态是RUNNABLE,其他线程都是TIME_WAITING,查看某个线程的状态如下,
sun.misc.Unsafe.park(Native Method)
java.util.concurrent.locks.LockSupport.parkNanos(Unknown Source)
java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(Unknown Source)
org.eclipse.jetty.util.BlockingArrayQueue.poll(BlockingArrayQueue.java:342)
org.eclipse.jetty.util.thread.QueuedThreadPool.idleJobPoll(QueuedThreadPool.java:526)
org.eclipse.jetty.util.thread.QueuedThreadPool.access$600(QueuedThreadPool.java:44)
org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:572)
java.lang.Thread.run(Unknown Source)
java.util.concurrent.locks.LockSupport.parkNanos(Unknown Source)
java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(Unknown Source)
org.eclipse.jetty.util.BlockingArrayQueue.poll(BlockingArrayQueue.java:342)
org.eclipse.jetty.util.thread.QueuedThreadPool.idleJobPoll(QueuedThreadPool.java:526)
org.eclipse.jetty.util.thread.QueuedThreadPool.access$600(QueuedThreadPool.java:44)
org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:572)
java.lang.Thread.run(Unknown Source)
可以看到线程挂起在idleJobPoll上,
private Runnable idleJobPoll() throws InterruptedException
{
return _jobs.poll(_maxIdleTimeMs,TimeUnit.MILLISECONDS);
}
_maxIdleTimeMs = 60000,时间为1分钟。所以大部分的时间,六个线程的状态都是TIME_WAITING。
3、线程池在Jetty中的应用
3.1、Acceptor线程
在AbstractConnector中的doStart()函数中,启动了acceptor线程。
/* ------------------------------------------------------------ */
@Override
protected void doStart() throws Exception
{
......
// Start selector thread
synchronized (this)
{
_acceptorThreads = new Thread[getAcceptors()];
for (int i = 0; i < _acceptorThreads.length; i++)
// 根据配置的acceptor的数目,启动N个线程
if (!_threadPool.dispatch(new Acceptor(i)))
throw new IllegalStateException("!accepting");
if (_threadPool.isLowOnThreads())
LOG.warn("insufficient threads configured for {}",this);
}
......
}
而Acceptor就是提交的任务,具体定义如下所示,
private class Acceptor implements Runnable
{
int _acceptor = 0;
Acceptor(int id)
{
_acceptor = id;
}
/* ------------------------------------------------------------ */
public void run()
{
// 获取当前线程
Thread current = Thread.currentThread();
String name;
// 加锁
synchronized (AbstractConnector.this)
{
if (_acceptorThreads == null)
return;
// 设置_acceptoThreads的第_acceptor为当前线程
_acceptorThreads[_acceptor] = current;
// 获取当前线程的名称
name = _acceptorThreads[_acceptor].getName();
// 更新名称
current.setName(name + " Acceptor" + _acceptor + " " + AbstractConnector.this);
}
// 获取当前优先级
int old_priority = current.getPriority();
try
{
// 更新优先级
current.setPriority(old_priority - _acceptorPriorityOffset);
while (isRunning() && getConnection() != null)
{
try
{
accept(_acceptor);
}
catch (EofException e)
{
LOG.ignore(e);
}
catch (IOException e)
{
LOG.ignore(e);
}
catch (InterruptedException x)
{
// Connector has been stopped
LOG.ignore(x);
}
catch (Throwable e)
{
LOG.warn(e);
}
}
}
finally
{
current.setPriority(old_priority);
current.setName(name);
synchronized (AbstractConnector.this)
{
if (_acceptorThreads != null)
_acceptorThreads[_acceptor] = null;
}
}
}
}
可以看到这里有一步是更新线程的名称,规则是name + " Acceptor" + _acceptor + " " + AbstractConnector.this。所以可以看到上面关于8个线程的截图中又一个线程的名称是qtp26994414-12 Acceptor0 SelectChannelConnector@0.0.0.0:8080,正好符合这个规则。正好表明了这个线程是用于accept。
3.2、Selector线程
SelectorManager中的doStart()函数中有下面的一段代码,
// 使用dispatch函数分发线程,并且获取返回值
boolean selecting = dispatch(new Runnable() {
public void run() {
String name = Thread.currentThread().getName();
int priority = Thread.currentThread().getPriority();
try {
SelectSet[] sets = _selectSet;
if (sets == null)
return;
// 获取第i个selector
SelectSet set = sets[id];
// 设置名称
Thread.currentThread().setName(name + " Selector" + id);
if (getSelectorPriorityDelta() != 0)
Thread.currentThread().setPriority(
Thread.currentThread().getPriority()
+ getSelectorPriorityDelta());
LOG.debug("Starting {} on {}", Thread.currentThread(),
this);
// 线程主内容:selector不停的doSelect()
while (isRunning()) {
try {
set.doSelect();
} catch (IOException e) {
LOG.ignore(e);
} catch (Exception e) {
LOG.warn(e);
}
}
} finally {
LOG.debug("Stopped {} on {}", Thread.currentThread(),
this);
Thread.currentThread().setName(name);
if (getSelectorPriorityDelta() != 0)
Thread.currentThread().setPriority(priority);
}
}
});
可以发现,selector线程的命名规则是name + " Selector" + id,线程截图中的selector线程的名称是qtp26994414-10 Selector0,正好符合这个规则。
4、ShutdownThread
当主线程终止前,需要调用的线程。
定义如下,
/* ------------------------------------------------------------ */
/**
* ShutdownThread is a shutdown hook thread implemented as
* singleton that maintains a list of lifecycle instances
* that are registered with it and provides ability to stop
* these lifecycles upon shutdown of the Java Virtual Machine
*/
public class ShutdownThread extends Thread
根据javadoc的描述,该线程提供了在停止虚拟机前先停止各个lifecycle的能力。
public static synchronized void register(LifeCycle... lifeCycles)
{
_thread._lifeCycles.addAll(Arrays.asList(lifeCycles));
if (_thread._lifeCycles.size()>0)
_thread.hook();
}
注册需要关注的lifecycle,在虚拟机停止前,这些lifecycle都需要停止。这里最主要的就是hook()函数,
private synchronized void hook()
{
try
{
if (!_hooked)
Runtime.getRuntime().addShutdownHook(this);
_hooked=true;
}
catch(Exception e)
{
LOG.ignore(e);
LOG.info("shutdown already commenced");
}
}
hook调用了Runtime.getRuntime().addShutdownHook(this),将本身线程加入到了主线程的钩子中。当主线程终止时,会调用这个线程。
下面来看看这个线程的主要内容,
/* ------------------------------------------------------------ */
@Override
public void run()
{
for (LifeCycle lifeCycle : _thread._lifeCycles)
{
try
{
if (lifeCycle.isStarted())
{
lifeCycle.stop();
LOG.debug("Stopped {}",lifeCycle);
}
if (lifeCycle instanceof Destroyable)
{
((Destroyable)lifeCycle).destroy();
LOG.debug("Destroyed {}",lifeCycle);
}
}
catch (Exception ex)
{
LOG.debug(ex);
}
}
}
代码十分简洁明了,不做解释了。
5、总结
Jetty自己实现了一个线程池,以及自定义了线程回收和线程池扩张的机制。这给我们自己完成一个线程池提供了比较好的一个例子,作为学习还是十分不错的。