Java Executors 使用

本文转自:Java Executor并发框架(一)整体介绍

一、概述

Java是天生就支持并发的语言,支持并发意味着多线程,线程的频繁创建在高并发及大数据量是非常消耗资源的,因为java提供了线程池。在jdk1.5以前的版本中,线程池的使用是及其简陋的,但是在JDK1.5后,有了很大的改善。JDK1.5之后加入了java.util.concurrent包,java.util.concurrent包的加入给予开发人员开发并发程序以及解决并发问题很大的帮助。这篇文章主要介绍下并发包下的Executor接口,Executor接口虽然作为一个非常旧的接口(JDK1.5 2004年发布),但是很多程序员对于其中的一些原理还是不熟悉,因此写这篇文章来介绍下Executor接口,同时巩固下自己的知识。如果文章中有出现错误,欢迎大家指出。

二、Executors工厂类

对于数据库连接,我们经常听到数据库连接池这个概念。因为建立数据库连接时非常耗时的一个操作,其中涉及到网络IO的一些操作。因此就想出把连接通过一个连接池来管理。需要连接的话,就从连接池里取一个。当使用完了,就“关闭”连接,这不是正在意义上的关闭,只是把连接放回到我们的池里,供其他人在使用。所以对于线程,也有了线程池这个概念,其中的原理和数据库连接池是差不多的,因此java jdk中也提供了线程池的功能。

线程池的作用:线程池就是限制系统中使用线程的数量以及更好的使用线程

根据系统的运行情况,可以自动或手动设置线程数量,达到运行的最佳效果:配置少了,将影响系统的执行效率,配置多了,又会浪费系统的资源。用线程池配置数量,其他线程排队等候。当一个任务执行完毕后,就从队列中取一个新任务运行,如果没有新任务,那么这个线程将等待。如果来了一个新任务,但是没有空闲线程的话,那么把任务加入到等待队列中。

为什么要用线程池:

  • 减少线程创建和销毁的次数,使线程可以多次复用
  • 可以根据系统情况,调整线程的数量。防止创建过多的线程,消耗过多的内存(每个线程1M左右)

Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。Executors类,提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService接口。

比较重要的几个类/接口:

类/接口说明
ExecutorService真正的线程池接口
ScheduledExecutorService能和Timer/TimerTask类似,解决那些需要任务重复执行的问题接口
ThreadPoolExecutorExecutorService的默认实现
ScheduledThreadPoolExecutor继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现
ForkJoinPool继承自AbstractExecutorService类,主要用于实现工作窃取

要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在Executors类里面提供了一些静态工厂,生成一些常用的线程池。

1. newSingleThreadExecutor

public static ExecutorService newSingleThreadExecutor()

public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory)

创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

2. newFixedThreadPool

public static ExecutorService newFixedThreadPool(int nThreads)

public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory)

创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,在提交新任务,任务将会进入等待队列中等待。如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

3. newCachedThreadPool

public static ExecutorService newCachedThreadPool()

public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)

创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,

那么就会回收部分空闲(60秒处于等待任务到来)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池的最大值是Integer的最大值(2^31-1)。

4. newScheduledThreadPool

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory)

创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

5. newWorkStealingPool

public static ExecutorService newWorkStealingPool(int parallelism);

public static ExecutorService newWorkStealingPool();

根据给定的/所有可用的并行等级,创建一个拥有足够的线程数目的线程池(ForkJoinPool)。或许会使用多重队列来降低冲突。并行的等级是和运行的最大线程数目相关。真实的线程数目或许会动态地增长和收缩。一个工作窃取的线程池对于提交的任务不能保证是顺序执行的。

6. 其它一些创建后不可改变配置的线程池

public static ExecutorService unconfigurableExecutorService(ExecutorService executor);

public static ScheduledExecutorService unconfigurableScheduledExecutorService(ScheduledExecutorService executor);

内部使用 DelegatedExecutorService / DelegatedScheduledExecutorService 实现创建之后就无法修改配置的线程池。

实例

注:使用了java8的lambda表达式以及stream

1.newSingleThreadExecutor

public class SingleThreadExecutorTest {

    public static void main(String[] args) {
        ExecutorService executor = Executors.newSingleThreadExecutor();

        IntStream.range(0, 5).forEach(i -> executor.execute(() -> {
            String threadName = Thread.currentThread().getName();
            System.out.println("finished: " + threadName);
        }));

        try {
            // close pool
            executor.shutdown();
            executor.awaitTermination(5, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if(!executor.isTerminated()) {
                executor.shutdownNow();
            }
        }
    }

}

输出结果:

finished: pool-1-thread-1
finished: pool-1-thread-1
finished: pool-1-thread-1
finished: pool-1-thread-1
finished: pool-1-thread-1

线程名都一样,说明是同一个线程

2.newFixedThreadPool

public class FixedThreadExecutorTest {

    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(3);

