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
}
}
参考文献: