该文的起源呢,是要归结到上一篇的AsyncTask源码整理一波,也是为了更好地掌握线程池这一块的知识。下面咱们看一个原始用线程的事例:
public class ExcuterActivity extends AppCompatActivity {
private static final String TAG = ExcuterActivity.class.getSimpleName();
ProgressBar progressBar;
ProgressBar progressBar1;
ProgressBar progressBar2;
ProgressBar progressBar3;
private Handler handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
int what = msg.what;
if (what == 0) {
progressBar1.setProgress((int) msg.obj);
} else if (what == 1) {
progressBar2.setProgress((int) msg.obj);
} else if (what == 2) {
progressBar3.setProgress((int) msg.obj);
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_excuter);
progressBar1 = findViewById(R.id.progress1);
progressBar2 = findViewById(R.id.progress2);
progressBar3 = findViewById(R.id.progress3);
for (int i = 0; i < 3; i++) {
final int progress = 0;
final int what = i;
new Thread() {
@Override
public void run() {
super.run();
excute(progress, what);
}
}.start();
}
}
private void excute(int progress, int what) {
while (progress < 100) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
progress++;
Message message = handler.obtainMessage(what);
message.obj = progress;
handler.sendMessage(message);
}
}
}
}
相信大家很熟悉上面的代码了吧,开启了三个线程,然后每过1秒增加一个进度,让handler处理消息。我想问如果任务很多的时候呢,是不是得创建很多个线程,所以这种做法肯定是不行的,下面就会衍生出线程池来了。其实线程池就是用来管理线程的,专门用一个队列来管理剩余的任务。android中用到的几种线程池其实主要围绕ThreadPoolExecutor
来派生出来的,所以下面主要来说明该类:
这张图包括了
ThreadPoolExecutor
所有的构造器,那咱们直接去看下参数最多的构造器:image.png
corePoolSize
:核心线程的个数maximumPoolSize
:线程池中最大的线程个数(最大线程个数=核心线程+非核心线程)keepAliveTime
:非核心线程在空闲的时候等待的时间unit
:上面参数等待的时间单位workQueue
:线程队列,能设置该队列能承载的最多线程threadFactory
:线程工厂,用于设置线程的名字,可以不用关心该参数handler
:当任务超过了队列能容载的任务时,处理的策略先来看一个基本的例子:
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
public Thread newThread(Runnable r) {
return new Thread(r, "thread #" + mCount.getAndIncrement());
}
};
private final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 5, 1,
TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>(10), sThreadFactory);
在上面初始化了3个核心的线程,然后最大的线程是5个,非核心线程在空闲的时候等待的时间是1秒,任务队列最大允许有10个任务,定义了一个sThreadFactory
只是为了打印线程的名字。
执行的地方换成了
threadPoolExecutor
来执行。image.png
excute获取每个线程的进度,以及每个线程的名字。
咋们再看下打印的日志:
可以看出来每过1秒钟,同时3个线程的progress加1。
如果线程池中的线程数未达到核心线程的个数,则会立马开启一个新的核心线程去执行
下面试着增大任务的个数为5个,其他的配置不变,看看情况会咋样:
image.png
可以看出来,这里用到的还是核心线程,并且将前面的1和2两个线程放到了线程队列中,等到1前面的3个任务执行完了后,让队列中的线程去处理后面的任务。
如果线程池的个数大于核心线程的个数,并且线程队列还能装下线程,因此让核心线程排到线程队列中,等到非队列的线程任务执行完了后,才会执行队列中的线程。
那下面把线程的个数填满线程队列,上面设置的线程队列最大容载是10个线程,10个+核心线程3个=13个。那咱们设置14个看下会发生什么,为了看到效果我把progress的界限改了下:
再执行看下:
可以看得出来,核心线程总共是3个,线程队列是10个线程容量,而任务是14个,因此当线程队列满了10个的时候,还需要一个线程,因此创建了一个非核心线程4来执行任务。
为了验证这个猜测,我们现在再增加任务到16个,看是不是创建了3个非核心线程,这里我把最多的线程个数调到7个,方便我们观察线程的动态:
image.png
image.png
可以看出来确实创建了3个非核心线程,也验证了我们的结论:
当线程队列满的时候,如果还有任务需要执行,此时需要几个线程就需要创建几个非核心线程
上面都是未超过非核心线程的个数,那么如果线程队列也满了,而且需要剩下的线程个数超过了非线程的个数会咋样呢,这里我把任务继续调到18个,那此时需要的非核心线程是不是就是18-(3个核心线程+10个线程队列的个数)=5个非核心线程,而我们定义的非核心线程是4个,那此时看看会发生什么:
image.png
这里抛了一个异常信息:
image.png
意思是线程的总共个数是7个,不能达到需要线程的个数。因此这里可以得出结论:
在任务需要非核心线程个数大于设置的最大非核心线程的个数时候,此时是直接抛RejectedExecutionException异常。
说完了上面的几种情况,其实java里面给提供了几种常用的线程池,在Executors
类中有如下几种线程池:
FixedThreadPool
image.png image.png
从日志也看得出来,总共是3个线程在倒腾,这个没什么好说的。
这里可以看到最大线程数和核心线程数是相等的,说明没有非核心线程的说法了,也就是自始至终都只有核心线程。
SingleThreadExecutor
image.png
image.png image.png
看到日志大家也明白了,自始至终只有一个线程在工作。
只有一个核心线程,和我们平常new一个thread是一个道理
CachedThreadPool
该线程只有非核心线程,并且非核心线程在空闲的时候等60s就销毁了
其他的几种线程池就自己看了,这里只是列举出一两种。
总结
- 如果线程池中的线程数未达到核心线程的个数,则会立马开启一个新的核心线程去执行
- 如果线程池的个数大于核心线程的个数,并且线程队列还能装下线程,因此让核心线程排到线程队列中,等到非队列的线程任务执行完了后,才会执行队列中的线程。
- 当线程队列满的时候,如果还有任务需要执行,此时需要几个线程就需要创建几个非核心线程。
- 在任务需要非核心线程个数大于设置的最大非核心线程的个数时候,此时是直接抛RejectedExecutionException异常。
网友评论