我们的2019Java 杂谈

Java:看一波线程池,反正也不亏

2019-06-06  本文已影响4人  GitCode8

前言

线程池在Java并发编程中,有着举足轻重的位置,学习和掌握它是学习Java的重中之重。反正有空看看,学点知识,又不亏。

在开发中,合理使用线程池能带来什么好处呢?

队列

线程池内部持有一个用于存储工作任务的队列,在核心线程满了时候,会将任务存储到队列中。常用队列类型:

线程池的使用

  1. 创建
ThreadPoolExecutor( int corePoolSize,
                    int maximumPoolSize,
                    long keepAliveTime,
                    TimeUnit unit, 
                    BlockingQueue<Runnable> workQueue, 
                    ThreadFactory threadFactory,
                    RejectedExecutionHandler handler)
  

在线程的构造方法中,共有5个参数:

  1. 提交任务到线程池
//execute()方法执行任务
executor.execute(new Runnable() {
    @Override
    public void run() {
        // TODO Auto-generated method stub
    }
});
//submit()方法提交任务
executor.execute(new Runnable() {
    @Override
    public void run() {
    // TODO Auto-generated method stub
    }
});

提交到线程池中有种方式:execute()方法和submit()方法。

execute()方法提交的任务无法获得返回值,无法判断提交状态。
submit()方法可以获得返回Future类型的对象,根据Future对象可以判断任务是否执行成功和通过get()方法获得返回值,但get()方法会阻塞当前线程一段时间。

  1. 线程池的关闭

由于两者都是遍历线程池中的工作线程,然后中断线程,所以无法响应中断的线程可能永远无法终止。

线程池的实现原理

当我们提交一个新任务到线程池时,任务在线程池的流程是怎样的呢?

  1. 核心线程是否已满。核心线程大于等于corePoolSize,表示已满。不是的话,则会创建新的核心线程执行新任务(只要核心线程未达到数量corePoolSize,都会新建)。不然执行下一步骤。
  2. 工作队列是否已经满。在有界队列中,存储任务的数量有限。如果任务未满了,新提交任务存储到工作队列。否则进入下个流程。
  3. 非核心线程是否已满。虽然核心线程队列已满,但非核心线程不一定满了。非核心线程大于maximumPoolSize,表示已满,线程池拒绝新任务,交给饱和策略处理。否则新建非核心线程处理新任务。

任务队列的任务什么时候被处理?

线程池中线程处理任务有两种方式:一种就是新建线程处理任务,另一种就是循环从阻塞队列获取任务来执行。

四种线程池

有时我们仅仅是使用一下线程池,不会自己定制线程池,毕竟线程池的构造方法参数那么多,我的妈耶。那看看类Executors提供的四个线程池。

FixedThreadPool

    //创建FixedThreadPool线程池
    Executors.newFixedThreadPool(nThreads); 
    
    //newFixedThreadPool方法的实现
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

创建一个FixedThreadPool线程池,线程的核心线程coreThreadSize和线程池线程容量maximumPoolSize都为nThreads。由于使用的无界的LinkedBlockingQueue队列,将导致maximumPoolSize参数失效,队列对任务将来者不拒。这里将保活时间设为0,意味着空线程会被立即终止。

CachedThreadPool

    //创建CachedThreadPool线程池
    Executors.newCachedThreadPool();
    
    //newCachedThreadPool方法的实现
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

CachedThreadPool的核心线程为0,线程池容量为Integer.MAX_VALUE,意味着maximumPoolSize是无界的。保活时间keepAliveTime设为60秒,空闲线程闲置60秒后被终止。由于使用了没有容量的SynchronousQueue队列,意味当提交一个新任务到线程池中,没有空闲线程来对接,就会新建新的线程来处理新任务。

SingleThreadExecutor

    //创建SingleThreadExecutor线程池
    Executors.newSingleThreadExecutor();
    
    //SingleThreadExecutor方法的实现
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

可以看到SingleThreadExecutor可以看做定制化的FixedThreadPool,将nThreads置为1,将核心线程和线程池容量设为1,以保证只有一个线程在执行。

ScheduledThreadPool

     //创建ScheduledThreadPool线程池
    Executors.newScheduledThreadPool(corePoolSize);
    
    //newScheduledThreadPool方法的实现
    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
    
    //ScheduledThreadPoolExecutor方法的实现
    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }

newScheduledThreadPool方法最终会调用ThreadPoolExecutor的构造来创建线程池。将定制化DelayQueue后的DelayedWorkQueue作为工作队列。DelayedWorkQueue队列会把执行时间小的任务排在前面优先执行。如果执行时间相同,就会优先执行提交时间早的任务。

FutureTask

在通过submit()方法提交任务到线程池,会返回有结果的Future类型的对象。Future是一个接口,FutureTask继承它,所以FutureTask也可以作为submit()方法得返回值。同时,FutureTask继承Runnable接口,这样又可以作为任务提交到线程池中,由调用线程直接执行。

当调用FutureTask的get()方法,如果FutureTask处于已完成状态(执行完毕),则会导致调用线程立即放回或者抛出异常。否则会使调用线程阻塞。

当调用FutureTask的cancel()方法,如果FutureTask处于未启动状态(为执行run方法),则该任务不会被执行;如果处于启动状态,会尝试以中断来尝试停止任务。如果已经完成状态,则返回false。

总结

通过对线程池知识点理解,可以清晰掌握创建线程池骚姿势和内涵。以为实战提供必要的理论知识。

上一篇下一篇

猜你喜欢

热点阅读