1、背景:为什么要有线程池,使用线程池的好处?
它的主要特点为:线程复用;控制最大并发数;管理线程
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
- 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
2、Executors线程池种类:
1、newCachedThreadPool(缓存线程池) 线程大小最大可以达到Integer.MAX_VALUE
2、newFixedThreadPool(定长线程池) 允许请求的队列长度为 Integer.MAX_VALUE
3、newScheduledThreadPool(调度线程池) 线程大小最大可以达到Integer.MAX_VALUE
4、newSingleThreadExecutor(单线程线程池) 允许请求的队列长度为 Integer.MAX_VALUE
- FixedThreadPool 和 SingleThreadExecutor : 允许请求的队列长度为 Integer.MAX_VALUE ,可能堆积大量的请求,从而导致 OOM。
- CachedThreadPool 和 ScheduledThreadPool : 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致 OOM。
1. 可缓存线程池
源码:
/**
* 1、该线程池的核心线程数量是0,线程的数量最高可以达到Integer 类型最大值;
* 2、创建ThreadPoolExecutor实例时传过去的参数是一个SynchronousQueue实例,说明在创建任务时,
* 若存在空闲线程就复用它,没有的话再新建线程。
* 3、线程处于闲置状态超过60s的话,就会被销毁。
*/
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
测试代码:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CacheThreadPool {
public static void main(String[] args) {
// 可缓存线程池 缓冲池容量大小为Integer.MAX_VALUE
//如果线程池长度超过处理需要,
// 可灵活回收空闲线程,若无可回收,则新建线程
ExecutorService ThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int index = i;
try {
Thread.sleep(index * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
ThreadPool.execute(new Runnable() {
public void run() {
System.out.println(Thread.currentThread()+":"+index);
}
});
}
}
}
打印结果:可以看到上面的代码因为每次循环都是隔index秒执行,这个时间足够之前的线程工作完毕,并在新循环中复用这个线程,程序的运行结果如下:
Thread[pool-1-thread-1,5,main]:0
Thread[pool-1-thread-1,5,main]:1
Thread[pool-1-thread-1,5,main]:2
Thread[pool-1-thread-1,5,main]:3
Thread[pool-1-thread-1,5,main]:4
Thread[pool-1-thread-1,5,main]:5
Thread[pool-1-thread-1,5,main]:6
Thread[pool-1-thread-1,5,main]:7
Thread[pool-1-thread-1,5,main]:8
Thread[pool-1-thread-1,5,main]:9
2.定长线程池(固定大小)
源码:
1、线程池的线程不会因为闲置超时被销毁。
2、使用的列队是LinkedBlockingQueue,表示如果当前线程数小于核心线程数,那么即使有空闲线程会复用线程去执行任务如果当前执行任务数量大于核心线程数,此时再提交任务就在队列中等待,直到有可用线程。
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
测试代码:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FixedThreadPool {
public static void main(String[] args) {
//固定容量线程池,
//可控制线程最大并发数,超出的线程会在队列中等待
ExecutorService fixedThreadPool=Executors.newFixedThreadPool(3);
for(int i=0;i<10;i++) {
final int index=i;
fixedThreadPool.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (Exception e) {
// TODO: handle exception
}
System.out.println(Thread.currentThread()+":"+index);
}
});
}
}
}
打印结果: 始终只有三个线程在执行任务,如果有别的任务,将会进入LinkedBlockingQueue队列进入等待执行
Thread[pool-1-thread-2,5,main]:1
Thread[pool-1-thread-1,5,main]:0
Thread[pool-1-thread-3,5,main]:2
Thread[pool-1-thread-1,5,main]:4
Thread[pool-1-thread-2,5,main]:3
Thread[pool-1-thread-3,5,main]:5
Thread[pool-1-thread-1,5,main]:6
Thread[pool-1-thread-3,5,main]:8
Thread[pool-1-thread-2,5,main]:7
Thread[pool-1-thread-1,5,main]:9
3.调度线程池
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());//使用延迟队列
}
//super 调用父类的 ScheduledThreadPoolExecutor extends ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledThreadPool {
public static void main(String[] args) {
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(2);
for (int i = 0; i < 10; i++) {
final int index=i;
scheduledThreadPool.schedule(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread()+":"+index);
}
}, 30, TimeUnit.SECONDS);//会发现延迟30秒后执行
}
}
}
4.单例线程池
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SingleThreadExecutor {
public static void main(String[] args) {
//单线程化的线程池
//它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int index = i;
singleThreadExecutor.execute(new Runnable() {
public void run() {
try {
System.out.println(Thread.currentThread()+":"+index);
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}
3、execute和submit的区别?
共同点: execute、submit都可以提交任务执行
异同点:1、 execute没有返回值,而submit方法具有返回值
2、execute出现异常 主线程可以获取到异常的,而submit也可以获取到该异常,不过要特殊处理,线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()
方法来获取返回值,get()
方法会阻塞当前线程直到任务完成。
4、实现 Runnable 接口和 Callable 接口的区别
Runnable
自 Java 1.0 以来一直存在,但Callable
仅在 Java 1.5 中引入,目的就是为了来处理Runnable
不支持的用例。
Runnable
接口不会返回结果或抛出检查异常,但是**Callable
接口**可以。所以,如果任务不需要返回结果或抛出异常推荐使用 Runnable
接口,这样代码看起来会更加简洁。
@FunctionalInterface
public interface Runnable {
/**
* 被线程执行,没有返回值也无法抛出异常
*/
public abstract void run();
}
@FunctionalInterface
public interface Callable<V> {
/**
* 计算结果,或在无法这样做时抛出异常。
* @return 计算得出的结果
* @throws 如果无法计算结果,则抛出异常
*/
V call() throws Exception;
}