Android线程学习笔记

前言

线程对于每个学习Android的人,都很熟悉,可以说是日常任务。但是真的有那么熟悉吗?比如说线程有哪些状态,怎样控制状态的跳转,恐怕很多人还不太清晰。本文就和大家一块学习Android源码中的Thread类(api26)。

开始

线程是一个程序的执行单元,JVM允许应用并发执行多个线程。

关于并发(Concurrency)和并行(Parallelism)网上有这样的区别:

  • 并发:在一时间段内执行多个任务
  • 并行:同一时刻执行多个任务

假如有A和B两个任务,则:
并发:ABABAB...
并行:AAAAAA...
           BBBBBB...

每个线程有一个优先级,高优先级优先执行。每个线程都可以标记为daemon线程。在一个线程中创建另一个线程时,新线程和原线程享有相同的优先级和daemon标记。

JVM启动后,通常只有一个非daemon线程,即执行main方法所在的线程。JVM在以下任意条件发生时将不再执行线程:

  1. Runtime.exit()方法在合法的情况下被执行。
  2. 所有非daemon线程死亡。线程死亡可以是run方法执行完毕,或者抛出异常引起。

创建线程有两种方法

  1. 继承Thread类,覆写run方法。如下:
 class PrimeThread extends Thread {
      long minPrime;
      PrimeThread(long minPrime) {
            this.minPrime = minPrime;
      }
      public void run() {
            // compute primes larger than minPrime
      }
}

PrimeThread p = new PrimeThread(143);
p.start();

需要注意的是,启动线程必须执行start方法,直接执行run方法并不会启动新的线程!!

  1. 实现Runnable接口,然后作为参数传递给Thread类执行,如下:
class PrimeRun implements Runnable {
      long minPrime;
       PrimeRun(long minPrime) {
            this.minPrime = minPrime;
      }
      public void run() {
            // compute primes larger than minPrime
      }
}

PrimeRun p = new PrimeRun(143);
new Thread(p).start();

每个线程有一个名称方便识别,多个线程可能有相同的名字。新创建的线程如果没有指定名称,会自动生成一个。

线程状态

线程状态指的是线程在JVM中的状态,每个时刻仅对应以下状态中的一个。

  1. NEW——新建状态。线程未start之前处于该状态。
  2. RUNNABLE——可运行状态。此时线程可以被JVM执行,但可能在等待操作系统的资源,比如处理器。
  3. BLOCKED——阻塞状态。此时线程等待进入synchronized代码块/方法,或者调用Object.wait()方法后,需要重入synchronized代码块/方法。
  4. WAITING——等待状态。以下三种方式可以触发进入该状态:
  • 调用Object.wait()无参方法
  • 调用Thread.join()无参方法
  • 调用LockSupoort.park()方法

处于等待状态的线程会等待其他线程执行特定的操作。比如,调用Object.wait()方法的线程会等待其他线程调用Object#notify()或者Object.notifyAll()方法,调用Thread.join()的线程会等待其他线程终结。

  1. TIMED_WAITING——和WAITING状态的区别是会有一个等待时间。调用以下方法触发进入该状态:
  • Thread.sleep
  • Object.wait(long)
  • Thread.join(long)
  • LockSupport.parkNanos
  • LockSupport.parkUntil
  1. TERMINATED——死亡状态。线程执行完毕。

以上为源码中线程的六种状态。而通常我们所说的线程状态有五种:新建、就绪、运行、阻塞、死亡。对应关系为:

  • 新建——NEW
  • 就绪、运行——RUNNABLE
  • 阻塞——BLOCKED、WAITING、TIMED_WAITING
  • 死亡——TERMINATED

源码中之所以没有区分就绪和运行状态,我个人的观点是CPU执行任务并不是连续的,在上面讲并发的时候提到,A、B两个任务是ABABAB这样交替执行的,所以对于A或者B来说会在就绪和运行之间反复切换,不好区分,所以统称为RUNNABLE。(如有误,欢迎指正)

线程的方法

public synchronized void start()

    public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        // Android-changed: throw if 'started' is true
        if (threadStatus != 0 || started)
            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 {
            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 */
            }
        }
    }

start之前,线程处于NEW状态,start之后就处于RUNNABLE状态。
start方法使JVM开始调用线程的run方法。需要注意的是,start只能调用一次。再次调用会抛出IllegalThreadStateException异常。

