Java线程池的分析和使用

最近想了解一下Java线程池的相关知识,看到Effective Java上和一篇博客,《深入理解Java之线程池》 — http://www.importnew.com/19011.html,看完受益良多,写一篇博客做一下笔记。

通常来说,我们需要使用线程的时候,就会创建一个Runnable或者xxxThread,然后执行。

而在并发编程中,如果有多个线程工作,并且每个线程工作时间很短,那么频繁的创建和销毁线程的开销是很大的。

因此我们引入了线程池的概念。池里有现成的线程,我们从池里获取可用线程,如果没有则创建新的线程或等待已有线程运行完其任务。运行结束的线程会回到线程池中,等待使用或者终止。

一、Java线程池的分析

在Java中,使用 ThreadPoolExecutor 或者 Executors 来创建线程池,提供四种模式。

(1)Executors.newCachedThreadPool:创建一个可缓存线程池,可以回收空闲的线程,如果任务数量太多,会新建线程。适合小程序和轻载服务器,不适合大负载的服务器,如果负载太重,CPU完全被占用了,当有更多任务时,会创建更多线程,情况会更糟。

(2)Executors.newFixedThreadPool:创建一个包含固定线程数目的线程池,超过线程数量的任务会进行等待。可以解决大负载服务器的并发问题。

(3)Exectors.newScheduledThreadPool:创建一个定长线程池,可以定时和周期性地执行任务,可以代替Timer。

(4)Executors.newSingleThreadExecutor:创建一个单线程的线程池,保证所有任务按指定顺序执行。

其实这几个线程池都来自同一个方法构造,来看看构造方法就一目了然:

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

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

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

//这个多了一层,最终也是调用了ThreadPoolExecutor
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
}

//此处的super就是ThreadPoolExecutor的构造方法
public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue());
}

来看看ThreadPoolExecutor的构造方法:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {...}

各个参数的含义:

corePoolSize:核心池的大小,当线程数量达到corePoolSize之后,新的任务会进入缓存队列。

maximumPoolSize:最大池大小,表示线程池中最大能创建多少线程。例如在缓存池中,新建任务可以超过corePoolSize,但是不能超过maximumPoolSize。

keepAliveTime:线程在没有任务执行时,最多经过多久会自动终止。一般来说,只有当线程数量超过corePoolSize时,这个参数会起作用。如果设置了allowCoreThreadTimeOut(boolean),方法,则也会起作用,直到池中线程数量为0。

unit:时间keepAlivetime的单位,TimeUnit类,是一个枚举类型。其中有

TimeUnit.DAYS;               //天
TimeUnit.HOURS;             //小时
TimeUnit.MINUTES;           //分钟
TimeUnit.SECONDS;           //秒
TimeUnit.MILLISECONDS;      //毫秒
TimeUnit.MICROSECONDS;      //微妙
TimeUnit.NANOSECONDS;       //纳秒

workQueue:阻塞队列,等待执行的任务就存储在这个队列之中。有多种选择:

ArrayBlockingQueue;     //数组实现
LinkedBlockingQueue;    //链表实现
PriorityBlockingQueue;  //优先队列实现
SynchronousQueue;       //这个好玩,自身是一个空队列,没有remove操作,就不能insert,也无法peek。
...

顺便看一下阻塞队列的实现,找其中一个方法看看可以有所了解:

public void putLast(E e) throws InterruptedException {
        if (e == null) throw new NullPointerException();
        Node<E> node = new Node<E>(e);
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            while (!linkLast(node))
                notFull.await();
        } finally {
            lock.unlock();
        }
    }

其实是使用了一个可重入锁,然后用notFull,相当于操作系统中的一个信号量,来等待加入队列成功。

threadFactory:线程工厂,用来创建新的线程。

handler:表示当拒绝处理任务时的策略。有以下四种类型:

ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 
ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。 
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

通过以上几个参数,可以看出如何配置和使用线程池。

二、使用

可以使用Executors的静态方法来创建,也可以通过ThreadPoolExecutor来创建,前者比较简洁,后者比较灵活。

1、使用ThreadPoolExecutor的示例:

public class Test {
    public static void main(String[] args) {   
        ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<Runnable>(5));

        for(int i=0;i<15;i++){
            MyTask myTask = new MyTask(i);
            executor.execute(myTask);
            System.out.println("线程池中线程数目:"+executor.getPoolSize()+",队列中等待执行的任务数目:"+
            executor.getQueue().size()+",已执行玩别的任务数目:"+executor.getCompletedTaskCount());
        }
        executor.shutdown();
    }
}

class MyTask implements Runnable {
   private int taskNum;

   public MyTask(int num) {
       this.taskNum = num;
   }

