线程池

线程池

一、简介

线程池类为 java.util.concurrent.ThreadPoolExecutor,常用构造方法为:

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,

long keepAliveTime, TimeUnit unit,BlockingQueue<Runnable> workQueue,RejectedExecutionHandlerhandler)

corePoolSize: 线程池维护线程的最少数量

maximumPoolSize:线程池维护线程的最大数量

keepAliveTime: 线程池维护线程所允许的空闲时间

unit: 线程池维护线程所允许的空闲时间的单位

workQueue: 线程池所使用的缓冲队列

handler: 线程池对拒绝任务的处理策略

一个任务通过 execute(Runnable)方法被添加到线程池,任务就是一个 Runnable类型的对象,任务的执行方法就是 Runnable类型对象的run()方法。

当一个任务通过execute(Runnable)方法欲添加到线程池时:

如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。

如果此时线程池中的数量等于 corePoolSize,但是缓冲队列 workQueue未满,那么任务被放入缓冲队列。

如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。

如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。

也就是:处理任务的优先级为:

核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。

当线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。

unit可选的参数为java.util.concurrent.TimeUnit中的几个静态属性:

NANOSECONDS、MICROSECONDS、MILLISECONDS、SECONDS。

workQueue我常用的是:java.util.concurrent.ArrayBlockingQueue

handler有四个选择:

ThreadPoolExecutor.AbortPolicy()

抛出java.util.concurrent.RejectedExecutionException异常

ThreadPoolExecutor.CallerRunsPolicy()

重试添加当前的任务,他会自动重复调用execute()方法

ThreadPoolExecutor.DiscardOldestPolicy()

抛弃旧的任务

ThreadPoolExecutor.DiscardPolicy()

抛弃当前的任务

二、一般用法举例

//------------------------------------------------------------

//TestThreadPool.java

//package cn.simplelife.exercise;

import java.io.Serializable;

import java.util.concurrent.ArrayBlockingQueue;

import java.util.concurrent.ThreadPoolExecutor;

import java.util.concurrent.TimeUnit;

public class TestThreadPool {

private static int produceTaskSleepTime = 2;

private static int consumeTaskSleepTime = 2000;

private static int produceTaskMaxNumber = 10;

public static void main(String[] args) {

//构造一个线程池

ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 4, 3,

TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3),

new ThreadPoolExecutor.DiscardOldestPolicy());

for(int i=1;i<=produceTaskMaxNumber;i++){

try {

//产生一个任务,并将其加入到线程池

String task = "task@ " + i;

System.out.println("put " + task);

threadPool.execute(new ThreadPoolTask(task));

//便于观察,等待一段时间

Thread.sleep(produceTaskSleepTime);

} catch (Exception e) {

e.printStackTrace();

}

}

}

public static class ThreadPoolTask implements Runnable,Serializable{

private static final long serialVersionUID = 0;

//保存任务所需要的数据

private Object threadPoolTaskData;

ThreadPoolTask(Object tasks){

this.threadPoolTaskData = tasks;

}

public void run(){

//处理一个任务,这里的处理方式太简单了,仅仅是一个打印语句

System.out.println("start .."+threadPoolTaskData);

try {

////便于观察,等待一段时间

Thread.sleep(consumeTaskSleepTime);

} catch (Exception e) {

e.printStackTrace();

}

threadPoolTaskData = null;

}

public Object getTask(){

return this.threadPoolTaskData;

}

}

}

//------------------------------------------------------------

说明:

1、在这段程序中,一个任务就是一个Runnable类型的对象,也就是一个ThreadPoolTask类型的对象。

2、一般来说任务除了处理方式外,还需要处理的数据,处理的数据通过构造方法传给任务。

3、在这段程序中,main()方法相当于一个残忍的领导,他派发出许多任务,丢给一个叫 threadPool的任劳任怨的小组来做。

这个小组里面队员至少有两个,如果他们两个忙不过来,任务就被放到任务列表里面。

如果积压的任务过多,多到任务列表都装不下(超过3个)的时候,就雇佣新的队员来帮忙。但是基于成本的考虑,不能雇佣太多的队员,至多只能雇佣 4个。

如果四个队员都在忙时,再有新的任务,这个小组就处理不了了,任务就会被通过一种策略来处理,我们的处理方式是不停的派发,直到接受这个任务为止(更残忍!呵呵)。

因为队员工作是需要成本的,如果工作很闲,闲到 3SECONDS都没有新的任务了,那么有的队员就会被解雇了,但是,为了小组的正常运转,即使工作再闲,小组的队员也不能少于两个。

