Android中线程池值得掌握一波

该文的起源呢,是要归结到上一篇的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来派生出来的,所以下面主要来说明该类:

image.png

这张图包括了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只是为了打印线程的名字。

image.png

执行的地方换成了threadPoolExecutor来执行。
image.png

excute获取每个线程的进度,以及每个线程的名字。

咋们再看下打印的日志:

image.png

可以看出来每过1秒钟,同时3个线程的progress加1。
如果线程池中的线程数未达到核心线程的个数,则会立马开启一个新的核心线程去执行

下面试着增大任务的个数为5个,其他的配置不变,看看情况会咋样:


image.png

可以看出来,这里用到的还是核心线程,并且将前面的1和2两个线程放到了线程队列中,等到1前面的3个任务执行完了后,让队列中的线程去处理后面的任务。

如果线程池的个数大于核心线程的个数,并且线程队列还能装下线程,因此让核心线程排到线程队列中,等到非队列的线程任务执行完了后,才会执行队列中的线程。
那下面把线程的个数填满线程队列,上面设置的线程队列最大容载是10个线程,10个+核心线程3个=13个。那咱们设置14个看下会发生什么,为了看到效果我把progress的界限改了下:

image.png

image.png

再执行看下:

image.png

可以看得出来,核心线程总共是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

image.png

从日志也看得出来,总共是3个线程在倒腾,这个没什么好说的。
这里可以看到最大线程数和核心线程数是相等的,说明没有非核心线程的说法了,也就是自始至终都只有核心线程。
SingleThreadExecutor
image.png

image.png

image.png

看到日志大家也明白了,自始至终只有一个线程在工作。

只有一个核心线程,和我们平常new一个thread是一个道理
CachedThreadPool

image.png

该线程只有非核心线程,并且非核心线程在空闲的时候等60s就销毁了
其他的几种线程池就自己看了,这里只是列举出一两种。

总结

  • 如果线程池中的线程数未达到核心线程的个数,则会立马开启一个新的核心线程去执行
  • 如果线程池的个数大于核心线程的个数,并且线程队列还能装下线程,因此让核心线程排到线程队列中,等到非队列的线程任务执行完了后,才会执行队列中的线程。
  • 当线程队列满的时候,如果还有任务需要执行,此时需要几个线程就需要创建几个非核心线程。
  • 在任务需要非核心线程个数大于设置的最大非核心线程的个数时候,此时是直接抛RejectedExecutionException异常。

推荐阅读更多精彩内容