多线程之线程池
1.线程池概述
线程池就是一个可以复用线程的技术。要理解什么是线程复用技术,先得看一下不使用线程池会有什么问题。
假设:用户每次发起一个请求给后台,后台就创建一个新的线程来处理,下次新的任务过来肯定也会创建新的线程,如果用户量非常大,创建的线程也讲越来越多。然而,创建线程是开销很大的,并且请求过多时,会严重影响系统性能。
而使用线程池,就可以解决上面的问题。如下图所示,线程池内部会有一个容器,存储几个核心线程,假设有3个核心线程,这3个核心线程可以处理3个任务。
但是任务总有被执行完的时候,假设第1个线程的任务执行完了,那么第1个线程就空闲下来了,有新的任务时,空闲下来的第1个线程可以去执行其他任务。依此内推,这3个线程可以不断的复用,也可以执行很多个任务。
所以,线程池就是一个线程复用技术,它可以提高线程的利用率。
2.创建线程池
在JDK5版本中提供了代表线程池的接口ExecutorService,而这个接口下有一个实现类叫ThreadPoolExecutor类,使用ThreadPoolExecutor类就可以用来创建线程池对象。
接下来,用这7个参数的构造器来创建线程池的对象。代码如下:
ExecutorService pool = new ThreadPoolExecutor(
3, //核心线程数有3个
5, //最大线程数有5个。 临时线程数=最大线程数-核心线程数=5-3=2
8, //临时线程存活的时间8秒。 意思是临时线程8秒没有任务执行,就会被销毁掉。
TimeUnit.SECONDS,//时间单位(秒)
new ArrayBlockingQueue<>(4), //任务阻塞队列,没有来得及执行的任务在,任务队列中等待
Executors.defaultThreadFactory(), //用于创建线程的工厂对象
new ThreadPoolExecutor.CallerRunsPolicy() //拒绝策略
);
-
临时线程什么时候创建?
新任务提交时,发现核心线程都在忙、任务队列满了、并且还可以创建临时线程,此时会创建临时线程。
-
什么时候开始拒绝新的任务?
核心线程和临时线程都在忙、任务队列也满了、新任务过来时才会开始拒绝任务。
3.线程池执行Runnable任务
创建好线程池之后,接下来我们就可以使用线程池执行任务了。线程池执行的任务可以有两种,一种是Runnable任务;一种是callable任务。下面的execute方法可以用来执行Runnable任务。
代码如下:
//先准备一个线程任务类
public class MyRunnable implements Runnable{
@Override
public void run() {
// 任务是干啥的?
System.out.println(Thread.currentThread().getName() + " ==> 输出666~~");
//为了模拟线程一直在执行,这里睡久一点
try {
Thread.sleep(Integer.MAX_VALUE);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//执行Runnable任务的代码,注意阅读注释,对照着前面的7个参数理解
ExecutorService pool = new ThreadPoolExecutor(
3, //核心线程数有3个
5, //最大线程数有5个。 临时线程数=最大线程数-核心线程数=5-3=2
8, //临时线程存活的时间8秒。 意思是临时线程8秒没有任务执行,就会被销毁掉。
TimeUnit.SECONDS,//时间单位(秒)
new ArrayBlockingQueue<>(4), //任务阻塞队列,没有来得及执行的任务在,任务队列中等待
Executors.defaultThreadFactory(), //用于创建线程的工厂对象
new ThreadPoolExecutor.CallerRunsPolicy() //拒绝策略
);
Runnable target = new MyRunnable();
pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行的!
pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行的!
pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行的!
//下面4个任务在任务队列里排队
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);
//下面2个任务,会被临时线程的创建时机了
pool.execute(target);
pool.execute(target);
// 到了新任务的拒绝时机了!
pool.execute(target);
执行结果如下:
4.线程池执行Callable任务
callable任务相对于Runnable任务来说,就是多了一个返回值。执行Callable任务需要用到下面的submit方法。
代码如下:
//先准备一个Callable线程任务
public class MyCallable implements Callable<String> {
private int n;
public MyCallable(int n) {
this.n = n;
}
// 2、重写call方法
@Override
public String call() throws Exception {
// 描述线程的任务,返回线程执行返回后的结果。
// 需求:求1-n的和返回。
int sum = 0;
for (int i = 1; i <= n; i++) {
sum += i;
}
return Thread.currentThread().getName() + "求出了1-" + n + "的和是:" + sum;
}
}
//再准备一个测试类,在测试类中创建线程池,并执行callable任务。
public class ThreadPoolTest2 {
public static void main(String[] args) throws Exception {
// 1、通过ThreadPoolExecutor创建一个线程池对象。
ExecutorService pool = new ThreadPoolExecutor(
3,
5,
8,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(4),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy());
// 2、使用线程处理Callable任务。
Future<String> f1 = pool.submit(new MyCallable(100));
Future<String> f2 = pool.submit(new MyCallable(200));
Future<String> f3 = pool.submit(new MyCallable(300));
Future<String> f4 = pool.submit(new MyCallable(400));
// 3、执行完Callable任务后,需要获取返回结果。
System.out.println(f1.get());
System.out.println(f2.get());
System.out.println(f3.get());
System.out.println(f4.get());
}
}
执行结果如下:
5.线程池工具类(Executors)
为了有快捷的创建线程池,Java为开发者提供了一个创建线程池的工具类,叫做Executors,它提供了方法可以创建各种不同特点的线程池。如下图所示
代码如下:
public class ThreadPoolTest3 {
public static void main(String[] args) throws Exception {
// 1、通过Executors创建一个线程池对象。
ExecutorService pool = Executors.newFixedThreadPool(17);
// 老师:核心线程数量到底配置多少呢???
// 计算密集型的任务:核心线程数量 = CPU的核数 + 1
// IO密集型的任务:核心线程数量 = CPU核数 * 2
// 2、使用线程处理Callable任务。
Future<String> f1 = pool.submit(new MyCallable(100));
Future<String> f2 = pool.submit(new MyCallable(200));
Future<String> f3 = pool.submit(new MyCallable(300));
Future<String> f4 = pool.submit(new MyCallable(400));
System.out.println(f1.get());
System.out.println(f2.get());
System.out.println(f3.get());
System.out.println(f4.get());
}
}
Executors创建线程池这么好用,为什么不推荐使用呢?原因在这里:看下图,这是《阿里巴巴Java开发手册》提供的强制规范要求。