4、通过调整 produceTaskSleepTime和 consumeTaskSleepTime的大小来实现对派发任务和处理任务的速度的控制,改变这两个值就可以观察不同速率下程序的工作情况。

5、通过调整4中所指的数据,再加上调整任务丢弃策略,换上其他三种策略,就可以看出不同策略下的不同处理方式。

6、对于其他的使用方法,参看jdk的帮助,很容易理解和使用。

JDK1.5新特性 (三) - 线程池(2)

2008-08-14 14:38

3.2 JDK1.5中的线程池

3.2.1 简单介绍

在J2SE(TM)5.0 中,Doug Lea 编写了一个优秀的并发实用程序开放源码库 util.concurrent,它包括互斥、信号量、诸如在并发访问下执行得很好的队列和散列表之类集合类以及几个工作队列实现。该包中的 PooledExecutor 类是一种有效的、广泛使用的以工作队列为基础的线程池的正确实现。Util.concurrent 定义一个 Executor 接口,以异步地执行 Runnable,另外还定义了 Executor 的几个实现,它们具有不同的调度特征。将一个任务排入 executor 的队列非常简单:

Executor executor = new QueuedExecutor();

Runnable runnable = … ;

executor.execute(runnable);

PooledExecutor 是一个复杂的线程池实现,它不但提供工作线程(worker thread)池中任务的调度,而且还可灵活地调整池的大小,同时还提供了线程生命周期管理,这个实现可以限制工作队列中任务的数目,以防止队列中的任务耗尽所有可用内存,另外还提供了多种可用的关闭和饱和度策略(阻塞、废弃、抛出、废弃最老的、在调用者中运行等)。所有的 Executor 实现为您管理线程的创建和销毁,包括当关闭 executor 时,关闭所有线程,

3.2.2 线程池的使用
线程池类为 java.util.concurrent.ThreadPoolExecutor,常用构造方法为:

ThreadPoolExecutor(int corePoolSize,
                   int maximumPoolSize,
                   long keepAliveTime, TimeUnit unit,
                   BlockingQueue<Runnable> workQueue,
                   RejectedExecutionHandler handler)

·       corePoolSize
线程池维护线程的最少数量

·       maximumPoolSiz
线程池维护线程的最大数量

·       keepAliveTime
线程池维护线程所允许的空闲时间

·       unit
线程池维护线程所允许的空闲时间的单位

·       workQueue
线程池所使用的缓冲队列

·       handler
线程池对拒绝任务的处理策略

一个任务通过 execute(Runnable)方法被添加到线程池,任务就是一个 Runnable类型的对象,任务的执行方法就是 Runnable类型对象的run()方法。

当一个任务通过execute(Runnable)方法欲添加到线程池时:

如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。

  如果此时线程池中的数量等于 corePoolSize,但是缓冲队列 workQueue未满,那么任务被放入缓冲队列。

  如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。

如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。

也就是:处理任务的优先级为:
核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。

当线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。

unit可选的参数为java.util.concurrent.TimeUnit中的几个静态属性:
NANOSECONDS、MICROSECONDS、MILLISECONDS、SECONDS。

workQueue我常用的是:java.util.concurrent.ArrayBlockingQueue

handler有四个选择:

·       ThreadPoolExecutor.AbortPolicy()
抛出java.util.concurrent.RejectedExecutionException异常

·       ThreadPoolExecutor.CallerRunsPolicy()
重试添加当前的任务,他会自动重复调用execute()方法

·       ThreadPoolExecutor.DiscardOldestPolicy()
抛弃旧的任务

·       ThreadPoolExecutor.DiscardPolicy()
抛弃当前的任务

用法举例

package cn.simplelife.exercise;

import java.util.concurrent.ArrayBlockingQueue;

import java.util.concurrent.ThreadPoolExecutor;

import java.util.concurrent.TimeUnit;

public class TestThreadPool {

    private static int produceTaskSleepTime = 2;

    public static void main(String[] args) {

        //构造一个线程池

        ThreadPoolExecutor producerPool = new ThreadPoolExecutor(1, 1, 0,

                TimeUnit.SECONDS, new ArrayBlockingQueue(3),

                new ThreadPoolExecutor.DiscardOldestPolicy());

        //每隔produceTaskSleepTime的时间向线程池派送一个任务。

        int i=1;

        while(true){

            try {

                Thread.sleep(produceTaskSleepTime);

                String task = "task@ " + i;

                System.out.println("put " + task);

                producerPool.execute(new ThreadPoolTask(task));

                i++;

            } catch (Exception e) {

                e.printStackTrace();

            }

        }

    }

}            

package cn.simplelife.exercise;

import java.io.Serializable;