public void interrupt()

    public void interrupt() {
        if (this != Thread.currentThread())
            checkAccess();

        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                nativeInterrupt();
                b.interrupt(this);
                return;
            }
        }
        nativeInterrupt();
    }

中断该线程。如果调用该方法所在的线程不是所要中断的线程,会通过checkAccess()方法判断是否有权限,可能抛出SecurityException异常。

  1. 如果所要中断的线程通过调用Object.waitThread.joinThread.sleep被阻塞,interrupt方法将会清除中断状态,并抛出InterruptedException
  2. 如果所要中断的线程通过java.nio.channels.InterruptibleChannel被阻塞在一个I/O操作中,那么这个channel会被关闭,这个线程会被设置中断状态,并且收到java.nio.channels.ClosedByInterruptException异常。
  3. 如果所要中断的线程在java.nio.channels.Selector中被阻塞,那么这个线程会被设置中断状态,并且从selection操作中立刻返回,可能是一个0值,就像调用了java.nio.channels.Selector#wakeup方法。

如果以上场景都不是,那么这个线程将会被设置中断状态。

public static native boolean interrupted()

判断该线程是否处于中断状态。该方法会在调用后清除中断状态,所以如果连续调用两次,第二次必定返回false,除非两次调用之间被再次中断。

public native boolean isInterrupted()

判断该线程是否处于中断状态。不会影响中断状态。

public final boolean isAlive()

判断线程是否存活。线程存活是指线程被start,并且没有死亡。即除NEW和TERMINATED外,其他状态均为存活。

public final void join() throws InterruptedException

    public final void join(long millis) throws InterruptedException {
        synchronized(lock) {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                lock.wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                lock.wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
        }
    }

如A、B两个线程,在A的run方法中调用B.join(),则A进入阻塞状态(lock.wait),直到B死亡(!isAlive()),A才会重新进入RUNNABLE状态。

public static native void yield()

该方法会使调用线程由运行状态进入就绪状态,即暂时让出CPU资源给其他任务执行。

该方法告知调度程序当前线程可以让出处理器资源,调度程序可以自由选择是否忽略这个通知。

public static void sleep(long millis) throws InterruptedException

使当前线程暂停一段时间,不会释放所拥有的资源,如对象锁等。线程由运行态进入阻塞态,暂停时间到,重新进入就绪态。

UncaughtExceptionHandler接口

可以通过public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh)为线程设置一个UncaughtExceptionHandler,在线程因未捕获的异常导致死亡时,JVM会调用该接口的void uncaughtException(Thread t, Throwable e)方法。

在Android应用开发过程中,如果需要自定义异常处理,可以通过非静态方法setUncaughtExceptionHandler()设置单个线程的UncaughtExceptionHandler(记为A),或者通过静态方法setDefaultUncaughtExceptionHandler()设置所有线程的默认UncaughtExceptionHandler(记为B)。只有在A不存在时,才会调用B。


Thread类有个与ThreadLocal相关的成员变量ThreadLocal.ThreadLocalMap threadLocals = null。关于ThreadLocal的知识可以参考深入学习理解java-ThreadLocal。
这里总结一下:

  1. ThreadLocal不是为了解决多线程同步的问题,而是为了记录各个线程的状态。比如可以用ThreadLocal记录线程的id。
  2. 每个Thread都维护了一个Map,ThreadLocal是这个Map的Key。ThreadLocal的get set方法本质是在操作当前线程的Map。

下面的例子是源码中的。
ThreadId类为每个线程提供一个唯一的ID值。不同的线程调用get()方法返回值不同,同一个线程调用get()方法始终返回同一个值。

 import java.util.concurrent.atomic.AtomicInteger;

 public class ThreadId {
     // Atomic integer containing the next thread ID to be assigned
     private static final AtomicInteger nextId = new AtomicInteger(0);

     // Thread local variable containing each thread's ID
     private static final ThreadLocal threadId =
         new ThreadLocal() {
             @Override 
             protected Integer initialValue() {
                 return nextId.getAndIncrement();
         }
     };

     // Returns the current thread's unique ID, assigning it if necessary
     public static int get() {
         return threadId.get();
     }
 }

你可能感兴趣的:(Android线程学习笔记)