在Java1.5之前,实现多线程编程比较麻烦,需要自己启动线程,并关注同步资源,防止线程死锁等问题,在1.5版本之后引入了并行计算框架,大大简化了多线程开发.
我们知道线程有5个状态:新建状态(New),可运行状态(Runnable,也叫做运行状态),阻塞状态(Blocked),等待状态(Waiting),结束状态(Terminated),线程的状态只能由新建状态转变为运行状态后才可能被阻塞或者等待,最终终结.
不可能出现本末倒置的情况,比如想把一个结束状态的线程转变为新建状态,则会出现异常.例如下面代码会出现异常:
1 import java.util.concurrent.TimeUnit; 2 3 public class Client { 4 public static void main(String[] args) throws Exception { 5 // 创建一个线程,新建状态 6 Thread t = new Thread(new Runnable() { 7 public void run() { 8 System.out.println("线程在运行……"); 9 } 10 }); 11 // 运行状态 12 t.start(); 13 // 是否是运行态,若不是则等待10毫秒 14 while (!t.getState().equals(Thread.State.TERMINATED)) { 15 TimeUnit.MILLISECONDS.sleep(10); 16 } 17 System.out.println(t.getState());//TERMINATED 18 // 直接由结束态转变为运行态 19 t.start(); 20 } 21 }
会报如下错误:
线程在运行……
TERMINATED Exception in thread "main" java.lang.IllegalThreadStateException at java.lang.Thread.start(Unknown Source) at cn.summerchill.test.Client.main(Client.java:20)
出现上面的异常就是不能从结束状态直接转变成可运行状态.
一个线程的运行时间分为三部分:T1为线程的启动时间,T2为线程的运行时间,T3为线程的销毁时间.如果一个线程不能被重复使用,每次创建一个线程都需要经过启动,运行,销毁这三个过程,那么这势必会增大系统的响应时间,有没有一个更好的方法降低线程的运行时间?
T2是无法避免的,只有通过代码优化缩短线程的运行时间.
T1和T2都可以通过线程池ThreadPool来缩短时间,比如在容器或者系统启动时,创建足够多的线程,当容器或系统需要时直接从线程池中获取,运算出结果再把线程返回到线程池中---ExecutorService就是实现了线程池的执行器,看实例代码:
import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor; public class Client { public static void main(String[] args) { //2个线程的线程池 ThreadPoolExecutor es = (ThreadPoolExecutor)Executors.newFixedThreadPool(2); //多次执行线程体 for (int i = 0; i < 4; i++) { es.submit(new Runnable() { public void run() { System.out.println(Thread.currentThread().getName()); } }); } System.out.println(es.getPoolSize()); System.out.println(es.getCorePoolSize()); //关闭执行器 es.shutdown(); } }
运行结果:
pool-1-thread-1 pool-1-thread-2 pool-1-thread-2 2 2 pool-1-thread-1
本次代码执行了4遍线程体,按照我们之前说的:一个线程不可能从结束状态转变为可运行状态,那为什么此处的2个线程可以反复使用呢?这就是我们要搞清楚的重点:
线程池的实现涉及以下三个名词:
(1)工作线程(Worker)
线程池中的线程,只有两个状态:可运行状态和等待状态,在没有任务的时候就处于等待状态,运行时可以循环的执行任务.
(2)任务接口(Task)
这是每个任务必须实现的接口,以供工作线程调度器调度,它主要规定了任务的入口,任务执行完的场景处理,任务的执行状态等...
这里有两种类型的任务,具有返回值或异常的Callable接口任务和无返回值并兼容旧版本的Runnable接口任务.
(3)任务队列(Work Queue)
用于存放等待处理的任务,一般是BlockingQueue的实现类,用来实现任务的排队处理.
***********分析源码****************
线程池的创建过程:
创建一个阻塞队列以容纳任务,在第一次执行任务时创建足够多的线程(不可超过许可线程数),并处理任务.之后每个工作线程自行从任务队列中获得任务,直到任务队列中的任务数量为0为止.
此时线程处于等待状态,一旦有任务再加入到队列中,即唤醒工作线程进行处理,实现线程的可复用性.
使用线程池减少的是线程的创建和销毁时间,这对于多线程来说非常有帮助.
我们常用的Servlet容器,每次请求处理的都是一个线程,如果不采用线程池技术,每次请求都会重新创建一个线程,这会导致系统的性能负荷加大,响应效率下降,降低了系统的友好性.