new Thread的弊端
执行一个异步任务你还只是如下new Thread吗?
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
}
}).start();
复制代码
new Thread的弊端如下:
- 每次new Thread新建对象性能差。
- 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom。
- 缺乏更多功能,如定时执行、定期执行、线程中断。 相比new Thread,Java提供的四种线程池的好处在于:
- 重用存在的线程,减少对象创建、消亡的开销,性能佳。
- 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
- 提供定时执行、定期执行、单线程、并发数控制等功能。
Java 线程池
Java通过Executors提供四种线程池,分别为:
- new CachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
- new FixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
- new ScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
- new SingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
Executors 主要是这个构造函数:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
复制代码
而Executors工厂类一共可以创建四种类型的线程池,通过Executors.newXXX即可创建。下面就分别都介绍一下把。
1. FixedThreadPool
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。示例代码如下:
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 {
System.out.println(index);
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
复制代码
因为线程池大小为3,每个任务输出index后sleep 2秒,所以每两秒打印3个数字。
定长线程池的大小最好根据系统资源进行设置。
原理
public static ExecutorService newFixedThreadPool(int nThreads){
return new ThreadPoolExecutor(nThreads,nThreads,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}
复制代码
- 它是一种固定大小的线程池;
- corePoolSize和maximunPoolSize都为用户设定的线程数量nThreads;
- keepAliveTime为0,意味着一旦有多余的空闲线程,就会被立即停止掉;但这里keepAliveTime无效;
- 阻塞队列采用了LinkedBlockingQueue,它是一个无界队列;
- 由于阻塞队列是一个无界队列,因此永远不可能拒绝任务;- 由于采用了无界队列,实际线程数量将永远维持在nThreads,因此maximumPoolSize和keepAliveTime将无效。
2. CachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。示例代码如下:
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int index = i;
try {
Thread.sleep(index * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
cachedThreadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(index);
}
});
}
复制代码
原理
//构造函数
public static ExecutorService newCachedThreadPool(){
return new ThreadPoolExecutor(0,Integer.MAX_VALUE,60L,TimeUnit.MILLISECONDS,new SynchronousQueue<Runnable>());
}
复制代码
线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。
- 它是一个可以无限扩大的线程池;
- 它比较适合处理执行时间比较小的任务;corePoolSize为0,maximumPoolSize为无限大,意味着线程数量可以无限大;
- keepAliveTime为60S,意味着线程空闲时间超过60S就会被杀死;
- 采用SynchronousQueue装等待的任务,这个阻塞队列没有存储空间,这意味着只要有请求到来,就必须要找到一条工作线程处理他,如果当前没有空闲的线程,那么就会再创建一条新的线程。
3. SingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。示例代码如下:
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int index = i;
singleThreadExecutor.execute(new Runnable() {
@Override
public void run() {
try {
System.out.println(index);
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
复制代码
结果依次输出,相当于顺序执行各个任务。
原理
public static ExecutorService newSingleThreadExecutor(){
return new ThreadPoolExecutor(1,1,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}
复制代码
它只会创建一条工作线程处理任务;采用的阻塞队列为LinkedBlockingQueue;
4. ScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行。延迟执行示例代码如下:
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
scheduledThreadPool.schedule(new Runnable() {
@Override
public void run() {
System.out.println("delay 3 seconds");
}
}, 3, TimeUnit.SECONDS);
复制代码
表示延迟3秒执行。
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("delay 1 seconds, and excute every 3 seconds");
}
}, 1, 3, TimeUnit.SECONDS);
复制代码
表示延迟1秒后每3秒执行一次。
ScheduledExecutorService比Timer更安全,功能更强大
原理
- 它接收SchduledFutureTask类型的任务,有两种提交任务的方式:scheduledAtFixedRate, scheduledWithFixedDelay
- 它采用DelayQueue存储等待的任务
DelayQueue内部封装了一个PriorityQueue,它会根据time的先后时间排序,若time相同则根据sequenceNumber排序;
DelayQueue也是一个无界队列;
工作线程会从DelayQueue取已经到期的任务去执行;
执行结束后重新设置任务的到期时间,再次放回DelayQueue
最佳实践:
AsynTask 4.0
- 默认线程池是 SingleThreadExecutor; 单一串行线程池。
- 内部还内置一个CPU*2+1的 FixedThreadPool,命名为:THREAD_POOL_EXECUTOR。
RxJava
- Schedulers.io( )
默认是一个CachedThreadScheduler,用于IO密集型任务,如异步阻塞IO操作(读写文件、读写数据库、网络信息交互等)所使用的 Scheduler。
- Schedulers.computation( )
默认是:FixedThreadPool。用于CPU 密集型计算,即不会被 I/O 等操作限制性能的操作,例如图形的计算,事件循环或和回调处理
- Schedulers.immediate( )
在当前线程立即开始执行任务,相当于不指定线程。这是默认的 Scheduler
- AndroidSchedulers.mainThread()
Android专用的,指定的操作将在 Android 主线程运行
- Schedulers.from(executor)
使用指定的Executor作为调度器
- Schedulers.newThread()
总是启用新线程,并在新线程执行操作。
EventBus:
- 使用 newCachedThreadPool,实现 前台线程通知
OKHTTP:
- 类似 newCachedThreadPool 实现:
executor = new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue(), Util.threadFactory("OkHttp ConnectionPool", true));
复制代码
Fresco
- Default implementation of SerialExecutorService
参考
- 代码例子参考网络
- 图片流程图部分来自知乎截图