为什么使用线程池
不用线程池
public class PoolTest {
public static void main(String[] args) {
Long start=System.currentTimeMillis();
List list=new ArrayList();
Random r=new Random();
for(int i=0;i<100000;i++)
{
Thread t=new Thread(new Runnable() {
@Override
public void run() {
list.add(r.nextInt());
}
});
t.start();
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Long end=System.currentTimeMillis();
System.out.println(end-start);
System.out.println(list.size());
}
}
10985
100000
用了线程池
public class PoolTest {
public static void main(String[] args) {
Long start=System.currentTimeMillis();
List list=new ArrayList();
Random r=new Random();
ExecutorService es = Executors.newSingleThreadExecutor();
for(int i=0;i<100000;i++)
{
es.execute(new Runnable() {
@Override
public void run() {
list.add(r.nextInt());
}
});
}
es.shutdown();
try {
es.awaitTermination(2, TimeUnit.DAYS);
} catch (InterruptedException e) {
e.printStackTrace();
}
Long end=System.currentTimeMillis();
System.out.println(end-start);
System.out.println(list.size());
}
}
30
100000
明显性能大大大大大大提高了,为什么呢?
因为Thread t=new Thread(new Runnable(){})方法太耗时了.多线程的基本思想是执行任务,任务被封装在Runable的run()方法里面。那么可以通过复用的思想,让一个Thread对象多次传入Runnable参数。
线程池的组成部分
1.核心线程,用于执行任务的线程
2.阻塞队列,当核心线程都在工作,新加入的任务就是放入阻塞队列中
2.救急线程,当阻塞队列也满了,使用救急线程执行任务。救急线程有等待时间。如果超过了等待时间 还是没有任务,就杀死救急线程
4.线程工厂,没什么用,负责给线程池的线程编号
5.拒绝策略,当救急线程也不管用了,使用拒绝策略处理新的任务
线程池的创建方法
1.自定义方法
public class MyThreadPoolDemo {
public static void main(String[] args) {
ExecutorService threadPool = new ThreadPoolExecutor(
2, //核心线程数
5, //总线程数量,救急线程数量=5-2
1L,
TimeUnit.SECONDS, //这两个是救急线程的等待时间
new LinkedBlockingQueue<>(3), //阻塞队列,长度是3,如果不指定,就是最大整数
Executors.defaultThreadFactory(), //工厂方法
new ThreadPoolExecutor.DiscardOldestPolicy()); //拒绝策略
try {
for(int i = 0; i < 10; i++){
int temp = i;
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName() + "\t 执行任务:" + temp);
});
}
}catch (Exception e){
e.printStackTrace();
}finally {
threadPool.shutdown();
}
}
}
面试阿里的时候碰到了一个让我崩溃是问题,如果不指定阻塞队列长度会怎么样?答案非常简单。这个时候拒绝策略就失效了,如果任务量太大,会导致内存爆满。正式考虑到内存的承受能力,才需要设置阻塞队列长度和线程数量,并加入拒绝策略。
但是目前的自带的构造线程池方法,要么阻塞队列长度无限,要么救急线程无限,所以都是不推荐的方法。
2.newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
特点
核心线程数 == 最大线程数(没有救急线程被创建),因此也无需超时时间。
阻塞队列是无界的,可以放任意数量的任务。但是容易造成系统爆满。
评价:适用于任务量已知,相对耗时的任务。
3. newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
特点
核心线程数是 0, 最大线程数是 Integer.MAX_VALUE,救急线程的空闲生存时间是 60s,意味着全部都是救急线程(60s 后可以回收),没有数量限制。但是这容易造成内存崩塌。
队列采用了 SynchronousQueue 实现特点是,它没有容量,没有线程来取是放不进去的(一手交钱、一手交货),类似于缓存。
评价:整个线程池表现为线程数会根据任务量不断增长,没有上限,当任务执行完毕,空闲 1分钟后释放线程。 适合任务数比较密集,但每个任务执行时间较短的情况。
4.newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
使用场景:
希望多个任务排队执行。线程数固定为 1,任务数多于 1 时,会放入无界队列排队。任务执行完毕,这唯一的线程也不会被释放。