        IntStream.range(0, 6).forEach(i -> executor.execute(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
                String threadName = Thread.currentThread().getName();
                System.out.println("finished: " + threadName);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }));

        try {
            // close pool
            executor.shutdown();
            executor.awaitTermination(5, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if(!executor.isTerminated()) {
                executor.shutdownNow();
            }
        }
    }

}

输出结果:

finished: pool-1-thread-3
finished: pool-1-thread-2
finished: pool-1-thread-1
finished: pool-1-thread-2
finished: pool-1-thread-3
finished: pool-1-thread-1

只创建了三个线程

3.newCachedThreadPool

public class CachedThreadExecutorTest {

    public static void main(String[] args) {
        ExecutorService executor = Executors.newCachedThreadPool();
        IntStream.range(0, 6).forEach(i -> executor.execute(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
                String threadName = Thread.currentThread().getName();
                System.out.println("finished: " + threadName);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }));

        try {
            // close pool
            executor.shutdown();
            executor.awaitTermination(5, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if(!executor.isTerminated()) {
                executor.shutdownNow();
            }
        }
    }

}

输出结果:

finished: pool-1-thread-4
finished: pool-1-thread-6
finished: pool-1-thread-5
finished: pool-1-thread-3
finished: pool-1-thread-2
finished: pool-1-thread-1

创建了6个线程

4.newScheduledThreadPool

public class ScheduledThreadExecutorTest {

    public static void main(String[] args) {
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);

        executor.scheduleAtFixedRate(() -> 
            System.out.println(System.currentTimeMillis())
        , 1000, 2000, TimeUnit.MILLISECONDS);

        try {
            // close pool
            Thread.sleep(10000);
            executor.shutdown();
            executor.awaitTermination(5, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if(!executor.isTerminated()) {
                executor.shutdownNow();
            }
        }
    }

}

输出结果:

1533218364402
1533218366403
1533218368401
1533218370402
1533218372403

5. newWorkStealingPool

让我们通过一个简单的需求来使用下Fork/Join框架,需求是:计算1+2+3+4的结果。

使用Fork/Join框架首先要考虑到的是如何分割任务,如果我们希望每个子任务最多执行两个数的相加,那么我们设置分割的阈值是2,由于是4个数字相加,所以Fork/Join框架会把这个任务fork成两个子任务,子任务一负责计算1+2,子任务二负责计算3+4,然后再join两个子任务的结果。

因为是有结果的任务,所以必须继承RecursiveTask,实现代码如下:

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;

public class CountTask extends RecursiveTask<Integer> {

    private static final long serialVersionUID = 5390823896306412900L;

    private static final int THRESHOLD = 2; //阈值
    private Integer start;
    private Integer end;

    public CountTask(Integer start, Integer end) {
        super();
        this.start = start;
        this.end = end;
    }

    @Override
    protected Integer compute() {
        Integer sum = Integer.valueOf(0);
        //如果任务足够小就计算任务
         boolean canCompute = (end-start) <= THRESHOLD;
         if(canCompute) {
             for(int i=start; i<=end; i++) {
                 sum += i;
             }
         } else {
            //如果任务大于阀值,就分裂成两个子任务计算
             int middle = (start + end) / 2;
             CountTask leftTask = new CountTask(start, middle);
             CountTask rightTask = new CountTask(middle+1, end);
             //执行子任务
             leftTask.fork();
             rightTask.fork();

            //等待子任务执行完,并得到其结果
             Integer leftResult = (Integer) leftTask.join();
             Integer rightResult = (Integer) rightTask.join();
            //合并子任务
             sum = leftResult + rightResult;
         }
        return sum;
    }

    public static void main(String[] args) {
        ForkJoinPool forkJoinPool = (ForkJoinPool) Executors.newWorkStealingPool();
        //生成一个计算任务,负责计算1+2+3+4+...+99+100
        CountTask task = new CountTask(1, 100);
        //执行一个任务
        Future<Integer> result = forkJoinPool.submit(task);
        try {
            System.out.println(result.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

}

输出结果:

5050

newWorkStealingPool 的例子引用自:Java并发编程指南15:Fork/join并发框架与工作窃取算法剖析

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值