Android多线程学习

463 阅读7分钟

1.线程的概念

  线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一个线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Android系统中,当我们启动一个应用时,Zygote会为其孵化一个进程,并在之后创建一个线程-即主线程或UI线程,应用的所有操作都在主线程进行。因此,当我们在主线程进行耗时操作时,就会阻塞主线程,导致页面卡顿甚至ANR的出现。为解决这一问题就必须使用多线程开发。

2.简单线程创建

  • Thread-通过继承Thread或new Thread()并重写run()方法
fun main(args: Array<String>){
    Thread(){
        run {
            print("Thread")

        }
    }.start()
    
    MyThread().start()
}

class MyThread :Thread(){
    override fun run() {
        super.run()
            print("MyThread")
    }
}

运行结果:

  • Runnable-通过实行Runnable
fun main(args: Array<String>){
    Thread(Runnable {
        println("Runnable")

    }).start()
    
    Thread(MyRunnable()).start()
}
class  MyRunnable :Runnable{
    override fun run() {
        println("MyRunnable")
    }
}

运行结果:

3.线程的创建及执行-源码分析

通过上述两种方式创建线程,都会调用到init()方法。

public Thread() {
    init(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}
@Override
public void run() {
    if (target != null) {//如果Runnable不为空
        target.run();//则调用Runnable的run()方法
    }
}
/**
 * Initializes a Thread.
 * 初始化线程
 * @param g the Thread group //线程组
 * 线程组表示一组线程。此外,线程组还可以包括其他线程组。线程组形成一个树,其中除了初始线程
 * 组以外的每个线程组都有一个父线程。
 * @param target the object whose run() method gets called //Runnable的实现类
 * @param name the name of the new Thread //线程的名字
 * @param stackSize the desired stack size for the new thread, or
 *        zero to indicate that this parameter is to be ignored.
 *        stackSize表示线程所需的堆栈大小,若为零表示该参数将被忽略。
 */
private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
    //这是一个native方法,返回对当前正在执行的线程对象的引用
    //这里返回的是主线程,因为我们的线程是在主线程中创建的,所以主线程是父进程
    Thread parent = currentThread();
    if (g == null) { //如果线程组为空
        g = parent.getThreadGroup();//获取主线程的线程组
    }
    //增加线程组中未启动线程的计数。
    //虽然未启动的线程没有添加到线程组中,但是为了便于在未启动的情况下可以收集它们,
    //必须对它们进行计数,以便在线程组中具有未启动线程的守护进程线程组不会被销毁。
    g.addUnstarted();
    this.group = g;//把线程组赋给新线程的线程组

    this.target = target;//传递Runnable
    this.priority = parent.getPriority();//获取线程的优先级
    this.daemon = parent.isDaemon();//是否是守护线程
    setName(name);//设置线程的名字

    init2(parent);

    /* Stash the specified stack size in case the VM cares */
    this.stackSize = stackSize;//存储指定的堆栈大小
    tid = nextThreadID();//生成新线程的ID
}

/**
 * Causes this thread to begin execution; the Java Virtual Machine
 * calls the <code>run</code> method of this thread.
 * 线程开始执行,Java虚拟机会调用Thread.run()方法
 * 同一个线程不能反复调用该方法
 */
public synchronized void start() {
    
    // Android-changed: throw if 'started' is true
    if (threadStatus != 0 || started)//如果不是一个新线程或线程已经start()了
        throw new IllegalThreadStateException();//则直接抛出异常

/* Notify the group that this thread is about to be started
 * so that it can be added to the group's list of threads
 * and the group's unstarted count can be decremented. */
 /**
  * 通知线程组这个线程即将启动
  * 这样就可以将它添加到线程组列表中,同时减少线程组的未启动计数。
  * /
    group.add(this);

    started = false;//表示还未真正启动
    try {
        //通过native方法执行线程任务
        nativeCreate(this, stackSize, daemon);
        started = true;//表示已经真正启动
    } finally {
        try {
            if (!started) {//启动失败
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
            /* do nothing. If start0 threw a Throwable then
              it will be passed up the call stack */
        }
    }
}

