JAVA线程池应用实践

0.引言

  在上一篇文章:http://blog.csdn.net/jin_kwok/article/details/74898102 中,我简单介绍过线程和多线程,在这里,我将详细介绍一下内容: 线程的创建方法和线程池的应用。

1.线程的创建方法

  1.1.【继承Thread类,覆写run()方法】

  如下代码所示,扩展了Thread类,并覆写了run方法,run()方法的内容就是线程获得CPU资源后所执行的部分,称为执行体,如下面的示例代码:执行步骤为:
  1.创建MyThread类实例,也就是创建线程对象;
  2.调用start()方法,是线程处于[就绪]状态,并一定会立即执行,而是需要等待CPU调度;
public class ThreadIntroduction {

	public static void main(String[] args)
	{
		for(int index=0;index<3;index++)
		{
			MyThread thread=new MyThread("just for a test");
			thread.start();	
		}
	}
}

class MyThread extends Thread
{
	//自定义
	private String description;
	public MyThread(String description)
	{
		this.description=description;		
	}
	
	@Override
	public void run()
	{
		System.out.println(description);
		
		for(int i=0;i<5;i++)
		{
			System.out.println(Thread.currentThread().getName()+": ["+i+"]");
		}
	}
}
某次运行结果


 1.2.【实现Runnable接口】

    Runnable接口中也有一个run()方法,同样是作为线程的执行体,因此,用户可以根据需要在其中填写代码,Runnable接口的实现类并不能作为一个线程直接启动,而是作为Thread类的target,再经由Thread对象类启动:
  1.实现Runnable接口,自定义run()方法内容;
  2.创建Runnable接口实现类对象;
  3.将Runnable接口实现类对象作为target,创建Thread类对象;
  4.调用start();
public class ThreadIntroduction {

	public static void main(String[] args)
	{
		for(int index=0;index<3;index++)
		{
			RunnableThread runnablethread=new RunnableThread("just for a Runnable Interface test");
			//Runnable接口实现类对象作为Thread类的target,创建Thread类对象
			new Thread(runnablethread).start();
		}
	}
}

class RunnableThread implements Runnable
{
	//自定义
	private String description;
	public RunnableThread(String description)
	{
		this.description=description;		
	}
	
	@Override
	public void run()
	{
		System.out.println(description);
		
		for(int i=0;i<5;i++)
		{
			System.out.println(Thread.currentThread().getName()+": ["+i+"]");
		}
	}
}
某次运行结果


  1.3.【通过Callable接口和Future创建线程

       1. 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。

       2. 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。

       3. 使用FutureTask对象作为Thread对象的target创建并启动新线程。

       4. 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

 
public class ThreadIntroduction {

	public static void main(String[] args)
	{
		//2.创建实现Callable的类的对象
		MyCallableThread callablethread=new MyCallableThread();
		//3.用FutureTask包装实现了Callable接口的类的对象
		FutureTask<String> ft=new FutureTask<String>(callablethread);
		//4.将包装后的对象作为target,创建Thread对象,并调用start
		new Thread(ft).start();
		
		try 
		{
			System.out.println(Thread.currentThread().getName()+":"+ft.get());
		} catch (InterruptedException | ExecutionException e) {
			e.printStackTrace();
		}
		
	}
}

//1.创建实现Callable的类,并实现call方法
class MyCallableThread implements Callable<String>
{

	@Override
	public String call() throws Exception
	{	
		for(int i=0;i<5;i++)
		{
			System.out.println(Thread.currentThread().getName()+": ["+i+"]");
		}
		return "just a test for Callable interface.";
	}
	
}
某次运行结果


2.线程池

   在第一节中介绍了三种创建线程的方式,这些线程从创建到结束(new->dead),生命周期很短,如果频繁的创建线程,其创建和销毁将会大量消耗系统资源,相应地,使用线程池则有诸多好处:
  <1>:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  <2>:提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
  <3>:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。但是要做到合理的利用线程池,必须对其原理了如指掌。

 2.1 java线程池核心类ThreadPoolExecutor

 首先看一下 ThreadPoolExecutor类的构造函数:  
public ThreadPoolExecutor(int corePoolSize,
        int maximumPoolSize,
        long keepAliveTime,
        TimeUnit unit,
        BlockingQueue<Runnable> workQueue)
其参数的意义分别为:
  <1>corePoolSize:核心线程池的大小,即核心线程池所能容纳的线程数量上限 ,当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建,超过核心线程池数量的任务将会被保存到阻塞队列(work Queue )中;
  <2>maximumPoolSize:线程池的最大线程容量,如果阻塞队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是如果使用了无界的任务队列这个参数就没什么效果;
  <3>keepAliveTime:默认情况下,只对池中非核心线程起作用,非核心线程在没有任务可执行的情况下的最大保持时间,换言之,当任务很少的时候,处于“空闲状态”的非核心线程将被辞退,销毁,池中线程数量保持为核心线程数。但是,如果调用了allowCoreThreadTimeOut(boolean)方法,空闲的核心线程也会被销毁;
  <4>workQueue:一个阻塞队列,用来存储等待执行的任务,当任务占满核心线程容限,新到来的任务将存储于阻塞队列中,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,阻塞队列有多种类型可选;
  <5>unit:keepAliveTime的单位:ns,ms,s,m....
  <6> RejectedExecutionHandler handler: 任务拒绝策略
 <7> ThreadFactory threadFactory: 线程工厂,用来创建线程
 <8> completedTaskCount: 用来记录已经执行完毕的任务个数


