1.线程开的太多会影响效率和吞吐量
举例:获取图片,联网下载100张图片,开启100个线程去下载。
线程的执行时间:
T= T1 (线程的创建时间) + T2 (run方法执行时间) + T3 (线程销毁时间)
总的执行时间为 100*T ;
可以看出线程的创建与销毁会占用资源,影响效率
线程池的作用或者说解决的问题:解决线程反复的创建与销毁,做到线程的复用
通俗讲解 线程池的工作机制
1.线程池:线程池里创建线程(个数自己定)
2.缓存队列(就是放置任务的队列)
3.存活时间(假设 60S)
执行过程(模拟执行任务):
1.创建线程池(此时线程数为 0)
2.突然来了6个Runnable(任务),首先将这6个任务放入缓存队列
3.开始在线程池里创建线程(假定 线程池内最多创建4个线程),线程池会请求队列,将缓存队列里的runnable 加载到线程中 如图:
4.线程池内线程开始执行任务A.B.C.D,此时呢缓存队列中还有3个任务EFG,在等待执行。
5.假设 ABC 三个任务执行完了,那么线程池会再次向缓存队列请求任务,那么EFG三个任务被请求过去,线程执行EFG三个任务。
6.此时D任务执行完了,线程池向缓存队列请求任务,但是队列里面没有任务了,那么线程4将会等待60S(自己设定时间),如果60S内有任务过来了,那么线程4将会执行任务,如果60S内依旧没有任务,那么线程4将会销毁。同理线程1.线程2.线程3将会在执行完EFG任务60S后自动销毁。
7.如果此时来了一个任务H,那么线程池会创建一个线程去执行,如果来了两个任务,那么线程池会创建两个线程去执行。
总结:这就是线程池的工作机制,具体细节与源码请继续往下看。
ThreadPoolExecutor 类:
/**
* 各个参数的意义
*/
public ThreadPoolExecutor(
int corePoolSize,//核心线程数,就是线程池里面的核心线程数量
int maximumPoolSize,//最大线程数,就是线程池中最大的线程数
long keepAliveTime,//存活时间,线程没事干的时候的存活时间,超过这个时间就会被销毁
TimeUnit unit,//线程存活时间的单位
BlockingQueue<Runnable> workQueue,//线程队列
ThreadFactory threadFactory,//线程创建工厂,如果线程池需要创建线程就会调用newThread 来创建
RejectedExecutionHandler handler//线程池的策略模式,如果队列满了,任务添加到线程池的时候就会有问题
) {
}
在代码中如何调用呢?
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ThreadPoolExecutor threadPoolExecutor=new ThreadPoolExecutor(
4,//核心线程数,就是线程池里面的核心线程数量
10,//最大线程数,就是线程池中最大的线程数
60,//存活时间
TimeUnit.SECONDS,//线程存活时间的单位
new LinkedBlockingDeque<Runnable>(128),//线程队列,参数是队列可以放多少个任务
new ThreadFactory() {
// ThreadFactory() 为线程池创建线程
// 为何要自己去new ThreadFactory(),而不是给我们写好呢?
//我们需要给线程设置名称之类的,如果源码写好了,我们怎么设置呢。
@Override
public Thread newThread(@NonNull Runnable r) {
Thread thread = new Thread();//
thread.setName("给线程设置名称");
return thread;
}
}
);
for (int i = 0; i < 20; i++) {
//创建一个任务
Runnable runnable=new Runnable() {
@Override
public void run() {
//做事情...
}
};
//将任务添加到线程池中
threadPoolExecutor.execute(runnable);
}
}
}
到此呢你在项目中就可以简单应用了,我稍微封装了一下,在文章下面会附上封装类的链接。
接下来讲几个关键点:
1.核心线程数 跟 最大线程数 有什么关系?或者说怎样去理解这两个参数呢?
- 正常情况下:
线程队列 128 ,核心线程数 4 , 最大线程数 10,Runnable 20 个;
运行代码(在这个类中右键,执行Thread main()方法):
public class ThreadTest {
public static void main( String[] args){
ThreadPoolExecutor threadPoolExecutor;
BlockingQueue blockingQueue=new LinkedBlockingDeque(128);
threadPoolExecutor = new ThreadPoolExecutor(
4,
10,
60,
TimeUnit.SECONDS,
blockingQueue,
new ThreadFactory() {
@Override
public Thread newThread(@NonNull Runnable r) {
Thread thread = new Thread(r);
thread.setDaemon(false);//不要设置成守护线程
return thread;
}
});
for (int i = 0; i < 20; i++) {
Runnable runnable=new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("下载图片完毕"+ Thread.currentThread().getName());
}
};
threadPoolExecutor.execute(runnable);
}
}
}
执行结果:
下载图片完毕Thread-1
下载图片完毕Thread-0
下载图片完毕Thread-2
下载图片完毕Thread-3
下载图片完毕Thread-1
下载图片完毕Thread-0
下载图片完毕Thread-2
下载图片完毕Thread-3
下载图片完毕Thread-1
下载图片完毕Thread-0
下载图片完毕Thread-2
下载图片完毕Thread-3
下载图片完毕Thread-1
下载图片完毕Thread-0
下载图片完毕Thread-2
下载图片完毕Thread-3
下载图片完毕Thread-1
下载图片完毕Thread-0
下载图片完毕Thread-2
下载图片完毕Thread-3
可以看出 依次执行4个线程。
- 不正常情况下:
线程队列 4 ,核心线程数 4 , 最大线程数 10,Runnable 20 个;
改一下线程队列的大小
BlockingQueue blockingQueue=new LinkedBlockingDeque(4);
可以看到报错了,自己去运行一下。
执行顺序呢,先抛出个RejectedExecutionException错误,然后开启10个线程,然后再开启4个线程。(不正常的,不要纠结)
Exception in thread "main" java.util.concurrent.RejectedExecutionException:
下载图片完毕Thread-0
下载图片完毕Thread-4
下载图片完毕Thread-3
下载图片完毕Thread-5
下载图片完毕Thread-1
下载图片完毕Thread-7
下载图片完毕Thread-2
下载图片完毕Thread-6
下载图片完毕Thread-8
下载图片完毕Thread-9
下载图片完毕Thread-0
下载图片完毕Thread-4
下载图片完毕Thread-3
下载图片完毕Thread-5
好,现在来分析一下这个不正常的情况:
RejectedExecutionException 报错的原因呢也是 AsyncTask 存在的一些隐患,比如我要执行200个Runnable 就肯定会报错。
很重要:
// 线程队列 4 ,核心线程数 4 , 最大线程数 10,目前加入的 Runnable 有 20 个
// 20 个都要放到队列中,但是队列只有 4 还有16个是没法放的,这个时候最大线程数是 10 非核心线程是6,
//那么我会拿6个出来执行,这个时候会 重新创建6个线程,目前线程池就达到了10个线程(达到最大线程数)
//但是还有 10 个没办法放,就只能抛异常了,意味着那10个Runnable 没办法执行了,就会抛异常。
2.线程队列
BlockingQueue:先进先出的队列 FIFO(RxJava用)
SynchronousQueue :线程安全的,它里面是没有固定的缓存(OKHttp用)
PriorityBlockingQueue : 无序的可以根据优先级进行排序,指定的对象要实现Comparable做比较;
3.Google给我们封装好的线程池
CachedThreadPool()
可缓存线程池:
1.线程数无限制
2.有空闲线程则复用空闲线程,若无空闲线程则新建线程
3.一定程序减少频繁创建/销毁线程,减少系统开销
创建方法:
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
FixedThreadPool()
定长线程池:
1.可控制线程最大并发数(同时执行的线程数)
2.超出的线程会在队列中等待
创建方法:
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(int nThreads);
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
ScheduledThreadPool()
定长线程池:
支持定时及周期性任务执行。
创建方法:
ExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(int corePoolSize);
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
SingleThreadExecutor()
单线程化的线程池:
1.有且仅有一个工作线程执行任务
2.所有任务按照指定顺序执行,即遵循队列的入队出队规则
创建方法:
ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
总结:线程池一般都自定义,无非就是那几个参数,但是面试问的很多,最好呢还是亲手自定义一下。附上工具类的链接:https://github.com/CatEatFishs/ThreadPoolExecutors
本人对线程也不是很熟悉,这篇文章是开发中总结的,如有错误请指正!