线程管理框架Executor,ExecutorService

之前的我们的线程启动是使用new Thread().start()这种方式来启动的,而且当要执行多个任务的时候就会创建多个线程来执行这些任务,下面我们来看看使用Executor框架来管理线程。

为什么要使用Executor线程池框架

1、每次执行任务创建线程 new Thread()比较消耗性能,创建一个线程是比较耗时、耗资源的。

2、调用 new Thread()创建的线程缺乏管理,被称为野线程,而且可以无限制的创建,线程之间的相互竞争会导致过多占用系统资源而导致系统瘫痪,还有线程之间的频繁交替也会消耗很多系统资源。

3、直接使用new Thread() 启动的线程不利于扩展,比如定时执行、定期执行、定时定期执行、线程中断等都不便实现。

使用Executor线程池框架的优点

1、能复用已存在并空闲的线程从而减少线程对象的创建从而减少了消亡线程的开销。

2、可有效控制最大并发线程数,提高系统资源使用率,同时避免过多资源竞争。

3、框架中已经有定时、定期、单线程、并发数控制等功能。

综上所述使用线程池框架Executor能更好的管理线程、提供系统资源使用率。

ok通过对比我们知道使用线程池框架Executor能给我们带来很多便利,下面来看看Executor框架的介绍以及使用。

1、在java5之后,并发编程引入了一种新的启动方式那就是使用线程管理器Executor

2、Executor框架是在java5中引入的,其内部使用了线程池机制,它在java.util.concurrent包下面,通过该框架来控制线程的启动、执行和关闭,可以简化编发编程的操作。因此在java5之后,通过Executor来启动线程比使用Thread.start()方法更好,除了易管理,效率更高(线程池实现,节约开销)外,还有关键的一点有助于逃避this逃逸问题--如果我们在构造函数中启动了一个线程,因为另一个任务可能会在构造器结束之前开始执行,此时可能会访问到初始化了一半的对象。

3、Executor框架包括:线程池,Executor,Executors,ExecutorService,completionService,Future,Callable等。

Executors介绍

1、Executors工厂类通过Executors类提供四种线程池方法newFixedThreadPool、newCachedThreadPool、newSingleThreadPool、newScheduleThreadPool通过这些不同的方法来创建不同的线程管理器对象即Executor对象。

下面来看看Executors类的四个方法以及demo

1、newFixedThreadPool(int count) 方法来创建固定数目的线程池,其中的空闲线程可重用。

static ExecutorService	newFixedThreadPool(int nThreads) 

示例:

    	//创建线程管理器对象
    	 ExecutorService executorService = Executors.newFixedThreadPool(5);
         for (int i = 0; i < 20; i++) {
             Runnable syncRunnable = new Runnable() {
                 @Override
                 public void run() {
                     System.out.println(Thread.currentThread().getName());
                 }
             };
             //任务提交到线程管理器中
             executorService.execute(syncRunnable);
         }
         executorService.shutdown();
    
执行结果:
pool-1-thread-2
pool-1-thread-1
pool-1-thread-2
pool-1-thread-2
pool-1-thread-3
pool-1-thread-2
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-2
pool-1-thread-2
pool-1-thread-2
pool-1-thread-2
pool-1-thread-2
pool-1-thread-2
pool-1-thread-3
pool-1-thread-5
pool-1-thread-2
pool-1-thread-1
pool-1-thread-4
从运行结果来看,总共创建了5个线程,开始执行五个线程,当五个线程都处于活动状态,再次提交的任务都会加入队列等到其他线程运行结束,当线程处于空闲状态时会被一下一个任务复用。
2、newCachedThreadPool()方法,创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用他们。

static ExecutorService	newCachedThreadPool() 
示例:

    	 ExecutorService executorService = Executors.newCachedThreadPool();
         for (int i = 0; i < 50; i++) {
             Runnable syncRunnable = new Runnable() {
                 @Override
                 public void run() {
                     System.out.println(Thread.currentThread().getName());
                 }
             };
             executorService.execute(syncRunnable);
         }
         executorService.shutdown();
    
结果:

pool-1-thread-1
pool-1-thread-2
pool-1-thread-5
pool-1-thread-4
pool-1-thread-3
pool-1-thread-7
pool-1-thread-8
pool-1-thread-6
pool-1-thread-9
pool-1-thread-10
pool-1-thread-12
pool-1-thread-16
pool-1-thread-1
pool-1-thread-13
pool-1-thread-11
pool-1-thread-14
pool-1-thread-17
pool-1-thread-15
pool-1-thread-18
pool-1-thread-19
pool-1-thread-20
pool-1-thread-20
pool-1-thread-6
pool-1-thread-16
pool-1-thread-14
pool-1-thread-15
pool-1-thread-13
pool-1-thread-3
pool-1-thread-9
pool-1-thread-9
pool-1-thread-17
pool-1-thread-10
pool-1-thread-13
pool-1-thread-14
pool-1-thread-6
pool-1-thread-18
pool-1-thread-1
pool-1-thread-2
pool-1-thread-3
pool-1-thread-15
pool-1-thread-16
pool-1-thread-20
pool-1-thread-8
pool-1-thread-12
pool-1-thread-21
pool-1-thread-7
pool-1-thread-5
pool-1-thread-4
pool-1-thread-11
pool-1-thread-19
可以看出缓存线程池大小是不定值,可以需要创建不同数量的线程,在使用缓存型池时,先查看池中有没有以前创建的线程,如果有就复用.如果没有就新建新的线程加入池中,缓存型池子通常用于执行一些生存期很短的异步型任务

3、newSingleThreadPool()方法,创建一个使用单个worker线程的Executor,以无界队列方式来运行该线程。

static ExecutorService	newSingleThreadExecutor() 
示例:

    ExecutorService executorService = Executors.newSingleThreadExecutor();
    for (int i = 0; i < 5; i++) {
        Runnable syncRunnable = new Runnable() {
            @Override
            public void run() {
              System.out.println(Thread.currentThread().getName());
            }
        };
        executorService.execute(syncRunnable);
    }
        executorService.shutdown();
    
结果:

pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
从运行结果可以看出,只会创建一个线程,当上一个执行完之后才会执行下一个,按照指定顺序(FIFO,LIFO优先级)执行。

4、newScheduleThreadPool(int corePoolSize)方法,创建一个定长的线程,支持定时及周期性任务的执行。

   schedule(Runnable runnable,int dealy,TimeUnit unit)方法来执行指定延迟时间的任务。

示例:

	ScheduledExecutorService  executorService = Executors.newScheduledThreadPool(5);
	System.out.println(System.currentTimeMillis());
	for(int i =0 ;i <10; i++) {
		Runnable runnable = new Runnable(){
			@Override
			public void run(){
				System.out.println(Thread.currentThread().getName() +" "+System.currentTimeMillis());
			}
		};
		//添加任务
		executorService.schedule(runnable, 5000, TimeUnit.MILLISECONDS);
	}
运行结果:

time: 1486610328219
name: pool-1-thread-1 time: 1486610333242
name: pool-1-thread-4 time: 1486610333242
name: pool-1-thread-1 time: 1486610333242
name: pool-1-thread-3 time: 1486610333242
name: pool-1-thread-2 time: 1486610333242
name: pool-1-thread-3 time: 1486610333242
name: pool-1-thread-1 time: 1486610333242
name: pool-1-thread-4 time: 1486610333242
name: pool-1-thread-5 time: 1486610333242
name: pool-1-thread-2 time: 1486610333242
这个和newFixedThreadPool有点相似,比如会创建固定数目的线程来执行所有任务,而且这些线程如果有空闲则会复用,而不同的是newScheduleThreadPool是延迟指定时间后在执行相关任务。

5、scheduleAtFixedRate(Runnable runnable,long initialDelay,long period,TimeUnit unit) 该方法执行一个在给定初始延迟后首次启用的定期操作,后续操作具有给定的周期,也就是将在initialDelay后开始执行,然后在initDelay+period后在执行一次,接着会在initDelay + 2*period 后执行,一次类推。

示例:

	ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
	for(int i = 0; i<10; i++) {
		Runnable runnable = new Runnable(){
			@Override
			public void run(){
				System.out.println("name: "+Thread.currentThread().getName() +"time: "+System.currentTimeMillis());
			}
		};
		executorService.scheduleAtFixedRate(runnable, 5000, 2000, TimeUnit.MILLISECONDS);
	}
	