public class ThreadPoolTask implements Runnable,Serializable{

    //JDK1.5中,每个实现Serializable接口的类都推荐声明这样的一个ID

    private static final long serialVersionUID = 0;

    private static int consumeTaskSleepTime = 2000;

    private Object threadPoolTaskData;

    ThreadPoolTask(Object tasks){

        this.threadPoolTaskData = tasks;

    }

    //每个任务的执行过程,现在是什么都没做,除了print和sleep,:)

    public void run(){

        System.out.println("start .."+threadPoolTaskData);

        try {

            //便于观察现象,等待一段时间

            Thread.sleep(consumeTaskSleepTime);

        } catch (Exception e) {

            e.printStackTrace();

        }

        threadPoolTaskData = null;

    }

}    

对这两段程序的说明:

1. 在这段程序中,一个任务就是一个Runnable类型的对象,也就是一个ThreadPoolTask类型的对象。

2. 一般来说任务除了处理方式外,还需要处理的数据,处理的数据通过构造方法传给任务。

3. 在这段程序中,main()方法相当于一个残忍的领导,他派发出许多任务,丢给一个叫 threadPool的任劳任怨的小组来做。

o      这个小组里面队员至少有两个,如果他们两个忙不过来, 任务就被放到任务列表里面。

o      如果积压的任务过多,多到任务列表都装不下(超过3个)的时候,就雇佣新的队员来帮忙。但是基于成本的考虑,不能雇佣太多的队员, 至多只能雇佣 4个。

o      如果四个队员都在忙时,再有新的任务, 这个小组就处理不了了,任务就会被通过一种策略来处理,我们的处理方式是不停的派发, 直到接受这个任务为止(更残忍!呵呵)。

o      因为队员工作是需要成本的,如果工作很闲,闲到 3SECONDS都没有新的任务了,那么有的队员就会被解雇了,但是,为了小组的正常运转,即使工作再闲,小组的队员也不能少于两个。

4. 通过调整 produceTaskSleepTime和 consumeTaskSleepTime的大小来实现对派发任务和处理任务的速度的控制, 改变这两个值就可以观察不同速率下程序的工作情况。

5. 通过调整4中所指的数据,再加上调整任务丢弃策略, 换上其他三种策略,就可以看出不同策略下的不同处理方式。

6. 对于其他的使用方法,参看jdk的帮助,很容易理解和使用。

JDK1.5新特性 (3) - 线程池

来源:互联网 作者:不详 时间:2008-06-04 Tag:JDK 1.5 线程池 点击: 7

3. 线程池

3.1简单的线程池实现

我们通常想要的是同一组固定的工作线程相结合的工作队列,它使用 wait() 和 notify() 来通知等待线程新的工作已经到达了。该工作队列通常被实现成具有相关监视器对象的某种链表。以下代码实现了具有线程池的工作队列。

public class WorkQueue

{

    private final int nThreads;

    private final PoolWorker[] threads;

    private final LinkedList queue;

    public WorkQueue(int nThreads)

    {

        this.nThreads = nThreads;

        queue = new LinkedList();

        threads = new PoolWorker[nThreads];

        for (int i=0; i<nThreads; i++) {

            threads[i] = new PoolWorker();

            threads[i].start();

        }

    }

    public void execute(Runnable r) {

        synchronized(queue) {

            queue.addLast(r);

            queue.notify();

        }

    }

    private class PoolWorker extends Thread {

        public void run() {

            Runnable r;

            while (true) {

                synchronized(queue) {

                    while (queue.isEmpty()) {

                        try

                        {

                            queue.wait();

                        }

                        catch (InterruptedException ignored)

                        {

                        }

                    }

                    r = (Runnable) queue.removeFirst();

                }

                // If we don't catch RuntimeException,

                // the pool could leak threads

                try {

                    r.run();

                }

                catch (RuntimeException e) {

                    // You might want to log something here

                }

            }

        }

    }

}

虽然线程池是构建多线程应用程序的强大机制,但使用它并不是没有风险的。用线程池构建的应用程序容易遭受任何其它多线程应用程序容易遭受的所有并发风险,诸如同步错误和死锁,它还容易遭受特定于线程池的少数其它风险,诸如与池有关的死锁、资源不足和线程泄漏。

用线程池执行任务

如果你开发项目的时候用到很多的short-lived任务,这里推荐使用"线程池"这项技术。你可以创建一个线程池来执行池中的的任务,来取代每次执行任务是都要为新的任务来new和discard。如果一个线程在池中是可用状态,那么任务将立即执行。执行完成之后线程返回池中,否则,任务将一直等待直到有线程处在可用状态。

