Jetty学习【四】 线程

本文详细介绍了Jetty中的线程池实现,包括QueuedThreadPool的属性、构造函数、启动流程和任务执行过程。同时,探讨了线程池在Jetty内部的应用,如Acceptor线程和Selector线程的角色,以及ShutdownThread的机制,最后进行了全面的总结。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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)

可以看到线程挂起在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自己实现了一个线程池,以及自定义了线程回收和线程池扩张的机制。这给我们自己完成一个线程池提供了比较好的一个例子,作为学习还是十分不错的。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值