Thread源码剖析

对于线程Thread类的使用,可以说是java语言必备,但你是否真正意义上去剖析过他的内部结构,本文从概述的几个问题出发,一起进行源码阅读(本文基于Android-27中的Thread源码

概述

对常用的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方法

部分内容引用一篇很详细的文章,戳-->《Java线程源码解析之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结束的状态
    image

五、总结:回答上述问题

  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方法可以看出,子线程的创建会继承一些默认参数,包含是否为守护线程,它是低级别的线程,不依赖于终端,但是依赖于系统,与系统“同生共死”。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容

  • 进程和线程 进程 所有运行中的任务通常对应一个进程,当一个程序进入内存运行时,即变成一个进程.进程是处于运行过程中...
    胜浩_ae28阅读 5,091评论 0 23
  • 进程和线程 进程 所有运行中的任务通常对应一个进程,当一个程序进入内存运行时,即变成一个进程.进程是处于运行过程中...
    小徐andorid阅读 2,800评论 3 53
  • 前言 昨天已经写了: 多线程三分钟就可以入个门了! 如果没看的同学建议先去阅读一遍哦~ 在写文章之前通读了一遍《J...
    Java3y阅读 982评论 1 10
  • Java多线程学习 [-] 一扩展javalangThread类 二实现javalangRunnable接口 三T...
    影驰阅读 2,952评论 1 18
  • 本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。 首先讲...
    李欣阳阅读 2,444评论 1 15