java并发编程——线程池和Executor介绍

  • 第一部分:概述

早期的应用程序大可能是单线程串行执行的,虽然程序的任务边界清晰有序,可是执行的效率却很低,尤为是执行花费时间较长的操做,会致使大量的等待和堆积。为了提升程序的执行效率和吞吐量,咱们很天然的会想到多线程,即为每一个任务都新建一个独立的线程,这样就极大地提升了程序的执行效率。但事实上多线程也会带来不少问题。好比大量的建立线程,这自己就会消耗不少的资源,尤为是内存,当建立的线程数量超过服务器可以承受的极限时,内存溢出是在所不免的;好比还有其余稳定性问题,以及多线程形成的程序调用和管理的混乱。因此,综上所述,咱们须要一个介于二者之间的工具,既能够建立大量的线程来提升程序的并发性和吞吐量,同时又能够有序的管理这些线程,可以可控。而Executor接口和线程池技术就是在这种背景下应运而生的。下面分别将这两种经常使用的并发技术作以介绍和总结。java

  • 第二部分:Executor和ExecutorService接口介绍

java.util.coucurrent包下面为咱们提供了丰富的并发工具,Executor和ExecutorService接口就是其中比较重要的两个。缓存

  • Executor接口介绍

public interface Executor{
    void execute(Runnable command);
}

以上是Executor接口的代码,能够看出这个借口很是简单,就只有一个execute()方法。但他却为强大的异步任务执行提供了基础,它支持不一样类型的任务执行策略。Executor框架基于生产者消费者模型,它提供了一个标准的方法将任务的提交和任务的执行过程解耦,并用Runnable来表示任务。下面来看一个基于Executor的简单服务器实现:服务器

public class ExecutorWebServer {
    private static final int LIMIT = 50;
    private static final Executor exe = Executors.newFixedThreadPool(LIMIT);
    
	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
        ServerSocket server = new ServerSocket(80);
        while(true){
        	Socket socket = server.accept();
        	Runnable task = new Runnable(){
        		public void run(){
        			doSomeThing(socket);
        		}
        	};
        	exe.execute(task);
        }
	}
}

经过上面列子咱们就很好的把任务的提交和任务的执行分开来,这就是Executor框架最大的优点。多线程

  • ExecutorService接口介绍

上面咱们讲了如何建立一个Executor,可是并无讲如何关闭它。既然Executor是为应用程序服务的,于是他们应该是可关闭的,并将关闭操做中受影响的任务状态反馈给应用程序。为了解决执行服务的生命周期问题,ExecutorService扩展了Executor接口,增长了管理生命周期的方法:并发

public interface ExecutorService Extends Executor{
    void shutdown();
    boolean isShutDown();
    boolean isTerminated();
    ……
}

ExecutorService的生命周期分为三种,运行,关闭和已终止。下面看这个简单的实例:框架

class NetworkService implements Runnable {
    private final ServerSocket serverSocket;
    private final ExecutorService pool;

    public NetworkService(int port, int poolSize)
        throws IOException {
      serverSocket = new ServerSocket(port);
      pool = Executors.newFixedThreadPool(poolSize);
    }
 
    public void run() { // run the service
      try {
        for (;;) {
          pool.execute(new Handler(serverSocket.accept()));
        }
      } catch (IOException ex) {
        pool.shutdown();
      }
    }
  }

  class Handler implements Runnable {
    private final Socket socket;
    Handler(Socket socket) { this.socket = socket; }
    public void run() {
      // read and service request on socket
    }
 }
  • 第三部分:线程池介绍及使用

  • 几种类型的线程池

能够经过Executors类的几个静态方法来建立线程池。包含如下几类线程池:
newFixedThreadPool建立固定长度的线程池。每提交一个任务时就新建一个线程,直到达到线程池的最大数量。异步

newCachedThreadPool建立一个可缓存的线程池,线程的数量不受限制,可是在之前线程可用时将重用他们。socket

newSingleThreadExecutor建立一个使用单个 worker 线程的 ,以无界队列方式来运行该线程。函数

newScheduleThreadPool建立一个线程池,它可安排在给定延迟后运行命令或者按期地执行。相似于一个定时器。工具

  • 设置线程池大小

线程池的理想大小取决于被提交任务的类型以及所部署系统的特性。在代码中最好不要固定线程池的大小,而要经过某种灵活机制来配置。线程池大小的配置只要避免“过大”和”太小“两种极端状况便可。线程池过大会致使大量的线程竞争CPU和内存,最终致使资源耗尽;线程池太小时又会致使资源浪费,因此设置一个合适的线程池大小很是重要。一般有一个简单的设置线程池大小的公式供咱们参考使用:

N(Threads) = N(cpu) * U(cpu) * (1 + w/c)

N(cpu)表明cpu的个数,U(cpu)表明cpu利用率, w/c表示等待时间与计算时间的比值

  • 配置线程池

ThreadPoolExecutor为Executor和ExecutorService接口提供了基本实现。下面咱们来看一下ThreadPoolExecutor类的构造函数:该类一共有四个构造函数,其中最基础的一个构造函数以下:

ThreadPoolExecutor(int corePoolSize, 
                   int maximumPoolSize, 
                   long keepAliveTime, 
                   TimeUnit unit, 
                   BlockingQueue<Runnable> workQueue)

corePoolSize表示的是核心池的大小,第二个参数表示线程池最大的线程数,第三个参数表示存活时间,第四个参数表示给定单元粒度的时间段,第五个参数表示的是工做队列。

  • 扩展线程池

ThreadPoolExecutor是能够扩展的,它提供了几个能够在子类中改写的方法:beforeExecutor,afterExecutor,terminated,这些方法可用于扩展ThreadPoolExecutor的行为。

相关文章
相关标签/搜索