Android基础探究之再探Thread

对android中的thread实现原理进行深入的学习

发布日期 2019-01-13

前言

对常用的Thread做一次源码剖析,更好的去理解和使用它,看完之后你会明白的几个问题:

  1. 调用start发生了什么?多次调用start会怎么样?
  2. start和run方法的区别
  3. join和sleep的区别
  4. 什么是守护进程

一、创建使用

1. 初始化

Thread构造函数

内部调用— init方法

java.lang.Thread#Thread()
java.lang.Thread#Thread(java.lang.Runnable)
java.lang.Thread#Thread(java.lang.ThreadGroup, java.lang.Runnable)
java.lang.Thread#Thread(java.lang.String)
java.lang.Thread#Thread(java.lang.ThreadGroup, java.lang.String)
java.lang.Thread#Thread(java.lang.ThreadGroup, java.lang.String, int, boolean)
java.lang.Thread#Thread(java.lang.Runnable, java.lang.String)
java.lang.Thread#Thread(java.lang.ThreadGroup, java.lang.Runnable, java.lang.String)
java.lang.Thread#Thread(java.lang.ThreadGroup, java.lang.Runnable, java.lang.String, long)

init()方法指定四个参数:ThreadGroup,任务runable,线程名称,栈大小,其中部分参数初始值都是继承父线程的属性

private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
    Thread parent = currentThread();//获取创建thread的线程
    if (g == null) {
        g = parent.getThreadGroup();
    }

    g.addUnstarted();//在ThreadGroup中标记增加了一个未启动的线程,里面操作很简单,nUnstartedThreads++;
    this.group = g;

    this.target = target;
    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();
}

...

//线程 tid递增一个
private static synchronized long nextThreadID() {
    return ++threadSeqNumber;
}

2. start方法

  • 在Android中,检测到再次调用start线程会抛出IllegalThreadStateException

      public synchronized void start() {
          
          // Android-changed: throw if 'started' is true
          if (threadStatus != 0 || started)
              throw new IllegalThreadStateException();
    
          //还记得上面init方法中,调用addUnstarted时,标记增加了未启动线程
      	//这里调用add方法,将线程添加到系统线程数组,并且将未启动线程数减一,相当于移出
          group.add(this);
    
          started = false;
          try {
              nativeCreate(this, stackSize, daemon);
      		//调用native方法启动线程,如果报错,则直接跳到finally执行,started为false,
      		//启动失败,从group中移除,同时group中未启动线程数++
              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 */
              }
          }
      } 
    

3. run方法

//Thread实现的Runnable接口
class Thread implements Runnable {

	...
	
	//调用传入的Runnable的run方法
	@Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

二、Thread阻塞

1. join方法

join方法用于等待线程执行完成,传入的时间单位为等待的最大时长,里面是一个 while (isAlive())循环函数,当不传入时间参数,则为永久等待直到线程结束,传入时间参数,当时间到达时会结束join方法

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;
            }
        }
    }
}
 public final void join() throws InterruptedException {
    join(0);
}
//等待多少毫秒在加多少纳秒
public final void join(long millis, int nanos)
throws InterruptedException {
    synchronized(lock) {
    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (nanos < 0 || nanos > 999999) {
        throw new IllegalArgumentException(
                            "nanosecond timeout value out of range");
    }

    if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
        millis++;
    }

    join(millis);
    }
}

2. sleep方法

sleep作用是使当前线程睡眠指定时间,其中几个关键点

  • 获取当前调用线程的lock:currentThread().lock;

  • 通过while (true)循环sleep当前线程,并检测睡眠时间达到传输参数时间,break当前循环

      public static void sleep(long millis, int nanos)throws InterruptedException {
          if (millis < 0) {
              throw new IllegalArgumentException("millis < 0: " + millis);
          }
          if (nanos < 0) {
              throw new IllegalArgumentException("nanos < 0: " + nanos);
          }
          if (nanos > 999999) {
              throw new IllegalArgumentException("nanos > 999999: " + nanos);
          }
    
         	//当睡眠时间为0,先检测线程是否已经中断,是的话抛出异常,否则直接return
          if (millis == 0 && nanos == 0) {
              // ...but we still have to handle being interrupted.
              if (Thread.interrupted()) {
                throw new InterruptedException();
              }
              return;
          }
    
          long start = System.nanoTime();
          long duration = (millis * NANOS_PER_MILLI) + nanos;
    
      	获取当前线程的lock
          Object lock = currentThread().lock;
    
          // Wait may return early, so loop until sleep duration passes.
          synchronized (lock) {
              while (true) {
                  sleep(lock, millis, nanos);
    
                  long now = System.nanoTime();
                  long elapsed = now - start;
    
                  if (elapsed >= duration) {
                      break;
                  }
    
                  duration -= elapsed;
                  start = now;
                  millis = duration / NANOS_PER_MILLI;
                  nanos = (int) (duration % NANOS_PER_MILLI);
              }
          }
      }
      public static void sleep(long millis) throws InterruptedException {
          Thread.sleep(millis, 0);
      }
    
      @FastNative
      private static native void sleep(Object lock, long millis, int nanos)
          throws InterruptedException;
    

