【并发编程】线程池--Executor框架

简介

我们知道线程池就是线程的集合,线程池集中管理线程,以实现线程的重用,降低资源消耗,提高响应速度等。线程用于执行异步任务,单个的线程既是工作单元也是执行机制,Eexecutor作为灵活且强大的异步执行框架,其支持多种不同类型的任务执行策略,提供了一种标准的方法将任务的提交过程和执行过程解耦开发,基于生产者-消费者模式,其提交任务的线程相当于生产者,执行任务的线程相当于消费者,并用Runnable来表示任务,Executor的实现还提供了对生命周期的支持,以及统计信息收集,应用程序管理机制和性能监视等机制。
Executor的类图

 类及其接口介绍

1、Executor

可以看到最顶层是 Executor 的接口。这个接口很简单,只有一个 execute 方法。此接口的目的是为了把任务提交和任务执行解耦。

2、ExecutorService

这还是一个接口,继承自 Executor,它扩展了 Executor 接口,定义了更多线程池相关的操作。

3、AbstractExecutorService

提供了 ExecutorService 的部分默认实现。

4、ThreadPoolExecutor

实际上我们使用的线程池的实现是 ThreadPoolExecutor。它实现了线程池工作的完整机制。也是我们接下来分析的重点对象。

5、ForkJoinPool

实现 Fork/Join 模式的线程池。

6、ScheduledExecutorService

这个接口扩展了ExecutorService,定义个延迟执行和周期性执行任务的方法。

7、ScheduledThreadPoolExecutor

此接口则是在继承 ThreadPoolExecutor 的基础上实现 ScheduledExecutorService 接口,提供定时和周期执行任务的特性。

Executors

Executor 框架还提供 Executors 对象。注意看这个对象比 Executor 接口后面对了个 s,要区分开,不要搞混。Executors 是一个工厂及工具类。提供了例如 newFixedThreadPool(10) 的方法,来创建各种不同的 Executor。

创建线程池

public class Thread4 {
     
    public static void main(String[] args) throws Exception {
         
        Thread.currentThread().setName("主线程");
        System.out.println(Thread.currentThread().getName()+":"+"输出的结果");
        //通过线程池工厂创建线程数量为2的线程池,这种方式不太灵活
        ExecutorService service = Executors.newFixedThreadPool(2);
        //执行线程,execute()适用于实现Runnable接口创建的线程
        service.execute(new ThreadDemo4());
        service.execute(new ThreadDemo6());
        service.execute(new ThreadDemo7());
        //submit()适用于实现Callable接口创建的线程
        Future<String> task = service.submit(new ThreadDemo5());
        //获取call()方法的返回值
        String result = task.get();
        System.out.println(result);
        //关闭线程池
        service.shutdown();
    }
}
//实现Runnable接口
class ThreadDemo4 implements Runnable{
     
    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+":"+"输出的结果");
    }
     
}
//实现Callable接口
class ThreadDemo5 implements Callable<String> {
 
    @Override
    public String call() throws Exception {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+":"+"输出的结果");
        return Thread.currentThread().getName()+":"+"返回的结果";
    }
 
}
//实现Runnable接口
class ThreadDemo6 implements Runnable{
     
    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+":"+"输出的结果");
    }
     
}
//实现Runnable接口
class ThreadDemo7 implements Runnable{
     
    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+":"+"输出的结果");
    }
     
}

通过上面的例子我们能看到我们创建了了一个线程数量为2的线程池,我们下面的ThreadDemo类中,有实现callable接口的,这种提交给线程池需要用submit,而且有返回值;也有实现runnable接口的,需要用execute关键字,没有返回值。

我们创建线程池的方式是使用下面这种方式,这种方式并不灵活,

        //通过线程池工厂创建线程数量为2的线程池,这种方式不太灵活
        ExecutorService service = Executors.newFixedThreadPool(2);

我们看一下源码

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

ThreadPoolExecutor 构造方法如下 

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}

构造函数解释

corePoolSize 即线程池的核心线程数量,其实也是最小线程数量。不设置 allowCoreThreadTimeOut 的情况下,核心线程数量范围内的线程一直存活。

maximumPoolSize 即线程池的最大线程数量。受限于线程池的 CAPACITY。线程池的 CAPACITY 为 2 的 29 次方 -1。这是由于线程池把线程数量和状态保存在一个整形原子变量中。状态保存在高位,占据了两位,所以线程池中线程数量最多到 2 的 29 次方 -1。

workQueue是一个阻塞的 queue,用来保存线程池要执行的所有任务。

所以我们创造了一个核心线程数量为 n,最大线程数量也为 n 的线程池。线程池中线程永远存活。线程池创建线程使用 defaultTHreadFactory。当无法创建线程时,使用 defaultHandler。 

除了线程数量是我们自己定义,其他都是java已经定义的好的。我们为了更加灵活的使用线程池,我们经常使用ThreadPoolExecutor(上边有此类的构造函数)去创建线程池,不仅仅只是传入线程数量,还能自定义其他变量,我们可以创建自定义类型的线程池。

比如:

//可以创建自己想要类型的线程池
ExecutorService service = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, new SynchronousQueue<Runnable>(),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

 线程池不建议使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 说明:Executors各个方法的弊端:

1、 newFixedThreadPool 和 newSingleThreadExecutor:

主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。

2、newCachedThreadPool 和 newScheduledThreadPool:

主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值