J2SE 5.0为大家提供了一个新的java.util.concurrent package,并且在这个报中提供了一个pre-built 的线程池架构。在java.util.concurrent中提供了一个Executor 接口,里面有一个execute的方法,参数是Runnable 类型

public interface Executor {

public void execute(Runnable command);

}
使用线程池架构,你就必须创建一个Executor实例,然后你给他分配一些runnable任务,例如:

Java代码 

Executor  executor  ...;      executor.execute(aRunnable1);      executor.execute(aRunnable2);    Executor executor = ...; executor.execute(aRunnable1); executor.execute(aRunnable2); 然后你创建或者找到Executor的实现类,实现类可以立即(或者连续)执行分配的任务,例如: Java代码   class  MyExecutor  implements  Executor  {              public  void  execute(Runnable  r)  {                      new  Thread(r).start();              }      }     class MyExecutor implements Executor { public void execute(Runnable r) { new Thread(r).start(); } } concurrency utilities也包括了一个ThreadPoolExecutor类,它提供了很多对线程的一般性操作,提供了四个构造函数,每个都可以指定如:线程池大小,持续时间,一个线程factory,和拒绝线程的handler。 Java代码   public  ThreadPoolExecutor(int  corePoolSize,                                                          int  maximumPoolSize,                                                          long  keepAliveTime,                                                          TimeUnit  unit,                                                          BlockingQueue<Runnable>  workQueue)     public  ThreadPoolExecutor(int  corePoolSize,                                                          int  maximumPoolSize,                                                          long  keepAliveTime,                                                          TimeUnit  unit,                                                          BlockingQueue<Runnable>  workQueue,                                                          ThreadFactory  threadFactory)      public  ThreadPoolExecutor(int  corePoolSize,                                                          int  maximumPoolSize,                                                          long  keepAliveTime,                                                          TimeUnit  unit,                                                          BlockingQueue<Runnable>  workQueue,                                                          RejectedExecutionHandler   handler)      public  ThreadPoolExecutor(int  corePoolSize,                                                          int  maximumPoolSize,                                                          long  keepAliveTime,                                                          TimeUnit  unit,                                                          BlockingQueue<Runnable>  workQueue,                                                          ThreadFactory  threadFactory,                                                          RejectedExecutionHandler   handler)     public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler  handler) public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler  handler)
但是你不必声明构造函数,Executors类会为你创建一个线程池。在一种最简单的情况下,你在Executors类中声明了newFixedThreadPool方法,并且在池中分配了许多线程。你可以使用ExecutorService(继承Executor的一个接口),去execute和submit 那些Runnable任务,使用ExecutorService中的submit方法可以得到一个返回结果,当然submit也可以返回一个Future对象用来检查任务是否执行。
让我们来先做一个Runnable类,名字为NamePrinter,它通知你运行、暂停、和耗费的时间。 Java代码   public  class  NamePrinter  implements  Runnable  {          private  final  String  name;          private  final  int  delay;          public  NamePrinter(String  name,  int  delay)  {              this.name  name;              this.delay  delay;          }          public  void  run()  {              System.out.println("Starting:  name);              try  {                  Thread.sleep(delay);              catch  (InterruptedException  ignored)  {              }              System.out.println("Done  with:  name);          }      }     public class NamePrinter implements Runnable { private final String name; private final int delay; public NamePrinter(String name, int delay) { this.name = name; this.delay = delay; } public void run() { System.out.println("Starting: " + name); try { Thread.sleep(delay); } catch (InterruptedException ignored) { } System.out.println("Done with: " + name); } } 然后下面是我们测试的项目UsePool,它创建一个有三个线程的线程池,分配了10个任务给它(运行10次NamePrinter),UsePool在被shutdown 和 awaitTermination之前将等待并执行分配的任务。一个ExecutorService必须要在terminated之前执行shutdown,shutdownNow方法是立即尝试shutdown操作。shutdownNow 方法将返回没有被执行的任务。 Java代码   import  java.util.concurrent.*;      import  java.util.Random;      public  class  UsePool  {          public  static  void  main(String  args[])  {              Random  random  new  Random();              ExecutorService  executor                                Executors.newFixedThreadPool(3);              //  Sum  up  wait  times  to  know  when  to  shutdown              int  waitTime  500;              for  (int  i=0;  i<10;  i++)  {                  String  name  "NamePrinter  i;                  int  time  random.nextInt(1000);                  waitTime  +=  time;                  Runnable  runner  new  NamePrinter(name,  time);                  System.out.println("Adding:  name  time);                  executor.execute(runner);              }              try  {                  Thread.sleep(waitTime);                  executor.shutdown();                  executor.awaitTermination                                  (waitTime,  TimeUnit.MILLISECONDS);              catch  (InterruptedException  ignored)  {              }              System.exit(0);          }           import java.util.concurrent.*; import java.util.Random; public class UsePool { public static void main(String args[]) { Random random = new Random(); ExecutorService executor = Executors.newFixedThreadPool(3); // Sum up wait times to know when to shutdown int waitTime = 500; for (int i=0; i<10; i++) { String name = "NamePrinter " + i; int time = random.nextInt(1000); waitTime += time; Runnable runner = new NamePrinter(name, time); System.out.println("Adding: " + name + " / " + time); executor.execute(runner); } try { Thread.sleep(waitTime); executor.shutdown(); executor.awaitTermination (waitTime, TimeUnit.MILLISECONDS); } catch (InterruptedException ignored) { } System.exit(0); } } 输出的结果是:
Adding: NamePrinter 0 / 30
Adding: NamePrinter 1 / 727
Adding: NamePrinter 2 / 980
Starting: NamePrinter 0
Starting: NamePrinter 1
Starting: NamePrinter 2
Adding: NamePrinter 3 / 409
Adding: NamePrinter 4 / 49
Adding: NamePrinter 5 / 802
Adding: NamePrinter 6 / 211
Adding: NamePrinter 7 / 459
Adding: NamePrinter 8 / 994
Adding: NamePrinter 9 / 459
Done with: NamePrinter 0
Starting: NamePrinter 3
Done with: NamePrinter 3
Starting: NamePrinter 4
Done with: NamePrinter 4
Starting: NamePrinter 5
Done with: NamePrinter 1
Starting: NamePrinter 6
Done with: NamePrinter 6
Starting: NamePrinter 7
Done with: NamePrinter 2
Starting: NamePrinter 8
Done with: NamePrinter 5
Starting: NamePrinter 9
Done with: NamePrinter 7
Done with: NamePrinter 9
Done with: NamePrinter 8
注意前三个NamePrinter对象启动的非查的快,之后的NamePrinter对象每次启动都要等待前面的执行完成。
在J2SE 5.0有非常多的pooling framework可以用,例如,你可以创建一个scheduled线程池……
更多信息还是看官方的concurrency utilities,地址: http://java.sun.com/j2se/1.5.0/docs/guide/concurrency/

public class PoolAsynService extends BaseService implements Runnable {

    private Thread thread = new Thread(this);

    private List waitToList = (List) Collections.synchronizedList(new LinkedList());

    // ////////////线程池参数/////////////////

    private int corePoolSize = 5;// : 线程池维护线程的最少数量

    private int maximumPoolSize = 10;// :线程池维护线程的最大数量

    private long keepAliveTime = 60;// : 线程池维护线程所允许的空闲时间

    private TimeUnit unit = TimeUnit.SECONDS;// : 线程池维护线程所允许的空闲时间的单位

    private BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<Runnable>(10);// :

    // 线程池所使用的缓冲队列

    private RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy();// :

    // 线程池对拒绝任务的处理策略

    // //////////线程池参数/////////////////

    private ThreadPoolExecutor threadPool = new ThreadPoolExecutor(corePoolSize,

            maximumPoolSize, keepAliveTime, unit, workQueue, handler);

    public void run() {

        while (!thread.isInterrupted()) {

            if (!waitToList.isEmpty()) {

                try {

                    threadPool.execute(new Executor());

                } catch (Exception e) {

                    logger.error("pool execute error!!!", e);

                }

            }

            

            Tools.block(25);

        }

    }

    public void doAsync(Object executor, Object... objects) {

        Throwable t = new Throwable();

        StackTraceElement[] elements = t.getStackTrace();

        StackTraceElement element = elements[1];

        String method = element.getMethodName();

        AsyncContext ctx = new AsyncContext();

        ctx.args = objects;

        ctx.executor = executor;

        ctx.method = method;

        if (method.endsWith("PA")) {

            waitToList.add(ctx);

        } else {

            logger.warn("async method name is not good!");

        }

    }

    private class AsyncContext {

        String method;

        Object executor;

        Object[] args;

    }

    private class Executor implements Runnable {

        public void run() {

            if (!waitToList.isEmpty()) {

                try {

                    Object task = waitToList.remove(0);

                    AsyncContext ctx = (AsyncContext) task;

                    doTaskByCtx(ctx);

                } catch (Exception e) {

                    logger.error("async error!!!", e);

                }

            }

        }

        private void doTaskByCtx(AsyncContext ctx) {

            String targetMethodName = ctx.method.substring(0, ctx

你可能感兴趣的:(线程池)