NOTE:同一个线程不能多次调用start()方法,上面源码已经分析了。简单示例:

fun main(args: Array<String>) {
    val thread = MyThread()
    thread.start()
    thread.start()
}

运行结果:

4.线程的关闭

线程执行完后,一定要记得及时开闭。不然会空耗系统资源。

  • stop()-强制线程停止执行(已过时)。某些情况会抛出SecurityException异常。
  • suspend()-被弃用
  • destroy()-被弃用
  • interrupt()-虽然方法注释上写着中断线程,但是它只是表示一个中断位置标识符。简单示例:
fun main(args: Array<String>) {
    MyThread().start()
}
class MyThread() : Thread() {
    var num:Int =1
    override fun run() {
        super.run()
        while (true) {
            try {
                println("interrupt()之前$num")
                if (num == 5) {
                    println("中断")
                    interrupt()
                }
                println("----${num++}----")
                Thread.sleep(1000)

            } catch (e: Exception) {
                //interrupt()
            }
        }
    }
}

看到了吗?即使调用了interrupt()还在运行。那怎么正确关闭呢?请看下面:

class MyThread() : Thread() {
    var num:Int =1
    override fun run() {
        super.run()
        //如果调用了interrupt()方法,isInterrupted()为true,否则为false
        //别调用interrupted(),该方法表示清除中断标志,并返回原状态
        //当然你也可以使用volatale关键字修饰的变量来作标识符,
        //但是如果是阻塞型任务,就找不到检查标识符的时机了
        while (!isInterrupted) {
            try {
                if (num == 5){
                    interrupt()
                }
                println("----${num}----")
                num++
                Thread.sleep(1000)
            } catch (e: Exception) {
                interrupt()
            }
        }
    }
}

NOTE:调用interrupt()方法后,线程会在合适的时候自己结束。

5.线程池

  一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。 例如,线程数一般取cpu数量+2比较合适,线程数过多会导致额外的线程切换开销。

5.1 线程池的创建

  • Executors
  • Executors.newFixedThreadPool(线程数)-线程数固定
  • Executors.newFixedThreadPool(线程数,threadFactory)-线程数固
  • Executors.newSingleThreadExecutor()-单一线程的线程池
  • Executors.newCachedThreadPool()-无固定大小。创建一个线程池,该线程池根据需要创建新的线程,如果以前构建的线程可用就会复用它们。这种线程池通常会改善执行许多短期异步任务的程序的性能。如果没有现有线程可用,将创建一个新线程并将其添加到线程池池中。60秒未使用的线程将终止并从缓存中删除。因此,一个闲置时间足够长的池不会消耗任何资源。

简单示例:

  • 执行单个任务
private val executorService = Executors.newFixedThreadPool(1)//创建持有一个线程的线程池
//同上
private val executorService2 = Executors.newFixedThreadPool(1,object :ThreadFactory{
    override fun newThread(r: Runnable?): Thread {
        return Thread(r)
    }

})
private var num:Int = 1
fun main(args: Array<String>) {
    //执行线程任务,无返回值
    //executorService.execute {
    //  doTask("execute")
    //}
    //执行线程任务,返回Future对象。通过它可以判断任务的执行状态
    val future= executorService.submit {
        doTask("submit")
    }
     while (!future.isDone){
        Thread.sleep(2000)
        println("--------------")
    }
}
private fun doTask(name:String){
    while (!executorService.isShutdown){
        if (num == 5){
            executorService.shutdown()
        }
        println("---$name---$num----")
        num++
        Thread.sleep(1000)
    }
}

运行结果:

  • 执行多个任务
