多线程/并发笔记:线程池的创建--Executor框架

原创 2017年09月14日 11:56:18

Java线程相关的类和接口大多在java.util.concurrent包下面,线程池的创建可以通过Executors定义的一些类方法获取各种线程池实例。

  1. newSingleThreadExecutor:创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行
  2. newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行。
  3. newSingleThreadScheduledExecutor:根据时间计划延迟创建单个工作线程 ExecutorService(或者周期性的创建)
  4. newFixedThreadPool :创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
  5. newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
  6. newWorkStealingPool:jdk1.8新增,创建持有足够线程的线程池来支持给定的并行级别,并通过使用多个队列,减少竞争,它需要穿一个并行级别的参数,如果不传,则被设定为默认的CPU数量。

newSingleThreadExecutor

首先,newSingleThreadExecutor有两个 。返回类型为ExecutorService接口,该接口继承自Executor接口,Executor接口提供了一个execute()方法,用于创建线程。ExecutorService接口则提供了一系列方法来管理线程池。

这里写图片描述
先看一个最简单的实现:

private static void singleThreadExecutor(){
        //创建一个单线程的线程池
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            //执行具体任务
            singleThreadExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println(Thread.currentThread().getName()+"线程执行中....,index:"+index);
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }

上面的代码通过Executors.newSingleThreadExecutor()创建了一个线程池。然后通过execute()方法接受一个Runnable的实例(若需要线程可以有返回值,可以使用submit()方法,该方法会返回Future对象,这是ExecutorService接口提供的扩展方法。),创建线程。
执行结果,通过jdk提供的jconsole工具可以查看jvm线程的情况:
这里写图片描述
这里写图片描述
至于线程的名称为什么为“pool-1-thread-1”,可以通过查看源码慢慢找到。
下面,通过源码,了解下线程池的具体实现。
在查看newSingleThreadExecutor的源码实现时,可以了解到,newSingleThreadExecutor是通过实现ThreadPoolExecutor对象创建线程池的。
这里写图片描述
在ThreadPoolExecutor构造方法中,可以通过Executors.defaultThreadFactory()创建一个ThreadFactory接口的对象。
这里写图片描述
defaultThreadFactory:
这里写图片描述
DefaultThreadFactory:
这里写图片描述
通过这里的实现也可以知道,为什么创建的线程名称为”pool-1-thread-1”了,因为在newThread()方法中,创建线程是设置了线程的name。

这里,再说newSingleThreadExecutor另一种重载方式,就是接收一个ThreadFactory对象。ThreadFactory通过名称就可以知道,是一个线程工厂接口。它的接口定义只有一个方法–创建一个线程:

Thread newThread(Runnable r);

ThreadFactory通常用于创建自定义的线程。具体就不在举例,可以参见jdk中的默认实现defaultThreadFactory。

newScheduledThreadPool

根据时间计划,延迟给定时间后创建 ExecutorService(或者周期性地创建 ExecutorService)。
newScheduledThreadPool方法也有两种重载方式,一个是接受一个int参数,用于指定创建线程池中保留线程的数量。另一个是指定线程池保留线程的数量的同时,可以接受一个自定义的ThreadFactory对象。同时,newScheduledThreadPool 返回的是ScheduledExecutorService接口,ScheduledExecutorService接口继承了ExecutorService接口。
这里写图片描述
ScheduledExecutorService接口额外提供了一下方法:
这里写图片描述
通过这些方法,可以实现线程池里线程的延迟启动,周期执行等。

private static void newScheduledThreadPool(){
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        //创建最大容量为2的线程池
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(2);
        //延迟1秒后,每2秒执行一次
        scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+",delay 1 seconds,"+df.format(System.currentTimeMillis()));
            }
        }, 1,2, TimeUnit.SECONDS);
    }

这里写图片描述
newScheduledThreadPool的这个特性,使得他可以替代java的定时器Timer实现定时任务。

newSingleThreadScheduledExecutor

通过它的定义可知,newSingleThreadScheduledExecutor返回一个ScheduledExecutorService 对象,并且默认实现了最大容量为1的ScheduledThreadPoolExecutor对象。

public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
        return new DelegatedScheduledExecutorService
            (new ScheduledThreadPoolExecutor(1));
    }

因此它和Executors.newScheduledThreadPool(1)是等价的。

newFixedThreadPool

创建一个可重复使用的、固定线程数量的 ExecutorService。
newFixedThreadPool 方法也有两种重载方式,一个是接受一个int参数,用于指定创建线程池的大小。另一个是指定线程池大小的同时,可以接受一个自定义的ThreadFactory对象。
这里写图片描述

private static void fixedThreadPool(int size){
        //创建一个大小为size的线程的线程池
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(size);
        for (int i = 0; i < 10; i++) {
            final int index = i;
            //创建线程,执行具体任务
            fixedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+",正在执行....,index:"+index);
                }
            });
        }
    }

上面的代码在执行时创建了大小为6的固定线程池。
这里写图片描述
通过对执行结果的监控发现,线程峰值总数为19,上面在使用newSingleThreadExecutor创建单线程池时进程数峰值为14,正好相差5个。
对于执行的线程任务,同样是执行10次,通过执行的结果可以发现有些线程被重复使用了。

