多线程执行框架Executor详解

为了能够更好的进行多线程编程,JDK提供了一套Executor执行框架,简化开发人员的对多线程的编程工作。

其框架结构图如下:

多线程执行框架Executor详解_第1张图片


框架图比较庞大,但我们只需要关注如下几个实现:

  • Executor接口:只有一个 execute 方法,用来接收一个可执行对象。
  • ExecutorService接口:表示具有接收任务功能。
  • ThreadPoolExecutor:表示一个线程池,当然它比我们之前写过的线程池 ThreadPool复杂的多。http://blog.csdn.net/zq602316498/article/details/41819489。
  • Executors类:线程池工厂,通过它可以取得一个特定功能的线程池。
  • ScheduleExecutorService:可以指定时间或周期执行的线程池。

我们平时用的最多的便是 Executors工厂类,这个工厂类提供了能产生多个不同功能线程池的方法。

  • newFixedThreadPool() 方法:具有固定数量的线程池,线程数量始终不变。当有一个新任务提交时,线程中若有空闲进程变会执行它。若没有,则新的任务会被暂停在一个任务队列中。
  • newCachedThreadPool() 方法:线程数量不固定,当线程不足时便会产生新的线程。
  • newSingleThreadExecutor()方法:只有一个线程的线程池,按先进先出的顺序执行队列中的任务。
  • newSingleThreadScheduledExecutor() 方法:只有一个线程的线程池,但是可以指定执行时间或执行周期
  • newScheduleThreadPool()方法:同上一个方法,只不过可以指定线程数量

自定义线程池

newFixedThreadPool()、newSingleThreadExecutor()和newCachedThreadPool()方法其内部都使用了 ThreadPoolExecutor线程池。

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue());
    }
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue(),
                                      threadFactory);
    }
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue()));
    }

我们可以看到,以上线程池都只是 ThreadPoolExecutor 类的封装。这是一个比较复杂的类,我们通过构造函数来慢慢了解它。

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

  • corePoolSize:线程池线程数量
  • maxMumPoolSize:最大线程数量
  • keepAliveTime:超过corePoolSize数量的线程的存活时间
  • unit: keepAliveTime的单位
  • workQueue:任务队列,保存了被提交但是未被执行的任务
  • threadFactory:用于创建线程的线程工厂

workQueue用来盛放被提交但未执行的任务队列,它是一个BlockingQueue 接口的对象,仅用于存放 Runnable对象。可以使用以下一种阻塞队列
直接提交队列:由SynchronousQueue 对象提供。即每一次提交任务时,如果没有空闲的线程,就会提交失败或者执行拒绝策略。因此,此时要设置很大的maximumPoolSize值。
有界的任务队列:可以使用 ArrayBlockingQueue。这时,如果有新任务需要执行,且实际线程数小于corePoolSize 时,会优先创建线程。若大于corePoolSize,则会将任务加入等待队列。若队列已满,则会创建新线程且保证线程数不大于 maximumPoolSize。若大于 maximumPoolSize,则执行拒绝策略。
无界任务队列:使用 LinkedBlockingQueue 类实现。和有界任务队列类似,只不过系统线程数到达corePoolSize后就不在增加。后续任务都会放入阻塞队列中,直到耗尽系统资源。
优先任务队列: 通过 PriorityBlockingQueue 实现。可以控制任务的执行先后顺序,是一个特殊的无界队列。

ThreadPoolExecutor 最后一个参数 handler 指定了拒绝策略,有如下几种:

  • AbortPolicy策略:直接抛出异常,阻止系统正常工作。
  • CallerRunsPolicy策略:只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。
  • DiscardOledestPolicy策略:丢弃最老的一个请求(即将被执行的任务),并尝试再次提交当前任务。
  • DiscardPolicy策略:丢弃无法处理的任务,不作任何处理。

以上拒绝策略都实现了 RejectedExecutionHandler 接口,我们也可以扩展这个接口来实现自己的拒绝策略。

下面,我们使用优先队列自定义线程池来实现具有优先级调度功能的线程池。使用优先队列时,任务线程必须实现 Comparable 接口。

package executor;

import java.util.concurrent.Executor;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class MyThread implements Runnable,Comparable{
	private String name;
	public MyThread(String name){
		this.name=name;
	}
	public MyThread(){
		name="";
	}
	
	@Override
	public void run(){
		try{
			Thread.sleep(100);
			System.out.println(name+" running....");
		}catch(InterruptedException e){
			e.printStackTrace();
		}
	}
	@Override
	public int compareTo(MyThread o){
		int me = Integer.valueOf(name.split("_")[1]);
		int other = Integer.valueOf(o.name.split("_")[1]);
		return me-other;
	}
	
	public static void main(String[] args){
		Executor exe = new ThreadPoolExecutor(100, 100, 0L, TimeUnit.SECONDS, new PriorityBlockingQueue());
		for(int i =0;i<1000;i++){
			exe.execute(new MyThread("testThread_"+(999-i)));
		}
		
	}
}

输出如下:

testThread_998 running....
testThread_999 running....
testThread_993 running....
testThread_995 running....
testThread_997 running....
。。。
testThread_912 running....
testThread_900 running....
testThread_910 running....
testThread_1 running....
testThread_2 running....
testThread_3 running....
testThread_4 running....
testThread_0 running....

可以看到,900-999号线程是任务提交时,未经过优先队列而直接被执行的任务(程序刚运行时有大量的空闲进程100个,无需使用等待队列),之后是经过优先队列中转之后被执行的任务。可以看到,优先级较高的任务(数字越小,优先级越高)优先被执行。

扩展ThreadPoolExecutor

ThreadPoolExecutor 也是一个可以扩展的线程池,它提供了 beforeExecute()、afterExecute()和terminated()3个接口对其进行控制。

在 Worker.runWorker() 的方法内部提供了这样一个调用过程:

try {
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        task.run();
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }

ThreadPoolExecutor.Worker 是默认的工作线程,在其执行过程中,提供了空的 beforeExecute() 和 afterExecute() 的实现。实际应用中,可以对其进行拓展,实现对线程池运行状态的跟踪,输入一些有用的调试信息,以帮助系统故障诊断。

class MyThreadPoolExecutor extends ThreadPoolExecutor {
	public MyThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
			long keepAliveTime, TimeUnit unit, BlockingQueue workQueue) {
		super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
	}

	protected void beforeExecute(Thread t, Runnable r) {
		System.out.println("beforeExecute MyThread name:" + t.getName()
				+ " ID:" + t.getId());
	}

	protected void afterExecute(Runnable r, Throwable t) {
		System.out.println("afterExecute MyThread name:" + Thread.currentThread().getName() + " ID:"
				+ Thread.currentThread().getId());
		System.out.println("afterExecute PoolSize:" + this.getPoolSize());
	}
}

以上代码实现了一个带有日志输出功能的线程池,该线程池会在任务执行前输入即将要执行的任务名称和线程ID,同时会在任务完成后,输出线程的 ID 和当前线程池的线程数量。

你可能感兴趣的:(Java,并发编程)