这个方法是延迟5秒钟后开始执行任务,之后没2秒钟执行一次,而这2秒中是从线程启动开始计算的,如果某个任务的执行时间比较长,则有可能这个任务下次在执行的时候,之前的这个任务还没结束,两个任务的执行可能会出现重叠的现象。

6、scheduleWithFixedRelay(Runnable runnable,long initialDelay,long period,TimeUnit unit)该方法创建并执行一个在给定初始延迟后首次启动的定期操作,随后在每一次执行终止和下一次执行开始之间都会等待给定的延迟时间即period ,这个和上面的方法就不同了,这个方法如果他执行某一个任务的时间很长的话,他的下一次执行也是在这个任务结束后在等待给定周期后才会执行。

示例:

	ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
	for(int i = 0; i<10; i++) {
		Runnable runnable = new Runnable(){
			@Override
			public void run(){
				System.out.println("name: "+Thread.currentThread().getName() +"time: "+System.currentTimeMillis());
			}
		};
		executorService.scheduleWithFixedDelay(runnable, 5000, 1000, TimeUnit.MILLISECONDS);
	}
	
这个方法计算方式是从这个线程执行完某个任务后再等待指定时间后才开始执行下一次任务,时间是从任务结束后计算的所以耗时再长的任务执行的时候两次的任务也不会出现重叠。

7、newSingleThreadPool()方法,Executors创建一个单线程的线程池,他只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO,LIFO优先级)执行,即所有的任务都会使用一个工作线程来执行。

示例:

	ExecutorService executorService = Executors.newSingleThreadExecutor();
	for(int i = 0; i<10; i++) {
		Runnable runnable = new Runnable(){
			@Override
			public void run(){
				System.out.println("name: "+Thread.currentThread().getName() +"time: "+System.currentTimeMillis());
			}
		};
		executorService.execute(runnable);
	}
	
运行结果:

name: pool-1-thread-1
name: pool-1-thread-1
name: pool-1-thread-1
name: pool-1-thread-1
name: pool-1-thread-1
name: pool-1-thread-1
name: pool-1-thread-1
name: pool-1-thread-1
name: pool-1-thread-1
name: pool-1-thread-1
从运行结果我们可以看出,这个线程池在执行任务的时候所有的任务都是被一个工作线程来执行,当上一个任务执行结束后下一个任务才会执行。


ok上面我们学习了Executors类的方法来创建各中ExecutorService实例来管理线程的执行,下面我们来看看ExecutorService。

ExecutorServcie接口继承自Executor接口,它提供了更丰富的实现线程方法,比如ExecutorService提供了关闭自己的方法,以及可以跟踪一个或多个异步执行状况而生成Future的方法。可以调用ExecutorService的shutDown()方法来平滑关闭ExecutorService,调用该方法后,ExecutorService不会再接受新的任务而已添加或已执行的任务会继续执行直到完成,所以我们一般使用ExecutorService接口来实现和管理线程。

ExecutorService的生命周期包括三种状态:运行、关闭、终止。创建后便进入了运行状态,当调用了shutdown()方法时就进入了关闭状态,这时的ExecutorService不会再接受新的任务但他会执行已经提交了的任务和正在执行的任务,当提交了的这些所有任务都结束后ExecutorService方法就进入了终止状态。如果不调用shutdown()方法则ExecutorService会一直处于运行状态,这时能接受新的任务当然会一直这个任务了。服务器端一般不会关闭它,保持一直运行即可。

ExecutorService 执行有返回值的任务Callable

在java5之后,任务分两类,一类是没有返回值的Runnable接口另一个是有返回值的Callable接口,两者都可以被ExecutorService执行,但是Runnable是没有返回值的而Callable任务是有返回值的并且Callable的call()方法只能通过ExecutorService的submit(Callable callable)方法来执行并且会返回一个Future是表示任务等待完成的Future。

示例:

1、Callable任务

package testDate;
import java.util.concurrent.Callable;
public class TestCall implements Callable<String> {
	private int id;
	public TestCall(int id) {
		this.id = id;
	}
	public String call() {
		// 该返回结果将被Future的get方法得到
		return "call()方法被自动调用,任务返回的结果是:" + id + "    "
				+ Thread.currentThread().getName();
	}
}

