`
iamzhongyong
  • 浏览: 804589 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

threadPoolExecutor代码

    博客分类:
  • java
 
阅读更多

java.util.concurrent包的出现,为我们实现多线程并发编程带来了很大的方便,但是同时也有很多问题,如果对于原理缺乏了解,使用起来可能就会有一些点忽略掉,导致应用出现问题,关于线程池已经想看源代码很长时间了,不能再拖下去了,本来打算是从头自己看源代码,但是发现一个哥们的博客,上面讲的挺详细,于是打算借鉴一些过来,然后加入自己的理解,不再重复造轮子了。

 

要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在Executors类里面提供了一些静态工厂,生成一些常用的线程池。

1newSingleThreadExecutor:创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

2newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

3newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

4newScheduledThreadPool:创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

5newSingleThreadScheduledExecutor:创建一个单线程的线程池。此线程池支持定时以及周期性执行任务的需求。

 

 

首先看ThreadPoolExecutor的构造函数中的核心,对于各个参数有必要介绍一下。

 

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
 

 

1corePoolSizecore zise 大家都懂得,不解释

2maximumPoolsize:线程池的最大数量

3keepAliveTime:空闲的线程等待工作的最长时间,纳秒,和unit配合使用

4woreQueue:线程池处理的任务队列

5threadFactory:线程创建的工厂类,threadPoolExecutor中的有默认实现,开始是最好自己实现一个,并且命名,方便问题排查

6handler:线程池的拒绝策略,默认有四种可选择,也可自己写一个

从两个if判断里面能够看出参数缺一不可,keepAliveTime的时间通过TimeUnit转为纳秒。

 

 

 

 

ThreadFactory是一个接口,里面就一个方法,Thread newThread(Runnable r) 用来创造线程

Executors中有一个默认的实现:DefaultThreadFactory

 

static class DefaultThreadFactory implements ThreadFactory {
        static final AtomicInteger poolNumber = new AtomicInteger(1);
        final ThreadGroup group;
        final AtomicInteger threadNumber = new AtomicInteger(1);
        final String namePrefix;

        DefaultThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null)? s.getThreadGroup() :
                                 Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" +
                          poolNumber.getAndIncrement() +
                         "-thread-";
        }

        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }
 

 



通过默认的factory创造的线程,是非守护,并且优先级为NORM_PRIORITY,线程的名字为“pool-线程池数目-thread-线程的编号”。

对于线程池中的所有线程默认都转换为非后台线程,这样主线程退出时不会直接退出JVM,而是等待线程池结束。还有一点就是默认将线程池中的所有线程都调为同一个级别,这样在操作系统角度来看所有系统都是公平的,不会导致竞争堆积。

 

线程的状态

为了节省资源和有效释放资源关闭一个线程池就显得很重要。线程池Executor是异步的执行任务,因此任何时刻不能够直接获取提交的任务的状态。这些任务有可能已经完成,也有可能正在执行或者还在排队等待执行。因此关闭线程池可能出现一下几种情况:

1)平缓关闭:已经启动的任务全部执行完毕,同时不再接受新的任务

2)立即关闭:取消所有正在执行和未执行的任务

线程池的状态,在源代码里面有以下注释,感觉比任务文章来的给力点呵呵

RUNNING

接收新的任务,同时处理队列中已有的任务

SHUTDOWN

不再接受新的任务,但是处理队列中的任务

STOP

不接受新任务,不处理队列中任务,同时终止正在运行的任务

TERMINATED

stop一样,但是所有的线程终止

RUNNINGSHUTDOWN:调用shutdown()方法

RUNNING  or SHUTDOWN STOP :调用shutdownNow()方法

SHUTDOWN TERMINATED 任务队列和线程池都空时

STOP TERMINATED :线程池为空时

 

 

 

 

     线程池的拒绝策略

这四种策略是独立无关的,是对任务拒绝处理的四中表现形式。最简单的方式就是直接丢弃任务。但是却有两种方式,到底是该丢弃哪一个任务,比如可以丢弃当前将要加入队列的任务本身(DiscardPolicy)或者丢弃任务队列中最旧任务(DiscardOldestPolicy)。丢弃最旧任务也不是简单的丢弃最旧的任务,而是有一些额外的处理。除了丢弃任务还可以直接抛出一个异常(RejectedExecutionException),这是比较简单的方式。抛出异常的方式(AbortPolicy)尽管实现方式比较简单,但是由于抛出一个RuntimeException,因此会中断调用者的处理过程。除了抛出异常以外还可以不进入线程池执行,在这种方式(CallerRunsPolicy)中任务将有调用者线程去执行。

四种策略均实现了RejectedExecutionHandler接口

方法rejectedExecution()在线程池不能接收任务的时候会被ThreadPoolExecutor调用,参数runnable是需要被执行的任务,executor是线程池对象。

public interface RejectedExecutionHandler {

    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);

}

 

除了上述的几种拒绝策略外,还可以自己实现RejectedExecutionHandler接口,例如如下拒绝策略,当任务没法加入任务对队列时,阻塞,直到可以放入队列中。

 

ejectedExecutionHandler handler = new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable task,ThreadPoolExecutor executor) {
           // 如果处理不过, 打日志, 并阻塞,
           logger.error("The thread pool is full and be blocked!"
                 + " corePoolSize=" + executor.getCorePoolSize()
                 + ", maxPoolSize=" + executor.getMaximumPoolSize()
                 + ", keepAliveTime="
                 + executor.getKeepAliveTime(TimeUnit.SECONDS)
                 + ", poolSize=" + executor.getPoolSize()
                 + ", activeCount=" + executor.getActiveCount()
                 + ", taskCount=" + executor.getTaskCount()
                 + ", largestPoolSize=" + executor.getLargestPoolSize()
                 + ", completedTaskCount="
                 + executor.getCompletedTaskCount());
 
           try {
              executor.getQueue().put(task);
           } catch (InterruptedException e) {
              logger.error("put the task into queue is failure!", e);
           }
        }
     };
 

 

 

 

  关于Worker对象

对于ThreadPoolExecutor而言,一个线程就是一个Worker对象,它与一个线程绑定,当Worker执行完毕就是线程执行完毕。

 

 /**
         * Runs a single task between before/after methods.
         */
        private void runTask(Runnable task) {
            final ReentrantLock runLock = this.runLock;
            runLock.lock();
            try {
                /*
                 * Ensure that unless pool is stopping, this thread
                 * does not have its interrupt set. This requires a
                 * double-check of state in case the interrupt was
                 * cleared concurrently with a shutdownNow -- if so,
                 * the interrupt is re-enabled.
                 */
                if (runState < STOP &&
                    Thread.interrupted() &&
                    runState >= STOP)
                    thread.interrupt();
                /*
                 * Track execution state to ensure that afterExecute
                 * is called only if task completed or threw
                 * exception. Otherwise, the caught runtime exception
                 * will have been thrown by afterExecute itself, in
                 * which case we don't want to call it again.
                 */
                boolean ran = false;
                beforeExecute(thread, task);
                try {
                    task.run();
                    ran = true;
                    afterExecute(task, null);
                    ++completedTasks;
                } catch (RuntimeException ex) {
                    if (!ran)
                        afterExecute(task, ex);
                    throw ex;
                }
            } finally {
                runLock.unlock();
            }
        }

        /**
         * Main run loop
         */
        public void run() {
            try {
                Runnable task = firstTask;
                firstTask = null;
                while (task != null || (task = getTask()) != null) {
                    runTask(task);
                    task = null;
                }
            } finally {
                workerDone(this);
            }
        }
 

 

执行一个给定的任务,任务可能会在一个新的线程中执行,也有可能在线程池中已近存在的线程中执行。如果任务不能被提交执行(可能是因为线程池shutdown或者任务队列已经满了),此时这个任务会被handle来处理

 

 

几种常见的死锁

1)锁顺序死锁(lock-ordering deadlock):多个线程试图通过不同的顺序获得多个相同的资源,则发生的循环锁依赖现象。

2)动态的锁顺序死锁(Dynamic Lock Order Deadlocks):多个线程通过传递不同的锁造成的锁顺序死锁问题。

3)资源死锁(Resource Deadlocks):线程间相互等待对方持有的锁,并且谁都不会释放自己持有的锁发生的死锁。也就是说当现场持有和等待的目标成为资源,就有可能发生此死锁。这和锁顺序死锁不一样的地方是,竞争的资源之间并没有严格先后顺序,仅仅是相互依赖而已。

锁顺序死锁

最经典的锁顺序死锁就是LeftRightDeadLock.

 

public class LeftRightDeadLock {
    final Object left = new Object();
    final Object right = new Object();
    public void doLeftRight() {
        synchronized (left) {
            synchronized (right) {
                execute1();
            }
        }
    }
    public void doRightLeft() {
        synchronized (right) {
            synchronized (left) {
                execute2();
            }
        }
    }
    private void execute2() {
    }
    private void execute1() {
    }
}
 

 

 

这个例子很简单,当两个线程分别获取到leftright锁时,互相等待对方释放其对应的锁,很显然双方都陷入了绝境。

动态的锁顺序死锁

与锁顺序死锁不同的是动态的锁顺序死锁只是将静态的锁变成了动态锁。 一个比较生动的例子是这样的。

 

public void transferMoney(Account fromAccount,//
        Account toAccount,//
        int amount
        ) {
    synchronized (fromAccount) {
        synchronized (toAccount) {
            fromAccount.decr(amount);
            toAccount.add(amount);
        }
    }
}
 

 

当我们银行转账的时候,我们期望锁住双方的账户,这样保证是原子操作。 看起来很合理,可是如果双方同时在进行转账操作,那么就有可能发生死锁的可能性。

 

很显然,动态的锁顺序死锁的解决方案应该看起来和锁顺序死锁解决方案差不多。 但是一个比较特殊的解决方式是纠正这种顺序。 例如可以调整成这样:

 

Object lock = new Object();
public void transferMoney(Account fromAccount,//
        Account toAccount,//
        int amount
        ) {
    int order = fromAccount.name().compareTo(toAccount.name());
    Object lockFirst = order>0?toAccount:fromAccount;
    Object lockSecond = order>0?fromAccount:toAccount;
    if(order==0){
        synchronized(lock){
            synchronized(lockFirst){
                synchronized(lockSecond){
                    //do work
                }
            }
        }
 
    }else{
        synchronized(lockFirst){
            synchronized(lockSecond){
                //do work
            }
        }
    }
}
 

 

这个挺有意思的。比较两个账户的顺序,保证此两个账户之间的传递顺序总是按照某一种锁的顺序进行的, 即使多个线程同时发生,也会遵循一次操作完释放完锁才进行下一次操作的顺序,从而可以避免死锁的发生。

资源死锁

资源死锁比较容易理解,就是需要的资源远远大于已有的资源,这样就有可能线程间的资源竞争从而发生死锁。 一个简单的场景是,应用同时从两个连接池中获取资源,两个线程都在等待对方释放连接池的资源以便能够同时获取 到所需要的资源,从而发生死锁。

资源死锁除了这种资源之间的直接依赖死锁外,还有一种叫线程饥饿死锁(thread-starvation deadlock)。 严格意义上讲,这种死锁更像是活跃度问题。例如提交到线程池中的任务由于总是不能够抢到线程从而一直不被执行,造成任务的“假死”状况。

除了上述几种问题外,还有协作对象间的死锁以及开发调用的问题。这个描述起来会比较困难,也不容易看出死锁来。

避免和解决死锁

通常发生死锁后程序难以自恢复。但也不是不能避免的。 有一些技巧和原则是可以降低死锁可能性的。

最简单的原则是尽可能的减少锁的范围。锁的范围越小,那么竞争的可能性也越小。 尽快释放锁也有助于避开锁顺序。如果一个线程每次最多只能够获取一个锁,那么就不会产生锁顺序死锁。

 


如何设置最有的线程数量

并行的任务增加资源显然能够提高性能,但是如果是串行的任务,增加资源并不一定能够得到合理的性能提升。 Amdahl定律描述的在一个系统中,增加处理器资源对系统行的提升比率。 假定在一个系统中,F是必须串行化执行的比重,N是处理器资源,那么随着N的增加最多增加的加速比:

理论上,当N趋近于无穷大时,加速比最大值无限趋近于1/F 这意味着如果一个程序的串行化比重为50%,那么并行化后最大加速比为2倍。

加速比除了可以用于加速的比率外,也可以用于衡量CPU资源的利用率。如果每一个CPU的资源利用率为100%,那么CPU的资源每次翻倍时,加速比也应该翻倍。 事实上,在拥有10个处理器的系统中,程序如果有10%是串行化的,那么最多可以加速1/(0.1+(1-0.1)/10)=5.3倍,换句话说CPU的利用率只用5.3/10=53%。而如果处理器增加到100倍,那么加速比为9.2倍,也就是说CPU的利用率只有个9.3%

 

 

假设引入的多线程都用于计算,那么性能一定会有很大的提升么? 其实引入多线程以后也会引入更多的开销。

切换上下文

 

如果可运行的线程数大于CPU的内核数,那么OS会根据一定的调度算法,强行切换正在运行的线程,从而使其它线程能够使用CPU周期。

切换线程会导致上下文切换。线程的调度会导致CPU需要在操作系统和进程间花费更多的时间片段,这样真正执行应用程序的时间就减少了。另外上下文切换也会导致缓存的频繁进出,对于一个刚被切换的线程来说,可能由于高速缓冲中没有数据而变得更慢,从而导致更多的IO开销。

内存同步

 

不同线程间要进行数据同步,synchronized以及volatile提供的可见性都会导致缓存失效。线程栈之间的数据要和主存进行同步,这些同步有一些小小的开销。如果线程间同时要进行数据同步,那么这些同步的线程可能都会受阻。

阻塞

 

当发生锁竞争时,失败的线程会导致阻塞。通常阻塞的线程可能在JVM内部进行自旋等待,或者被操作系统挂起。自旋等待可能会导致更多的CPU切片浪费,而操作系统挂起则会导致更多的上下文切换。

了解了性能的提升的几个方面,也了解性能的开销后,应用程序就要根据实际的场景进行取舍和评估。没有一劳永逸的优化方案,不断的进行小范围改进和调整是提高性能的有效手段。当前一些大的架构调整也会导致较大的性能的提升。

简单的原则是在保证逻辑正确的情况小,找到性能瓶颈,小步改进和优化。

 

 

 

 

 

对于多线程编程,源码的分析一下文章总结的很齐全

 

  1. J.U.C 整体认识
  2. 原子操作 part 1 从AtomicInteger开始
  3. 原子操作 part 2 数组、引用的原子操作
  4. 原子操作 part 3 指令重排序与happens-before法则
  5. 原子操作 part 4 CAS操作
  6. 锁机制 part 1 Lock与ReentrantLock
  7. 锁机制 part 2 AQS
  8. 锁机制 part 3 加锁的原理 (Lock.lock)
  9. 锁机制 part 4 锁释放与条件变量 (Lock.unlock And Condition)
  10. 锁机制 part 5 闭锁 (CountDownLatch)
  11. 锁机制 part 6 CyclicBarrier
  12. 锁机制 part 7 信号量 (Semaphore)
  13. 锁机制 part 8 读写锁 (ReentrantReadWriteLock) (1)
  14. 锁机制 part 9 读写锁 (ReentrantReadWriteLock) (2)
  15. 锁机制 part 10 锁的一些其它问题
  16. 并发容器 part 1 ConcurrentMap (1)
  17. 并发容器 part 2 ConcurrentMap (2)
  18. 并发容器 part 3 ConcurrentMap (3)
  19. 并发容器 part 4 并发队列与Queue简介
  20. 并发容器 part 5 ConcurrentLinkedQueue
  21. 并发容器 part 6 可阻塞的BlockingQueue (1)
  22. 并发容器 part 7 可阻塞的BlockingQueue (2)
  23. 并发容器 part 8 可阻塞的BlockingQueue (3)
  24. 并发容器 part 9 双向队列集合 Deque
  25. 并发容器 part 10 双向并发阻塞队列 BlockingDeque
  26. 并发容器 part 11 Exchanger
  27. 并发容器 part 12 线程安全的List/Set CopyOnWriteArrayList/CopyOnWriteArraySet
  28. 线程池 part 1 简介
  29. 线程池 part 2 Executor 以及Executors
  30. 线程池 part 3 Executor 生命周期
  31. 线程池 part 4 线程池任务拒绝策略
  32. 线程池 part 5 周期性任务调度
  33. 线程池 part 6 线程池的实现及原理 (1)
  34. 线程池 part 7 线程池的实现及原理 (2)
  35. 线程池 part 8 线程池的实现及原理 (3)
  36. 线程池 part 9 并发操作异常体系
  37. 并发总结 part 1 死锁与活跃度
  38. 并发总结 part 2 常见的并发场景
  39. 并发总结 part 3 常见的并发陷阱
  40. 并发总结 part 4  性能与伸缩性

 

 

 

分享到:
评论

相关推荐

    线程池之ThreadPoolExecutor.docx

    线程池是多线程编程中一种高效管理线程资源的方式,主要由Java的`ThreadPoolExecutor`类实现。线程池的工作机制在于控制线程数量,它会将任务放入队列,然后根据线程池的设定创建并启动线程执行这些任务。如果线程...

    线程池ThreadPoolExecutor使用简介与方法实例

    下面是一个使用ThreadPoolExecutor的示例代码: ``` package demo; import java.io.Serializable; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java...

    java ThreadPoolExecutor 并发调用实例详解

    在给定的代码中,我们可以看到一个使用 ThreadPoolExecutor 并发调用实例的示例。在这个示例中,我们首先创建了一个 ThreadPoolExecutor 对象,并将其配置为拥有 8 个核心线程和 12 个最大线程,keep alive 时间为 ...

    Python线程池模块ThreadPoolExecutor用法分析

    例如,以下代码创建了一个包含2个线程的线程池: ```python with ThreadPoolExecutor(max_workers=2) as executor: # 在这里执行线程池任务 ``` 使用`with`语句的好处在于它会在退出`with`块时自动调用`executor....

    java线程池ThreadPoolExecutor类使用详解.docx

    在《阿里巴巴java开发手册》中...另外由于前面几种方法内部也是通过ThreadPoolExecutor方式实现,使用ThreadPoolExecutor有助于大家明确线程池的运行规则,创建符合自己的业务场景需要的线程池,避免资源耗尽的风险。

    Spring线程池ThreadPoolExecutor配置并且得到任务执行的结果

    Spring线程池ThreadPoolExecutor配置并且得到任务执行的结果 在Java中,线程池是一种非常重要的技术,能够帮助我们更好地管理线程资源,提高系统的性能和可扩展性。其中,ThreadPoolExecutor是Java中的一种线程池...

    Java线程池与ThreadPoolExecutor.pdf

    线程池通过ThreadPoolExecutor类实现,这是一个高度可配置的工具,能够根据具体需求定制线程的创建、管理和销毁策略。 ThreadPoolExecutor的核心参数包括: 1. corePoolSize:核心线程数,这是线程池在非繁忙状态下...

    Android线程池管理的代码例子

    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler); ``` ScheduledExecutorService则是一...

    Java ThreadPoolExecutor 线程池的使用介绍

    提供工厂方法来创建不同类型的线程池,这篇文章主要介绍了Java ThreadPoolExecutor 线程池的使用介绍,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来...

    11-线程池ThreadPoolExecutor底层原理源码分析(上)-周瑜.pdf

    根据提供的文件信息,我们可以深入探讨线程池`ThreadPoolExecutor`的工作原理及其实现细节,同时也会涉及并发编程中的一些关键概念和技术。 ### 线程池`ThreadPoolExecutor`概述 `ThreadPoolExecutor`是Java中非常...

    实战JAVA高并发程序设计源代码汇总版

    4. **线程池**:`ExecutorService`和`ThreadPoolExecutor`是JAVA中实现线程池的主要类,它们允许开发者有效地管理线程,提高系统资源利用率。 5. **原子变量**:`java.util.concurrent.atomic`包中的原子类,如`...

    《第二行代码》11章源码

    可能包括使用AsyncTask、Handler、Looper、ThreadPoolExecutor等进行异步处理。如果源码中有这些内容,那么开发者可以从中学习到如何避免主线程阻塞。 3. **网络编程**:Android应用经常需要与服务器交互,获取或...

    Java 核心技术 第八版源代码【Core Java. Volume II. Advanced Features, 8th Edition】

    书中会涵盖`Thread`类、`Runnable`接口、守护线程、线程同步(包括`synchronized`关键字、`wait()`, `notify()`, `notifyAll()`方法)、线程池(`ExecutorService`, `ThreadPoolExecutor`, `Future`)等概念。...

    高并发之——通过源码深度解析ThreadPoolExecutor类是如何保证线程池正确运行的

    AtomicInteger类型的常量ctl是贯穿线程池整个生命周期的重要属性,它是一个原子类对象,主要用来保存线程的数量和线程池的状态,我们看下与这个属性相关的代码如下所示。 //主要用来保存线程数量和线程池的状态,高...

    spring线程池ThreadPoolExecutor配置以及FutureTask的使用

    这个类是Spring对Java内置的`java.util.concurrent.ThreadPoolExecutor`的封装,允许开发者在Spring应用上下文中声明式地定义线程池。在本篇文章中,我们将深入探讨`ThreadPoolTaskExecutor`的配置及其使用,并结合`...

    线程池使用示例(含源代码)

    在Java中,我们可以使用`java.util.concurrent`包下的`ExecutorService`接口及其实现类,如`ThreadPoolExecutor`来创建和管理线程池。 线程池的主要优点包括: 1. **资源管理**:线程池能有效控制运行的线程数量,...

    Java通用范例开发金典源代码

    - 线程池:ExecutorService和ThreadPoolExecutor的使用,以及线程池的配置和优化。 5. **输入输出流** - 文件操作:如FileInputStream和FileOutputStream用于读写文件。 - 缓冲流:BufferedReader和...

    100行Java代码构建一个线程池

    在示例代码中,作者使用简单的Java代码创建了一个线程池,通过`ThreadPoolManager`类来管理线程池,并使用`SimpleThread`类作为处理任务的线程。 【示例代码分析】 1. `TestThreadPool`类是测试类,模拟客户端发送...

Global site tag (gtag.js) - Google Analytics