   @Override
   public void run() {
       System.out.println("正在执行task "+taskNum);
       try {
           Thread.currentThread().sleep(2000);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       System.out.println("task "+taskNum+"执行完毕");
   }
}

2、(MyTask类不变)ExecutorService没有getPoolSize方法,有的线程池可以强转。
(1)使用静态方法创建Single线程池,无法强转:

public static void main(String[] args) {   
        ExecutorService threadPool = Executors.newSingleThreadExecutor();
        for(int i = 0; i < 10; i++){
            MyTask myTask = new MyTask(i);
            threadPool.execute(myTask);
            System.out.println("execute task: " + i);
        }
        threadPool.shutdown();
    }

结果按顺序执行:

正在执行task 0
task 0执行完毕
正在执行task 1
task 1执行完毕
正在执行task 2
task 2执行完毕
正在执行task 3
task 3执行完毕
正在执行task 4
task 4执行完毕
正在执行task 5

(2)创建定长线程池:

public static void main(String[] args) {   
        ThreadPoolExecutor threadPool = (ThreadPoolExecutor)Executors.newFixedThreadPool(5);
        for(int i = 0; i < 10; i++){
            MyTask myTask = new MyTask(i);
            threadPool.execute(myTask);
            System.out.println("pool size: " + threadPool.getPoolSize()
            + " queue size: " + threadPool.getQueue().size());

        }
        threadPool.shutdown();
    }

结果:

正在执行task 0
pool size: 1 queue size: 0
pool size: 2 queue size: 0
正在执行task 1
pool size: 3 queue size: 0
正在执行task 2
pool size: 4 queue size: 0
正在执行task 3
pool size: 5 queue size: 0
pool size: 5 queue size: 1
pool size: 5 queue size: 2
pool size: 5 queue size: 3
pool size: 5 queue size: 4
pool size: 5 queue size: 5
正在执行task 4
task 0执行完毕
正在执行task 5
task 1执行完毕
task 2执行完毕
正在执行task 6
正在执行task 7
task 3执行完毕
正在执行task 8
task 4执行完毕
正在执行task 9
task 5执行完毕
task 7执行完毕
task 6执行完毕
task 8执行完毕
task 9执行完毕

(3)
创建缓存线程池:

public static void main(String[] args) {   
        ThreadPoolExecutor threadPool = (ThreadPoolExecutor)Executors.newCachedThreadPool();
        threadPool.setCorePoolSize(5);
        for(int i = 0; i < 10; i++){
            MyTask myTask = new MyTask(i);
            threadPool.execute(myTask);
            System.out.println("pool size: " + threadPool.getPoolSize() 
            + " queue size: " + threadPool.getQueue().size());
        }
        threadPool.shutdown();
    }

结果:

正在执行task 0
pool size: 1 queue size: 0
pool size: 2 queue size: 0
正在执行task 1
pool size: 3 queue size: 0
正在执行task 2
pool size: 4 queue size: 0
正在执行task 3
pool size: 5 queue size: 0
正在执行task 4
pool size: 6 queue size: 0
正在执行task 5
pool size: 7 queue size: 0
正在执行task 6
pool size: 8 queue size: 0
正在执行task 7
pool size: 9 queue size: 0
正在执行task 8
pool size: 10 queue size: 0
正在执行task 9
task 0执行完毕
task 1执行完毕
task 4执行完毕
task 3执行完毕
task 2执行完毕
task 7执行完毕
task 5执行完毕
task 9执行完毕
task 8执行完毕
task 6执行完毕

对比结果,可以看出
单线程的线程池是顺序执行。
定长线程池,超出定长的任务会在阻塞队列中等待。
缓存线程池,超出corePoolSize,仍然会创建新的线程来执行任务。

关于缓存线程池,有趣的是,
设置了缓存线程池最大为6之后,

public static void main(String[] args) throws InterruptedException {   
        ThreadPoolExecutor threadPool = (ThreadPoolExecutor)Executors.newCachedThreadPool();
        threadPool.setCorePoolSize(5);
        threadPool.setMaximumPoolSize(6);
        //丢弃任务,不抛出异常
        threadPool.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
        for(int i = 0; i < 10; i++){
            MyTask myTask = new MyTask(i);
            threadPool.execute(myTask);
            System.out.println("pool size: " + threadPool.getPoolSize() 
            + " queue size: " + threadPool.getQueue().size());
        }
        threadPool.shutdown();
    }

结果是这样的:

正在执行task 0
pool size: 1 queue size: 0
pool size: 2 queue size: 0
正在执行task 1
pool size: 3 queue size: 0
正在执行task 2
pool size: 4 queue size: 0
正在执行task 3
pool size: 5 queue size: 0
正在执行task 4
pool size: 6 queue size: 0
pool size: 6 queue size: 0
pool size: 6 queue size: 0
pool size: 6 queue size: 0
pool size: 6 queue size: 0
正在执行task 5
task 1执行完毕
task 2执行完毕
task 0执行完毕
task 3执行完毕
task 4执行完毕
task 5执行完毕

后面的6-9全部都被丢弃了。思考了一下,可能是主线程执行太快,缓存池中的线程全都没执行完,后面任务就直接丢弃了。
修改for循环:

for(int i = 0; i < 10; i++){
            MyTask myTask = new MyTask(i);
            Thread.sleep(1000);
            threadPool.execute(myTask);
            System.out.println("pool size: " + threadPool.getPoolSize() 
            + " queue size: " + threadPool.getQueue().size());
        }

主线程每次休眠1s,结果0-9全部执行了。
由此也可以看出,如果当前任务数量大于maximunPoolSize,那么则会拒绝执行,执行handler的策略。

第一次结合源码写博客,如有错误请指正~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值