3.sleep与join的区别

  1. join里面调用的wait方法,wait方法可以释放锁,而sleep方法是持有锁
  2. join(0)是一直等待线程执行完成,只有这个线程执行完后,才能执行其他线程,中间通过循环lock.wait(delay)实现,它是非静态方法,
  3. sleep是静态方法,通过currentThread获取当前线程的lock,它只能作用当前线程

三、Thread终止

1. stop方法

stop方法已经被弃用,强行调用的话会抛出UnsupportedOperationException异常

 @Deprecated
public final void stop() {
    stop(new ThreadDeath());
}

  @Deprecated
public final void stop(Throwable obj) {
    throw new UnsupportedOperationException();
}

2. interrupt方法

  • interrupt的作用是中断线程,我们经常调用,interrupt的使用有几个注意点

  • 当线程处于wait,sleep,join等方法阻塞状态时,它会清除当前阻塞状态,并抛出InterruptedException异常

  • 在I/O通讯状态中调用interrupt,数据通道会被关闭,并将线程状态标记为中断,并抛出ClosedByInterruptException异常

  • 如果在java.nio.channels.Selector上堵塞,会标记中断状态,并马上返回select方法

  • Lock.lock()方法不会响应中断,Lock.lockInterruptibly()方法则会响应中断并抛出异常,区别在于park()等待被唤醒时lock会继续执行park()来等待锁,而 lockInterruptibly会抛出异常

  • synchronized被唤醒后会尝试获取锁,失败则会通过循环继续park()等待,因此实际上是不会被interrupt()中断的;

  • 一般情况下,抛出异常时,会清空Thread的interrupt状态,在编程时需要注意;

//用来中断的IO通讯对象,在调用interrupt方法后会调用blocker的中断方法
private volatile Interruptible blocker;

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

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

四、线程的状态

public enum State {
    NEW,
    RUNNABLE,
    BLOCKED,
    WAITING,
    TIMED_WAITING,
    TERMINATED;
}

public State getState() {
    // get current thread state
    return State.values()[nativeGetStatus(started)];
}
  • NEW:线程创建还未启动时状态
  • RUNNABLE:线程运行状态,包括一些系统资源等待,如:IO等待,CPU时间片切换等
  • BLOCKED:正在等待monitor lock的状态,比如:1. 即将进入synchronized方法或者块前等待获取锁的这个临界时期状态。2.调用wait方法释放锁之后再次进入synchronized方法或者块前的临界状态
  • WAITING:基于上个BLOCKED状态来说,WAITING就是拿到锁了,处于wait过程中的状态,注意它是特指无限期的等待,也就是join()或者wait()等,它是join或者直接wait方法当获取到lock执行后,处于等待notify的WAITING状态。
  • TIMED_WAITING:与上面WAITING相对,WAITING是指无限期的等待,TIMED_WAITING就是有限期的等待状态,包括join(long),wait(long),sleep(long)等。
  • TERMINATED:线程执行完成,run结束的状态

五、总结:

  1. 调用2次start时,看start源码中,里面判断如果当前线程状态和是否启动标记,if (threadStatus != 0 || started),如果已经启动则抛出IllegalThreadStateException异常,可以通过继承Thread类或者实现Runnable去开启线程,这样每次new了新的对象启动线程
  2. start是启动当前Thread线程,Thread实现了Runnable接口的run方法,当线程启动,run方法会被调用,Thread里面的Run会调用传入Runnable Target的run方法,达到实现我们自定义任务的目的。如果没有传入Runnable参数则do nothing
  3. join是等待线程执行完成,方法通过内部一个while(alive)的循环函数去实现wait等待,alive是一直检测线程的存活状态,它相当于,在那个线程执行join,即在哪个线程执行wait,调用的线程对象可以理解为lock对象,即调用了lock.wait(), sleep方法是一直持有锁的状态,同时sleep是静态方法,它通过currentThread获取当前线程的lock,并只能作用当前线程
  4. 守护线程意思是后台服务线程,比如垃圾回收线程,要理解它就知道另一个用户线程,用户线程是维持程序运行状态,或者说jvm存活的线程,如果用户线程都跑完了,那么不管守护线程是否运行,程序和jvm都会退出,当然此时,守护线程也会退出,由此可以看出守护线程和用户线程对于程序运行的相关性。由上述线程的init方法可以看出,子线程的创建会继承一些默认参数,包含是否为守护线程,它是低级别的线程,不依赖于终端,但是依赖于系统,与系统“同生共死”。