fun main(args: Array<String>) {
    val executorService = Executors.newFixedThreadPool(3)//创建三个线程的线程池
    //执行三个任务
    val futures = executorService.invokeAll(arrayListOf(MyTask(),MyTask(),MyTask()))
    futures.forEach {
        println(it.get()+"${System.currentTimeMillis()}")
    }
}
class MyTask:Callable<String>{//实现Callable接口
    var num:Int =1
    override fun call(): String {
        while (num<7){
            Thread.sleep(1000)
            println("${Thread.currentThread().name}---->>$num}----")
            num ++
        }
        return "${Thread.currentThread().name}--结束"
    }

}

运行结果:

  • ThreadPoolExecutor-上述两个方法都会调用到ThreadPoolExecutor
-------Executors类-------------
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>(),
                                  threadFactory);
}

简单示例:

fun main(args: Array<String>) {
    val threadPoolExecutor = ThreadPoolExecutor(
            2,
            2,
            1,
            TimeUnit.MINUTES,
            LinkedBlockingDeque())
    val futures = executorService.invokeAll(arrayListOf(MyTask(), MyTask()))
    futures.forEach {
        println(it.get() + "${System.currentTimeMillis()}")
    }

}

运行结果:

6.线程池创建-源码分析

/**
 * If false (default), core threads stay alive even when idle.
 * If true, core threads use keepAliveTime to time out waiting
 * for work.
 * 默认是false,核心线程即使处于闲置状态也会存活在线程池中。如果为true,
 * 则会响应keepAliveTime参数
 */
private volatile boolean allowCoreThreadTimeOut;
/**
 * @param corePoolSize the number of threads to keep in the pool, even
 *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
 * 核心线程数。除非设置了allowCoreThreadTimeOut为ture,否则即使它们处于闲置状态,
 * 也会存活在线程池中
 * @param maximumPoolSize the maximum number of threads to allow in the
 *        pool
 *        线程池允许的最大线程数
 * @param keepAliveTime when the number of threads is greater than
 *        the core, this is the maximum time that excess idle threads
 *        will wait for new tasks before terminating.
 *        线程的存活时间,即在等待任务时的时间
 * @param unit the time unit for the {@code keepAliveTime} argument
 * 时间类型
 * @param workQueue the queue to use for holding tasks before they are
 *        executed.  This queue will hold only the {@code Runnable}
 *        tasks submitted by the {@code execute} method.
 *        工作队列
 */
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         threadFactory, defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
}
//上述三个构造器,最终都会调用到这里
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

上面分析还有两个参数没有说明

  1. BlockingQueue - 阻塞队列
  • LinkedBlockingQueue - 是一个单向链表实现的阻塞队列。该队列按 FIFO(先进先出)排序元素,新元素插入到队列的尾部,并且队列获取操作会获得位于队列头部的元素。如果不指定大小,默认值Integer.MAX_VALUE。
fun main(args: Array<String>) {
    val threadPoolExecutor = ThreadPoolExecutor(
            2,
            2,
            1,
            TimeUnit.MINUTES,
            LinkedBlockingDeque())
    val futures = executorService.invokeAll(arrayListOf(MyTask(), MyTask(), MyTask(),MyTask()))
    futures.forEach {
        println(it.get() + "${System.currentTimeMillis()}")
    }

}

运行结果:上面我们创建了核心线程为2,最大线程数为2的线程池,但是我们的任务有四个,所以先执行前面两个任务,第三、四个任务被阻塞,直到有线程已经执行完,再来执行第三、四个任务。

  • ArrayBlockingQueue - 是一个用数组实现的有界阻塞队列,策略也是先进先出。
  • SynchronousQueue - 没有容量,是无缓冲等待队列,是一个不存储元素的阻塞队列,会直接将任务交给消费者,必须等队列中的添加元素被消费后才能继续添加新的元素。

Executors.newCachedThreadPool()线程池用的就是该队列。

2.RejectedExecutionHandler - 拒绝策略,即任务在什么情况下不会被执行。通常都是使用默认的。