2、ExecutorService执行任务

    ExecutorService executorService = Executors.newCachedThreadPool();
    List<Future<String>> list = new ArrayList<Future<String>>();
    for (int i = 0; i < 10; i++) {
    	TestCall testCall = new TestCall(i);
     //Future 表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果
    Future<String> future = executorService.submit(testCall);
    list.add(future);
    }
    for(Future<String> fu : list) {
    	try {
    		//计算完成后只能使用 get 方法来获取结果,如有必要,计算完成前可以阻塞此方法
			System.out.println(fu.get());
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ExecutionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			//关闭
			executorService.shutdown();
		}
    }
    
执行结果:

call()方法被自动调用,任务返回的结果是:0    pool-1-thread-1
call()方法被自动调用,任务返回的结果是:1    pool-1-thread-2
call()方法被自动调用,任务返回的结果是:2    pool-1-thread-3
call()方法被自动调用,任务返回的结果是:3    pool-1-thread-4
call()方法被自动调用,任务返回的结果是:4    pool-1-thread-5
call()方法被自动调用,任务返回的结果是:5    pool-1-thread-6
call()方法被自动调用,任务返回的结果是:6    pool-1-thread-7
call()方法被自动调用,任务返回的结果是:7    pool-1-thread-3
call()方法被自动调用,任务返回的结果是:8    pool-1-thread-5
call()方法被自动调用,任务返回的结果是:9    pool-1-thread-2

任务的具体过程,一但任务传给ExecutorService的submit()方法则该方法自动在一个线程上执行。
从结果中可以看出任务提交到submit()方法也是首先选择空闲的线程来执行任务,如果没有空闲的则新建一个来执行这个任务。另外需要注意如果Future的返回值尚未完成,则get方法会阻塞等待直到Future完成返回,可以通过isDone()方法来判断是否完成了返回。


上面我们使用的线程池都是通过Executors类创建的,下面我们自己来创建一个自己的线程池。

1、创建自己的线程池其实是很简单的,使用ThreadPoolExecutor类来创建。

示例:

1、任务执行

    	//阻塞队列
    	BlockingQueue<Runnable>  bq= new ArrayBlockingQueue<Runnable>(10);
    	List<Future<String>> list = new ArrayList<Future<String>>();
    	//自定义线程池
    	ThreadPoolExecutor exec = new ThreadPoolExecutor(5,10,50,TimeUnit.MILLISECONDS,bq);
    	for(int i =0;i<20 ;i++) {
    		TestRunnable testRun = new TestRunnable(i);
    		//执行任务
    		exec.execute(testRun);
    	}
    	exec.shutdown();
    

2、任务:

package testDate;

public class TestRunnable implements Runnable {
private int id;
public TestRunnable(){};
public TestRunnable(int id) {
	this.id = id;
}
@Override
public void run(){
	System.out.println(Thread.currentThread().getName() +"  id: "+id);
}
}

执行结果:

pool-1-thread-2  id: 1
pool-1-thread-3  id: 2
pool-1-thread-2  id: 5
pool-1-thread-2  id: 6
pool-1-thread-2  id: 7
pool-1-thread-2  id: 8
pool-1-thread-2  id: 9
pool-1-thread-2  id: 10
pool-1-thread-2  id: 11
pool-1-thread-2  id: 12
pool-1-thread-2  id: 13
pool-1-thread-2  id: 14
pool-1-thread-7  id: 16
pool-1-thread-1  id: 0
pool-1-thread-1  id: 18
pool-1-thread-8  id: 17
pool-1-thread-4  id: 3
pool-1-thread-5  id: 4
pool-1-thread-6  id: 15
pool-1-thread-9  id: 19

通过执行我们发现自己定义的线程也可以执行,但还是建议不要使用自定义的线程池而应该使用Executors工程方法创建的线程池,他们均为大多数使用场景预定义了合适的线程数所以还是应该使用Executors工厂类创建的线程池。

总结:

Executors 工具类的不同方法按照我们的需求创建了不同的线程池,来满足业务的需求。

Executor 接口对象能执行我们的线程任务。

ExecutorService接口继承了Executor接口并进行了扩展,提供了更多的方法我们能获得任务执行的状态并且可以获取任务的返回值。

使用ThreadPoolExecutor 可以创建自定义线程池。

Future 表示异步计算的结果,他提供了检查计算是否完成的方法,以等待计算的完成,并可以使用get()方法获取计算的结果


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值