线程池简介 我们在实现并发功能时会频繁的创建和销毁线程,这样会加大系统的开销,而线程池会缓存一定数量的线程,可以避免这样的情况,并且线程池可以对现场进行简单的管理,简化了并发编程。
ThreadPoolExecutor Java中的线程池定义为Executor接口,实现这个接口的类为ThreadPoolExecutor,是线程池的真正实现。常用的构造方法为
1 2 3 4 5 6 public ThreadPoolExecutor (int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory)
corePoolSize:线程池中的核心线程数量,核心线程理论上会一直存活,就算是闲置状态。
maximumPoolSize:允许线程池最大容纳的线程数,一旦满了,后面新到来的任务会被阻塞。
keepAliveTime:线程闲置的超时时长,闲置时间超过就会被销毁,适用于非核心线程。如果ThredPoolExecutor的allowCoreThreadTimeOut设置为true,那么核心线程也会超时销毁。
unit:超时时长的单位,为枚举类型,常用的有毫秒 TimeUnit.MILLISECONDS、秒TimeUnit.SECONDS、分TimeUnit.MINUTES。
workQueue:任务队列。
threadFactory:提供创建线程的功能。
当任务到来的时候,如果核心线程数量没有达到最大核心线程数量,那么就创建核心线程执行任务。如果超过就将任务插入队列等待核心线程空下来。如果队列也满了,那就只有创建非核心线程(或者复用闲置非核心线程)执行任务。如果全部线程数达到最大值,拒绝执行任务。
四种线程池 FixedThreadPool 构造函数如下
1 2 3 4 5 public static ExecutorService newFixedThreadPool (int nThreads) { return new ThreadPoolExecutor (nThreads, nThreads, 0L , TimeUnit.MILLISECONDS, new LinkedBlockingQueue <Runnable>()); }
核心线程和线程数一样说明只有核心线程,那么线程都不会被回收,可以很快的响应任务。
且队列没有没有大小限制,可以看出新到来的任务肯定不会被拒绝,因为队列不会满,所有任务都可以被执行(核心线程满了就等待)。
CachedThreadPool 构造函数如下
1 2 3 4 5 public static ExecutorService newCachedThreadPool () { return new ThreadPoolExecutor (0 , Integer.MAX_VALUE, 60L , TimeUnit.SECONDS, new SynchronousQueue <Runnable>()); }
没有核心线程,且线程数是int的最大值,适用于大量的任务。
SynchronousQueue实际上是不能插入的,所以任务到来的时候,首先没有核心线程,那么就会插入队列等待核心线程,但是这个队列不能插入,所以开启非核心线程执行,也就是说任务到来立马执行,适用于大量且耗时少的任务。
ScheduledThreadPool 构造函数如下
1 2 3 4 5 6 7 8 9 public static ScheduledExecutorService newScheduledThreadPool (int corePoolSize) { return new ScheduledThreadPoolExecutor (corePoolSize); } public ScheduledThreadPoolExecutor (int corePoolSize, ThreadFactory threadFactory) { super (corePoolSize, Integer.MAX_VALUE, 0 , NANOSECONDS, new DelayedWorkQueue (), threadFactory); }
SingleThreadExecutor 构造函数如下
1 2 3 4 5 6 public static ExecutorService newSingleThreadExecutor () { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor (1 , 1 , 0L , TimeUnit.MILLISECONDS, new LinkedBlockingQueue <Runnable>())); }
就一个核心线程,线程只允许一个,等待队列没限制,也就是说任务是串行执行的,不用考虑同步的问题。
执行任务 执行Runnable 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class Main { public static void main (String[] args) { ExecutorService exe = Executors.newCachedThreadPool(); for (int i = 0 ; i != 5 ; i++) { exe.execute(new Task ()); } exe.shutdown(); } } class Task implements Runnable { @Override public void run () { } }
通过execute执行任务,shutdown()方法可以防止新任务被提交给这个Executor,执行完之前的所有任务后会尽快退出。
执行Callable Runnable执行任务不返回任何值,想要有返回值的话可以使用Callable
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public class Main { public static void main (String[] args) { ExecutorService exe = Executors.newCachedThreadPool(); List<Future<String>> results = new ArrayList <>(); for (int i = 0 ; i != 5 ; i++) { results.add(exe.submit(new Task ())); } exe.shutdown(); for (Future<String> fs : results) { try { System.out.println(fs.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } } } class Task implements Callable <String> { public static int i = 0 ; @Override public String call () { return i++ + "" ; } }
首先确定返回值的类型,这里是String,执行的方法是call而不是run。使用submit提交任务而不是execute,submit会返回Future的值,然后我们遍历通过get()方法拿到返回值。值得注意的是,当Future未完成的时候,get()方法会阻塞,直到完成拿到返回值,我们可以通过isDone()判断是否完成。