关闭

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

标签: 线程池java并发多线程
149人阅读 评论(1) 收藏 举报
分类:

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模型。简而言之,就是将任务分成小任务执行,最终整合每个子任务的结果。限于篇幅,下回分解。

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场