newCachedThreadPool

通过名称就可以知道,这是创建一个可缓存的线程池。即可以重复利用已存在的线程来执行任务。返回的也是一个ExecutorService对象。所以,这种方式创建的线程池会先查看池中有没有以前建立的线程,如果有,就 重用。如果没有,就建一个新的线程加入池中。
这里写图片描述

private static void cachedThreadPool(){
        //线程池创建
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

        for (int i = 0; i < 10; i++) {
            final int index = i;
            //线程调用
            cachedThreadPool.execute(new Runnable() {
                public void run() {
                    try {
                        Thread.sleep( index*1000);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+",index:"+index);
                }
            });
            try {
                Thread.sleep( 2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

这里写图片描述
可以看出,有些线程像pool-1-thread-1和pool-1-thread-2被调用了多次,execute会在执行时首先在线程池中随机选择一个已有空闲线程来执行任务,如果线程池中没有空闲线程,便会创建一个新的线程来执行任务。

newWorkStealingPool

newWorkStealingPool是JDK1.8后新增的方法,可以创建一个带并行级别的线程池,并行级别决定了同一时刻最多有多少个线程在执行,所以它的两种重载模式中参数int就是并行级别。不传的话将默认为当前系统的CPU个数。

这里写图片描述

为了体现这种特性,这里使用带返回值得线程模拟。返回的结果为线程名称和执行时间。
关于线程的创建可以参见:多线程/并发笔记:线程创建的三种方式

private static void workStealingPool() throws InterruptedException {
        ExecutorService workStealingPool = Executors.newWorkStealingPool();

        List<Callable<String>> callables = new ArrayList<>();
        for (int i = 1; i <= 10; i++) {
            final int count = i;
            Callable call = new Callable() {
                @Override
                public Object call() throws Exception {
                    Date now = new Date();
                    Thread.sleep(1000);//此任务耗时1s
                    return "线程" + Thread.currentThread().getName() + "完成任务:"
                            + count + "   时间为:" + now.toLocaleString();
                }
            };
            callables.add(call);
        }

        //JDK1.8写法,打印执行结果
        //invokeAll可以批量提交一组线程
        workStealingPool.invokeAll(callables)
                .stream()
                .map(future -> {
                    try {
                        return future.get();
                    }
                    catch (Exception e) {
                        throw new IllegalStateException(e);
                    }
                })
                .forEach(System.out::println);
    }

执行结果:
这里写图片描述
可以看到,对于批量提交的10个任务,同一时间执行有四个执行。这是因为我的CPU是4核的。
这里写图片描述
将上面的代码改为:

ExecutorService workStealingPool = Executors.newWorkStealingPool(2);

执行结果就是:
这里写图片描述
对于newWorkStealingPool的实现,其实已经不载是Executor框架下的了,查看它的实现方式就会发现:
这里写图片描述
使用的ForkJoinPool的方式创建线程池,这是java多线程的另一个模型了Fork/Join模型。简而言之,就是将任务分成小任务执行,最终整合每个子任务的结果。限于篇幅,下回分解。

版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

Java 多线程 Executor 线程池 从线程返回结果 Java编程思想读书笔记

Java SE5 的java.util.concurrent包中的执行器Executor可以为你管理Thread对象,从而简化并发编程。Executor在客户端和任务之间提供了一个间接层,与客户端直接...

Java多线程之concurrent包(三)——Executor框架与线程池

Executor框架简介在Java 5之后,并发编程引入了一堆新的启动、调度和管理线程的API。Executor框架便是Java 5中引入的,其内部使用了线程池机制,它在java.util.cocur...

多线程五---------线程池 Executor 管理线程

1. Executor 管理线程   CachedThreadPool     /*ExecutorService exe = Executors.newCachedThreadPool();   ...

多线程(六)executor和线程池

继续使用上文中的例子说明如何使用executor和线程池:        FTPFileTransfer fileTransfer = new FTPFileTransfer( fileArray);...
  • OnlyQi
  • OnlyQi
  • 2011-07-07 21:03
  • 1041

多线程框架线程池编程

  • 2012-08-09 17:11
  • 521KB
  • 下载

java&android线程池-Executor框架之ThreadPoolExcutor&ScheduledThreadPoolExecutor浅析(多线程编程之三)

无论是在java还是在android中其实使用到的线程池都基本是一样的,因此本篇我们将来认识一下线程池Executor框架(相关知识点结合了并发编程艺术书以及Android开发艺术探索而总结),下面是...

springmvc配线程池Executor做多线程并发操作

加载xml文件 在ApplicationContext.xml文件里面添加 xmlns:task="http://www.springframework.org/schema/task"xml...

多线程之线程池Executor应用

JDK1.5之后,提供了自带的线程池,以便我们更好的处理线程并发问题。 Executor类给我提供了多个线程池创建的方式:    创建固定的线程池 Executors.newFixedThread...

java.util.concurrent 多线程框架---线程池编程(三)

1 引言 在软件项目开发中,许多后台服务程序的处理动作流程都具有一个相同点,就是:接受客户端发来的请求,对请求进行一些相关的处理,最后将处理结果返回给客户 端。这些请求的来源和方式可能会各不相同,但...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)