并发工具和默认线程池解析

  • 前言

前面和大家分析了下自定义线程池的具体实现,这里在和大家讨论下自定义的线程池,和内置的一些并发工具的知识

  • FixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

这是一个指定线程数的默认线程池
这里从创建的参数可以清楚地看到,就是默认的创建了一个输入线程数大小的线程池,并使用底层用链表的阻塞队列,这里他的队列默认长度是Integer.MAX_VALUE
为什么不推荐使用这个方式来创建线程池呢?
从创建的参数构造我们可以看出它使用的阻塞队列可以近乎认为是一个无界队列,这样就使得我们设置的最大线程数这个限制条件永远不会生效,并且由于一直可以加入队列,这个时间限制的参数也不会生效,在极限情况下,如果任务不断添加,也会产生内存溢出的情况

  • SingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

这里就是创建了一个线程数为1的线程池,同样使用了这么一个无界队列,这样上述问题同样会发生,这里就不进行赘述了

  • CachedThreadPool
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

这里可以看见参数的设置,核心线程数为0,最大线程数是Integer.MAX_VALUE,可以近乎认为最大线程数无界,这里可以看到使用的阻塞队列是没有容量的,一个put必须对应一个take,这样就可以做到使用的时候创建出来,并且线程60秒没有使用就会被终止
但是这样同样会产生问题,就是按照这种情况,如果提交任务的速度远远高于执行任务的速度,那么就会不断地创建非核心线程,可能会因为创建过多线程导致耗尽CPU和内存资源

  • ScheduledThreadPoolExecutor
public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }

这个就是主要用来在给定延迟之后运行任务的

  • CountDownLatch
这个应用就是允许一个或者多个线程等待其他线程完成操作
在生成构造方法的时候会传入一个int的参数作为计数器
也就是我们调用countDown的方法时,计数器就会-1,await方法会阻塞当前线程,直到计数器为0

线程 keepAliveSeconds 默认单位 task默认线程池大小_阻塞队列

  • CyclicBarrier
这可循环使用的屏障,和CountDownList区别就是可以重用,当他的计数器为0时会重置计数器,可以继续使用
这里具有了一些新的功能,在构造中可以创建Runnable,用于在线程到达屏障时候优先执行

线程 keepAliveSeconds 默认单位 task默认线程池大小_自定义_02

  • Semaphore
同时控制访问的线程数,就比如设置了10,同时就只能有十个来进行访问,主要就是用作流量控制,就比如我们启动了40个线程读取文件数据,之后要存到数据库,但是数据库连接就只有10个,那么就可以使用它进行控制

线程 keepAliveSeconds 默认单位 task默认线程池大小_自定义_03

  • FutureTask和Future区别
public interface Future<V> {

    boolean cancel(boolean mayInterruptIfRunning);
    boolean isCancelled();
    boolean isDone();
    V get() throws InterruptedException, ExecutionException;
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}





public class FutureTask<V> implements RunnableFuture<V>

public interface RunnableFuture<V> extends Runnable, Future<V> {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}

上图列出来了Future和FutureTask的方法
这里可以看出Future是一个接口,是配合Callable进行时用的,可以拿到结果值,中断等但是它并不是一个对象
这时FutureTask作为他的实现类就出现了,他实现了RunnableFuture,RunnableFuture又继承了Runnable和Futrue
这里就可以明白它既可以作为Runnable使用,又可以获取返回值