一.线程池的引入
- 线程的创建和销毁需要时间
- 创建对象:需要分配内存等资源
- 销毁对象:虽然java有垃圾自动回收机制,但是垃圾回收器一直在后台跟踪并销毁
- 频繁的创建和销毁线程对象再高并发的时候对性能影响特别大
既然线程的频繁销毁会影响性能,那我们有没什么方法可以解决呢?这就可以使用我们的线程池来解决了。
我们想想平时我们使用的共享自行车,我们是不是不用自己去买一台,只需要在指定的地点扫码就可以使用,在不使用的时候归还到指定的地点就可以了。
这里的共享自行车就是线程,指定的地点就是线程池。
我们先创建好一定数量的线程放入线程池,在需要使用时直接拿来使用不用创建,在使用完后再归还线程池。这样就可以避免频繁的创建销毁。
二.线程池的优点
- 提高响应速度(减少线程创建的时间)
- 降低资源消耗(充分利用线程池里的线程)
- 提高线程的管理性(避免无限创建,消耗系统资源)
三.线程池使用
1.线程池的创建
//创建单线程的线程池
ExecutorService pool = Executors.newSingleThreadExecutor();
//创建固定线程数的线程池
ExecutorService pool2 = Executors.newFixedThreadPool(10);
//创建动态线程池
ExecutorService pool3 = Executors.newCachedThreadPool();
//创建定时任务线程池
ExecutorService pool4 = Executors.newScheduledThreadPool(10);
2.线程池执行Runnable
package com.wq.pool;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyThreadPool {
public static void main(String[] args) {
//1.创建线程池
//创建单线程的线程池
// ExecutorService pool = Executors.newSingleThreadExecutor();
//创建固定线程数的线程池
ExecutorService pool = Executors.newFixedThreadPool(10);
// //创建动态线程池
// ExecutorService pool3 = Executors.newCachedThreadPool();
// //创建定时任务线程池
// ExecutorService pool4 = Executors.newScheduledThreadPool(10);
//2.创建任务
int i = 0;
pool.submit(new TestRunnable(i));
//3.关闭线程池
pool.shutdown();
}
}
class TestRunnable implements Runnable{
int i ;
public TestRunnable(int i) {
this.i = i;
}
@Override
public void run() {
while (i < 20 ){
System.out.println("任务"+i+"开始");
System.out.println("任务"+i+"结束");
i++;
}
}
}
3.线程池执行Callable
package com.wq.pool;
import com.wq.thread2.MyCallable;
import java.util.Random;
import java.util.concurrent.*;
public class MyThreadPool2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//1.创建线程池
// //创建单线程的线程池
// ExecutorService pool = Executors.newSingleThreadExecutor();
// //创建固定线程数的线程池
ExecutorService pool = Executors.newFixedThreadPool(10);
// //创建动态线程池
// ExecutorService pool3 = Executors.newCachedThreadPool();
// //创建定时任务线程池
// ExecutorService pool4 = Executors.newScheduledThreadPool(10);
//2.创建任务
int i = 0;
for (i=0 ; i < 20 ; i++){
TestCallable myCallable = new TestCallable();
Future future = pool.submit(myCallable);
String result = (String)future.get();
System.out.println(result);
}
//3.关闭线程池
pool.shutdown();
}
}
class TestCallable implements Callable<String> {
@Override
public String call() throws Exception {
int randomNum = new Random().nextInt(10);
String result = String.valueOf(randomNum);
return result;
}
}
这里我们讨论个问题:
如果我们的TestCallable加了sleep(1000)那我们创建个newFixedThreadPool(10)的线程池,能在大约2秒的时间内执行完毕吗?
class TestCallable implements Callable<String> {
@Override
public String call() throws Exception {
int randomNum = new Random().nextInt(10);
String result = String.valueOf(randomNum);
Thread.sleep(1000);
return result;
}
}
执行了发现我们很明显不行,这个原因是什么呢?按道理说,一个固定线程数为10的线程一次执行10个任务,在休息1秒,执行10个任务,再休息1秒,就可以结束了。
原因:在我们的for循环里有get()方法,当我们的任务没有执行完成的时候,get()方法阻塞了for循环,导致任务没有不间断的进入线程池中。
改进:我们在创建任务里用一个list来接收Future,有点类似与我们异步处理。
//2.创建任务
int i = 0;
ArrayList<Future> arrayList = new ArrayList<Future>();
for (i=0 ; i < 20 ; i++){
TestCallable myCallable = new TestCallable();
Future future = pool.submit(myCallable);
arrayList.add(future);
}
for (Future item : arrayList){
System.out.println(item.get());
}
四.线程池创建的参数
我们点开newSingleThreadExecutor(),newFixedThreadPool(10),newCachedThreadPool(),newScheduledThreadPool(10) 都可以看到其实都是调用了这个ThreadPoolExecutor构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
来! 我们好好说说这个几个参数都是干什么用的
- corePoolSize:核心线程数,线程池最少时保持的线程数
- maximumPoolSize:最大线程数,线程池最多存在的线程数
- keepAliveTime:存活时间,线程池中缓冲线程(大于核心线程数的线程数就是缓冲线程数)的存活时间
- unit:时间单位
- workQueue:决定了缓存任务的排队策略。
- threadFactory:线程工厂,工厂设计模式,生产线程
- handler:拒绝策略
四种拒绝策略
- CallerRunsPolicy:发现线程池还在运行就直接运行这个线程
- DiscardOldestPolicy:在线程池的等待队列中将头一个去除,再将当前线程放入
- DiscardPolicy:不做处理
- AbortPolicy:抛出异常(默认策略)
结束语
面试的时候线程还是经常会提到,这次也是再把基础的过一遍,收获还是很多,起码不会在面试的时候就回答到线程的三种创建方式。可以再和面试官聊聊线程同步,线程通信,线程池的基本原理。虽不至于吊打,但起码还是能扯扯。