一、Executors线程框架
1)概述
传统方式,通过new Thread来创建对象,具有以下几个特点:
a. 每次new Thread新建对象性能差。
b. 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom。
c. 缺乏更多功能,如定时执行、定期执行、线程中断。
相比new Thread,Java提供的四种线程池的好处在于:
a. 重用存在的线程,减少对象创建、消亡的开销,性能佳。
b. 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
c. 提供定时执行、定期执行、单线程、并发数控制等功能。
2)Java的四种线程池
Java通过Executors提供四种线程池,分别为:
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
若是Executors提供的线程池还不能满足你的使用要求,你可以使用自定义的线程池。
Executors内的线程池其实是公用了一个方法来创建的,可以通过输入具体的参数来生成指定功能的线程池
同样,还可以指定或者自定义具体的线程拒绝策略RejectedExecutionHandler
jdk提供的拒绝策略分别为有:
AbortPolicy直接抛出RejectedExecutionException拒绝异常,程序继续执行
CallerRunsPolicy只要线程池未关闭,直接在调用线程运行当前被丢弃的任务
DiscardOldestPolicy丢弃最老或者说最早插入队列的那个请求,尝试再次提交当前任务
DiscardPolicy丢弃任务,不给予任何处理
通常来说,可以自定义拒绝策略,或者在插入任务之前就打印log或者发放httpClient通知来保证数据的安全。
public ThreadPoolExecutor(int corePoolSize,//核心线程池大小
int maximumPoolSize,//最大线程池大小
long keepAliveTime,//活跃时间
TimeUnit unit,//时间单位
BlockingQueue<Runnable> workQueue,//存放线程的队列
ThreadFactory threadFactory,//线程工厂
RejectedExecutionHandler handler//线程拒绝方式
){...}
3)自定义线程池
1.使用有界队列的自定义线程池
在使用有界队列时,若有新的任务需要执行,如果线程池实际线程数小于corePoolSize,则优先创建线程,
若大于corePoolSize,则会将任务加入队列,
若队列已满,则在总线程数不大于maximumPoolSize的前提下,创建新的线程,
若线程数大于maximumPoolSize,则执行拒绝策略,或其他自定义方式。
public class UseThreadPoolExecutor1 {
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
1, //coreSize
2, //MaxSize
60, //60
TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(3) //指定一种队列 (有界队列)
);
MyTask mt1 = new MyTask(1, "任务1");
MyTask mt2 = new MyTask(2, "任务2");
MyTask mt3 = new MyTask(3, "任务3");
MyTask mt4 = new MyTask(4, "任务4");
MyTask mt5 = new MyTask(5, "任务5");
MyTask mt6 = new MyTask(6, "任务6");
pool.execute(mt1);
pool.execute(mt2);
pool.execute(mt3);
pool.execute(mt4);
pool.execute(mt5);
pool.execute(mt6);
pool.shutdown();
}
}
插入了6个任务,创建线程执行任务1,234插入队列,然后创建线程执行任务5,然后2个线程依次执行队列中的任务。
默认采用AbortPolicy拒绝策略,程序抛出异常后继续执行
执行结果:
2.使用无界队列的自定义线程池
在使用有界队列时,若有新的任务需要执行,如果线程池实际线程数小于corePoolSize,则优先创建线程,
若大于corePoolSize,则会将任务加入队列。
如果任务创建和处理的速度差异很大,无界队列会保持快速增长,直到出现oom内存溢出。
public class UseThreadPoolExecutor2 implements Runnable{
private static AtomicInteger count = new AtomicInteger(0);
@Override
public void run() {
try {
int temp = count.incrementAndGet();
System.out.println("任务" + temp);
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception{
BlockingQueue<Runnable> queue =
new LinkedBlockingQueue<Runnable>();
ExecutorService executor = new ThreadPoolExecutor(
2, //core
10, //max
120L, //2fenzhong
TimeUnit.SECONDS,
queue);
for(int i = 0 ; i < 20; i++){
executor.execute(new UseThreadPoolExecutor2());
}
Thread.sleep(1000);
System.out.println("queue size:" + queue.size()); //10
Thread.sleep(2000);
}
}
插入20个任务,由于corePoolSize核心线程数为2,所以执行2个任务,其他18个任务存入队列依次执行
结果如下: