Android 线程与线程池 Thread&ThreadPool

1. 线程Thread

学过计算机的人都知道,什么是Thread。对于计算机来说,Thread是最小的执行单元。做过Android开发的人都知道,我们开发的App至少存在一个线程,那就是主线程。那么如果我们想要执行耗时的任务的话,我们是不能在主线程做的,因为这样会引起ANR。此时,我们就要开启一个新的线程来做我们的任务,我们可以称之为工作线程。

1.1 开启新的线程

那么,我们应该如何在新线程中执行自己的任务呢?我们有两种方法。
1. 继承Thread类并覆盖其run()方法
2. 使用Runnable作为参数实例化Thread

1.1.1 继承Thread类并覆盖其run()方法

一个简单的例子如下,

    class MyThread extends Thread{
        @Override
        public void run() {
            // do some thing
            super.run();
        }
    }

我们就可以实例化一个线程MyThread thread = new MyThread.

1.1.2 使用Runnable作为参数实例化Thread

Runnable是什么呢?其实Runable只是一个接口,其定义了一个函数run(),这个函数就是线程执行的内容。当我们使用的时候,我们需要实现Runnable接口,将需要做的工作代码放在run()方法中。
简单的一个例子如下,
首先我们实现Runnable接口

    class MyRunnable implements Runnable{
        @Override
        public void run() {
            //do some thing
        }
    }

然后我们可以这样实例化一个Thread

        MyRunnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable);

更简单一点,我们可以这样,

        Thread thread = new Thread(new MyRunnable());

或者,我们可以将Runnable接口匿名实现,这样,

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                //do some thing
            }
        });

上边三种代码原理其实是一样的哈。都是将Runnable的实现(也就是run()方法的实现啦)传给Thread。

1.2 执行新的线程

上面讲了两种开启新的线程,那么我们应该怎么让线程开始执行呢?我们可以通过调用Thread.start()方法来执行线程。

1.3 如何停止线程

上面我们介绍了,如何新建一个线程,并执行。那么,我们就会想,如果一个线程正在执行中,我们不想让他继续执行了,那么我们有什么办法让它停止呢?我们在Developer上发现,Thread类有stop方法的,但是在API 1的时候,就被弃用,它是这样解释的,

because stopping a thread in this manner is unsafe and can leave your application and the VM in an unpredictable state.

当我们使用stop()停止一个线程是不安全的,可以造成App或虚拟机不可控的状态。
那是不是我们就没有办法停止一个线程了?
首先,我们知道,当一个线程的run()方法执行完后,该线程会自动关闭。那么,我们就可以在run()方法内部设置一个标志位,判断是否应该执行还是退出,比如这样,

    class MyThread extends Thread {
        private boolean stop = false;

        public void stopThread() {
            this.stop = true;
        }

        @Override
        public void run() {
            while (!stop) {
                // do some thing
            }
            super.run();
        }
    }

2. 线程池ThreadPool

上面我们介绍了Thread,考虑这样的一个场景,我们需要100个(甚至更多)的线程来执行不同的任务,那么就涉及到100个线程的新建、调用和销毁。这样给处理器或内容带来了较大的负担。那么,有没有什么方法可以解决该问题呢?

线程池就是解决该问题的。什么是线程池呢?通俗的理解就是,存放线程的池子。Developer这样提到,

Thread pools address two different problems: they usually provide improved performance when executing large numbers of asynchronous tasks, due to reduced per-task invocation overhead, and they provide a means of bounding and managing the resources, including threads, consumed when executing a collection of tasks.

两个好处,1. 可以减少多个线程带来的负担。2. 提供了管理资源(包括线程)的方法。

2.1 新建线程池

Andorid并没有提供关于线程池的构造函数,而是提供了工厂模式,封装在Executors类中。我们可以这样得到线程池,

2.1.1 newCachedThreadPool()

ExecutorService threadPool = Executors.newCachedThreadPool();

上边的代码是得到一个缓冲线程池,什么是缓冲线程池呢,就是在必要的时候,会新建一个新的线程加入该线程池,什么是必要的时候呢?就是该线程池的线程不能复用的时候(也就是当线程池中的所有线程都是在运行的时候),否则,就会复用线程。

2.2.2 newFixedThreadPool

ExecutorService threadPool = Executors.newFixedThreadPool(2);

上边是得到一个固定个数的线程池,当我们要执行的任务个数大于该固定值时,其他任务就会等待,直到该线程中的某个任务执行完,然后执行等待的任务。

2.2.3 newSingleThreadExecutor

ExecutorService threadPool = Executors.newSingleThreadExecutor();

上边的代码是得到一个单一线程的线程池。当我们向线程池添加任务时,任务会按顺序依次执行。

2.2 将线程加入到线程池

2.2.1 execute

上边介绍了如何得到不同的线程池,那么我们如何将线程加入到线程池呢?其提供了execute方法,我们可以将类型为Runnable的实例传进来。Runnable就是我们上边提到的接口啦。比如,我们可以这样,

threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    Log.i(TAG, "current Thread id: " + Thread.currentThread().getId());
                }
            });

传入的时候,我们要复写其run()方法,也就是将我们要执行的任务代码放入到run()方法中。

2.2.2 submit

ThreadPool同时还提供了submit方法,我们可以将Runnable或Callable传进来,其同时还有返回值,类型为Future

2.3 如何管理加入线程池的任务

2.2节 我们介绍了如何将任务加入到线程池,那么问题来了,如果我们将很多任务加入到线程池,然后想取消其中的任务,那么怎么办呢?

ExecutorService接口提供了两个函数shutdown()和shutdownNow(),其中shutdown()是关闭向线程池加入任务,但是已经加入的则会继续执行完毕。shutdownNow是关闭线程池中等待执行的任务,已经在执行的则会强行中断。

举个例子,我们新建一个单线程池,然后加入10个任务,每个任务休眠1秒钟,由于是单线程池,那么加入的10个任务就会依次在线程池中执行。我们在线程池执行任务的时候,分为调用shutdownshutdownNow,然后看一下结果。

ExecutorService threadPool = Executors.newSingleThreadExecutor();
        idx = 0;
        for (int i = 0; i < 10; i++) {
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    idx++;
                    Log.i(TAG, "Thread[" + idx + "] start, id: " + Thread.currentThread().getId());
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    Log.i(TAG, "Thread[" + idx + "]  end, id: " + Thread.currentThread().getId());
                }
            });
        }

调用shutdown的运行截图如下,
shutdown的运行截图
由截图,我们可以看到,虽然我们在Thread5开始执行的时候,调用了shutdown,但是并没有影响已经加入到线程池的任务,在调用shutdown前已经加入的任务,将会继续执行完毕。但是调用shutdown后,并不能向线程池加入新的任务。

调用shutdownNow的运行截图如下,
shutdownNow的运行截图
由截图,我们可以看到,我们在Thread5开始的时候,调用shutdownNow,我们发现,Thread6-10并没有执行,而且我们还发现,Thread5被中断了。

发布了31 篇原创文章 · 获赞 15 · 访问量 9万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章