我们使⽤线程的时候就去创建⼀个线程,这样实现起来⾮常简便,但是就会有⼀个问题:
如果并发的线程数量很多,并且每个线程都是执⾏⼀个时间很短的任务就结束了,这样频繁创建线程就会⼤⼤降低系统的效率,因为频繁创建线程和销毁线程需要时间。
那么有没有⼀种办法使得线程可以复⽤,就是执⾏完⼀个任务,并不被销毁,⽽是可以继续执⾏其他的任务?
在Java中可以通过线程池来达到这样的效果。
线程池:其实就是⼀个容纳多个线程的容器,其中的线程可以反复使⽤,省去了频繁创建线程对象的操作,⽆需反复创建线程⽽消耗过多资源。
1. 降低资源消耗。减少了创建和销毁线程的次数,每个⼯作线程都可以被重复利⽤,可执⾏多个任务。
2. 提⾼响应速度。当任务到达时,任务可以不需要的等到线程创建就能⽴即执⾏。
3. 提⾼线程的可管理性。可以根据系统的承受能⼒,调整线程池中⼯作线线程的数⽬,防⽌因为消耗过多的内存,⽽把服务器累趴下(每个线程需要⼤约1MB内存,线程开的越多,消耗的内存也就越⼤,最后死机)。
Java⾥⾯线程池的顶级接⼝是 java.util.concurrent.Executor ,但是严格意义上讲 Executor
并不是⼀个线程池,⽽只是⼀个执⾏线程的⼯具。真正的线程池接⼝是java.util.concurrent.ExecutorService 。
要配置⼀个线程池是⽐较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在 java.util.concurrent.Executors 线程⼯⼚类⾥⾯提供了⼀些静态⼯⼚,⽣成⼀些常⽤的线程池。官⽅建议使⽤Executors⼯程类来创建线程池对象。
Java类库提供了许多静态⽅法来创建⼀个线程池:
Executors类中创建线程池的⽅法如下:
a、 newFixedThreadPool 创建⼀个固定⻓度的线程池,当到达线程最⼤数量时,线程池的规模将不再变化。
b、 newCachedThreadPool 创建⼀个可缓存的线程池,如果当前线程池的规模超出了处理需求,将回收空的线程;当需求增加时,会增加线程数量;线程池规模⽆限制。
c、 newSingleThreadPoolExecutor 创建⼀个单线程的Executor,确保任务对了,串⾏执⾏
d、 newScheduledThreadPool 创建⼀个固定⻓度的线程池,⽽且以延迟或者定时的⽅式来执⾏,类似Timer;
使⽤线程池中线程对象的步骤:
1. 创建线程池对象。
2. 创建Runnable接⼝⼦类对象。(task)
3. 提交Runnable接⼝⼦类对象。(take task)
获取到了⼀个线程池ExecutorService 对象,定义了⼀个使⽤线程池对象的⽅法如下:
public Future submit(Runnable task) :获取线程池中的某⼀个线程对象,并执⾏Future接⼝:⽤来记录线程任务执⾏完毕后产⽣的结果。线程池创建与使⽤。
4. 关闭线程池(⼀般不做)。
示例:
class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("我要一个教练");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("教练来了:"+Thread.currentThread().getName());
System.out.println("教完后,教练回到了游泳池");
}
}
public class ThreadPoolDemo {
public static void main(String[] args) {
//创建线程池对象。
//创建一个固定长度的线程池,当到达线程最大数量时,线程池的规模将不再变化。
//ExecutorService pool = Executors.newFixedThreadPool(3);
//创建一个可缓存的线程池,如果当前线程池的规模超出了处理需求,将回收空的线程;当需求增加时,会增加线程数量
//ExecutorService pool = Executors.newCachedThreadPool();
//创建一个单线程的Executor
//ExecutorService pool = Executors.newSingleThreadExecutor();
//创建Runnable接口子类对象。(task)
MyRunnable myRunnable = new MyRunnable();
// //提交Runnable接口子类对象。(take task)
// pool.submit(myRunnable);
// pool.submit(myRunnable);
// pool.submit(myRunnable);
// //注意:submit方法调用后,程序并不终止,因为线程池控制了线程的关闭
//
//
// //关闭线程池(一般不做)。
// pool.shutdown();
//
//new Thread(myRunnable,"线程1").start();
//创建一个固定长度的线程池,而且以延迟或者定时的方式来执行,类似Timer
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
for (int i = 0; i < 3; i++) {
scheduledExecutorService.schedule(myRunnable,3, TimeUnit.SECONDS);//每个任务延迟10秒执行
}
scheduledExecutorService.shutdown();//关闭线程池,不会马上终止程序
while(!scheduledExecutorService.isTerminated()){
}
System.out.println("程序终止!!");
}
}
⼀般情况下,使⽤Runnable接⼝、Thread实现的线程我们都是⽆法返回结果的。但是如果对⼀些场合需要线程返回的结果。就要使⽤⽤Callable、Future这⼏个类。Callable只能在ExecutorService的线程池中跑,但有返回结果,也可以通过返回的Future对象查询执⾏状态。
Future 本身也是⼀种设计模式,它是⽤来取得异步任务的结果。
看看其源码:
public interface Callable {
V call() throws Exception;
}
它只有⼀个call⽅法,并且有⼀个返回V,是泛型。可以认为这⾥返回V就是线程返回的结果。
ExecutorService接⼝:线程池执⾏调度框架
Future submit(Callable task);
Future submit(Runnable task, T result);
Future> submit(Runnable task);
示例:
import java.util.concurrent.*;
class HandleCallable implements Callable{
private String name;
public HandleCallable(String name){
this.name = name;
}
@Override
public Integer call() throws Exception {
System.out.println(this.name+"开始计算了...");
//睡眠
Thread.sleep(200);
int sum = 0;
//产生一个随机数
int random = (int)(Math.random()*10);//Math.random()--->0~1
System.out.println(this.name+"产生的随机数:"+random);
for (int i = 0; i <= random; i++) {
sum +=i;
}
return sum;
}
}
public class FutureTest {
public static void main(String[] args) {
//线程池
ExecutorService pool = Executors.newFixedThreadPool(3);
HandleCallable t1 = new HandleCallable("线程1");
HandleCallable t2 = new HandleCallable("线程2");
HandleCallable t3 = new HandleCallable("线程3");
//提交任务
Future result1 = pool.submit(t1);
Future result2 = pool.submit(t2);
Future result3 = pool.submit(t3);
//关闭线程池
pool.shutdown();
//将结果打印一下
try {
System.out.println("线程1的结果:"+result1.get());
System.out.println("线程2的结果:"+result2.get());
System.out.println("线程3的结果:"+result3.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}