美文网首页
Android线程学习笔记

Android线程学习笔记

作者: sollian | 来源:发表于2018-04-24 17:11 被阅读28次

    前言

    线程对于每个学习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<Integer> threadId =
             new ThreadLocal<Integer>() {
                 @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线程学习笔记

          本文链接:https://www.haomeiwen.com/subject/dtndlftx.html