线程池就是一个可以复用线程的技术。前面三种多线程方法就是在用户发起一个线程请求就创建一个新线程来处理,下次新任务来了又要创建新线程,而创建新线程的开销是很大的,这样会严重影响系统的性能。线程池就相当于预先创建好几个线程(招聘几个打工人),来分配之后要处理的任务(干活)。
线程池的接口:ExecutorService
线程池对象
使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象
可以看到有7个参数,通过这些参数设置线程池的规模和特征。ExecutorService的常用方法有execute、submit、shutdown、shutdownNow。
- ThreadPoolExecutor构造器的参数:
- 1:指定线程池的线程数量(核心线程): corePoolSize
- 2:指定线程池可支持的最大线程数: maximumPoolSize
- 3:指定临时线程的最大存活时间: keepAliveTime
- 4:指定存活时间的单位(秒、分、时、天): unit
- 5:指定任务队列: workQueue
- 6:指定用哪个线程工厂创建线程: threadFactory
- 7:指定线程忙,任务满的时候,新任务来了怎么办: handler,默认丢弃任务并抛出RejectedExecutionException异常。
- ThreadPoolExecutor创建线程池对象
ExecutorService pool=new ThreadPoolExecutor(3,6,8, TimeUnit.SECONDS, new ArrayBlockingQueue<>(6), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
- 线程池处理Runnable任务 ——pool.execute()
首先实现Runnable接口,重写run方法。然后创建MyRunnable任务对象,只不过这里不是把MyRunnable任务对象交给Thread处理,而是使用线程池pool的execute()方法。
class myRunnable implements Runnable{ @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + " ——>" + i); } try { System.out.println(Thread.currentThread().getName() + " —— 休眠"); Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + " —— 启动"); } catch (InterruptedException e) { e.printStackTrace(); } } } // Runnable target=new myRunnable(); pool.execute(target);
- 线程池处理Callable任务 ——pool.submit()
定义类实现Callable接口,重写call方法,封装要做的事情。然后把Callable对象进行submit(),并且可以返回执行后的结果。
class myCallable implements Callable<String> { private int n; public myCallable(int n) { this.n = n; } @Override public String call() throws Exception { int s=0; for (int i = 0; i < n; i++) { s+=n; } return Thread.currentThread().getName()+" 1+...+" + n + ", 子线程执行结果: "+s; } } // Callable myCallable=new myCallable(10); Future<String> f1 = pool.submit(myCallable); System.out.println(f1.get());
使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象
Executors的底层其实也是基于线程池的实现类ThreadPoolExecutor创建线程池对象的。Executors得到线程池对象的常用方法:
方法 |
说明 |
弊端 |
public static ExecutorService newCachedThreadPool() |
线程数量随着任务增加而增加,如果线程任务执行完毕且空闲了一段时间则会被回收掉。 |
允许创建的线程数量最大上限是Integer.MAX_VALUE,非常非常大, 可能会创建大量线程,出现OOM错误( 内存溢出 java.lang.OutOfMemoryError ) |
public static ExecutorService newFixedThreadPool(int nThreads) |
创建固定线程数量的线程池,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程替代它。 |
允许请求的任务队列长度是Integer.MAX_VALUE,可能会堆积大量请求,出现OOM错误 |
public static ExecutorService newSingleThreadExecutor () |
创建只有一个线程的线程池对象,如果该线程出现异常而结束,那么线程池会补充一个新线程。 |
允许请求的任务队列长度是Integer.MAX_VALUE,可能会堆积大量请求,出现OOM错误 |
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) |
创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务。 |
允许创建的线程数量最大上限是Integer.MAX_VALUE, 可能会创建大量线程,出现OOM错误 |
Executors不适合做大型互联网场景的线程池方案,建议使用ThreadPoolExecutor来指定线程池参数,这样可以明确线程池的运行规则,规避资源耗尽的风险。
定时器
定时器是一种控制任务延时调用,或者周期调用的技术。定时器的实现方式有两种:
方式一:Timer。创建Timer定时器对象,然后开启定时器。这是一种单线程方法,处理多个任务按照顺序执行,存在延时与设置定时器的时间有出入。可能因为其中的某个任务的异常使Timer线程死掉,从而影响后续任务执行。
Timer t=new Timer(); t.schedule(new TimerTask() { @Override public void run() { System.out.println("定时器~"); } },3000,3000);
方式二: ScheduledExecutorService定时器。基于线程池,某个任务的执行情况不会影响其他定时任务的执行。先得到线程池对象,然后再进行周期调度方法。
ScheduledExecutorService pool=Executors.newScheduledThreadPool(3); pool.scheduleAtFixedRate(new TimerTask() { @Override public void run() { System.out.println("定时器"); } },3,3,TimeUnit.SECONDS);
并发与并行
CPU同时可以处理线程的数量有限,所以CPU会轮询为每个线程服务,只是由于CPU切换的速度很快,给我们的感觉这些线程在同时执行,其实可能只是并发。所以说多个线程其实是并发与并行同时进行的。
线程的6种状态
NEW(新建) |
线程刚被创建,但是并未启动。 |
Runnable(可运行) |
线程已经调用了start()等待CPU调度。 |
Blocked(锁阻塞) |
线程在执行的时候未竞争到锁对象,则该线程进入Blocked状态。 |
Waiting(无限等待) |
一个线程进入Waiting状态,另一个线程调用notify或者notifyAll方法才能够唤醒。 |
Timed Waiting(计时等待) |
同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。带有超时参数的常用方法有Thread.sleep 、Object.wait。 |
Teminated(被终止) |
因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。 |