【注意:】当任务过多时的流程:核心线程满—>阻塞队列满—>最大线程满—>异常处理(拒绝接受任务)

用一个图来表示线程池的用法:


 2.2 线程池应用举例

public class MainClassForTest {

	public static void main(String[] args) 
	{
		int corePoolSize=2;
        int maximumPoolSize=4;
        long keepAliveTime=20;
        TimeUnit unit=TimeUnit.SECONDS;
        BlockingQueue<Runnable> workQueue=new ArrayBlockingQueue<Runnable>(2);
        
		ThreadPoolExecutor pool =new ThreadPoolExecutor(corePoolSize, maximumPoolSize, 
				keepAliveTime, unit, workQueue);	
		
		//创建6个任务,并将其加入到线程池中
		for(int i=0;i<6;i++)
		{
			//创建自定义任务实例
			MyRunnable task=new MyRunnable("ID["+i+"]");
			//加入到线程池
			pool.execute(task);
			
			System.out.println("当前任务数量:"+pool.getTaskCount());
			System.out.println("当前池中线程数量:"+pool.getPoolSize());
			System.out.println("当前阻塞队列中任务数量:"+pool.getQueue().size());		
		}		
	}
}

/**
 * 实现Runnable接口,创建自定义任务类MyRunnable
 */
class MyRunnable implements Runnable
{
	private String id;
	public MyRunnable(String id)
	{
		this.id=id;
	}
	@Override
	public void run() 
	{	
		try{
			Thread.sleep(1000L);
		}catch( InterruptedException e)
		{
			e.printStackTrace();
		}
		
		System.out.println(Thread.currentThread().getName()+":"+id);
		
	}
	
}
 【上面代码某次运行结果】:


【注意:】应该注意到,上述例子的代码中:创建的任务总数=最大线程数+阻塞队列容限,正因为满足这一条,上述代码运行中并没有出现拒绝接受任务的异常,但是,实际中难免出现,怎么处理呢?

2.3.异常处理

 当并发任务数量>线程池最大线程数+阻塞队列容限,就会出现以下异常:拒绝执行任务


 为了避免上述异常,我们一般在阻塞队列这个参数上做文章,阻塞队列有几种类型可选:
  <1> ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
  <2> LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
  <3>SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
  <4>PriorityBlockingQueue:一个具有优先级得无限阻塞队列。
 
 为了避免出现拒绝任务的异常,一般将队列的容限设置一个较大值,此外,有四种异常处理模式可选:
 

  (1) 默认的ThreadPoolExecutor.AbortPolicy:处理程序遭到拒绝将抛出运行时RejectedExecutionException;
  (2) ThreadPoolExecutor.CallerRunsPolicy:线程调用运行该任务的 execute 本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度

  (3) ThreadPoolExecutor.DiscardPolicy:不能执行的任务将被删除;

  (4) ThreadPoolExecutor.DiscardOldestPolicy :如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,则重复此过程)。

  在上面例子中加上一行代码,设置异常处理模式:

public static void main(String[] args) 
	{
		int corePoolSize=2;
        int maximumPoolSize=4;
        long keepAliveTime=20;
        TimeUnit unit=TimeUnit.SECONDS;
        BlockingQueue<Runnable> workQueue=new ArrayBlockingQueue<Runnable>(2);
        
		ThreadPoolExecutor pool =new ThreadPoolExecutor(corePoolSize, maximumPoolSize, 
				keepAliveTime, unit, workQueue);
		//设置异常处理模式
		pool.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
		//创建8个任务,并将其加入到线程池中
		for(int i=0;i<8;i++)
		{
			//创建自定义任务实例
			MyRunnable task=new MyRunnable("ID["+i+"]");
			//加入到线程池
			pool.execute(task);
			
			System.out.println("当前任务数量:"+pool.getTaskCount());
			System.out.println("当前池中线程数量:"+pool.getPoolSize());
			System.out.println("当前阻塞队列中任务数量:"+pool.getQueue().size());		
		}		
	}

  此外,我们也可以通过实现RejectedExecutionHandler接口,自定义异常处理;

class MyRejectedExecutionHandler implements RejectedExecutionHandler
{

	@Override
	public void rejectedExecution(Runnable r, ThreadPoolExecutor executor)
	{
		// TODO Auto-generated method stub
		
	}

}


参考文献:







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jin_Kwok

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值