前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JDK的线程Thread核心源码解析

JDK的线程Thread核心源码解析

作者头像
JavaEdge
发布2021-02-22 14:15:51
2270
发布2021-02-22 14:15:51
举报
文章被收录于专栏:JavaEdge

1 类注释

程序中执行的线程。JVM允许应用程序拥有多个并发运行的执行线程。

每个线程都有一个优先级。优先级高的线程优先于优先级低的线程执行。每个线程可能被标记为守护线程,也可能不被标记为守护线程。

当在某个线程中运行的代码创建一个新 Thread 对象时,新线程的优先级最初设置为创建线程的优先级,并且只有在创建线程是一个守护线程时,新线程才是守护线程。

当JVM启动时,通常有一个非守护的线程(它通常调用某个指定类的main方法)。JVM 继续执行线程,直到发生以下任何一种情况时停止:

  • Runtime 类的 exit 方法已被调用,且安全管理器已允许执行退出操作(比如调用 Thread.interrupt 方法)
  • 不是守护线程的所有线程都已死亡,要么从对 run 方法的调用返回,要么抛出一个在 run 方法之外传播的异常

每个线程都有名字,多个线程可能具有相同的名字,Thread 有的构造器如果没有指定名字,会自动生成一个名字。

2 线程的基本概念

2.1 线程的状态

源码中一共枚举了六种线程状态

  • 线程的状态机

2.1.1 状态机说明

  • NEW 表示线程创建成功,但还没有运行,在 new Thread 后,没有 start 前,线程的状态都是 NEW
  • 当调用start(),进入RUNNABLE,当前线程sleep()结束,其他线程join()结束,等待用户输入完毕,某个线程拿到对象锁,这些线程也将进入RUNNABLE
  • 当线程运行完成、被打断、被中止,状态都会从 RUNNABLE 变成 TERMINATED
  • 如果线程正好在等待获得 monitor lock 锁,比如在等待进入 synchronized 修饰的代码块或方法时,会从 RUNNABLE 转至 BLOCKED
  • WAITINGTIMED_WAITING 类似,都表示在遇到 Object#waitThread#joinLockSupport#park 这些方法时,线程就会等待另一个线程执行完特定的动作之后,才能结束等待,只不过 TIMED_WAITING 是带有等待时间的

2.2 线程的优先级

优先级代表线程执行的机会的大小,优先级高的可能先执行,低的可能后执行,在 Java 源码中,优先级从低到高分别是 1 到 10,线程默认 new 出来的优先级都是 5,源码如下:

分别为最低,普通(默认优先级),最大优先级

2.3 守护线程

创建的线程默认都是非守护线程。

  • 创建守护线程时,需要将 Thread 的 daemon 属性设置成 true

守护线程的优先级很低,当 JVM 退出时,是不关心有无守护线程的,即使还有很多守护线程,JVM 仍然会退出。 在工作中,我们可能会写一些工具做一些监控的工作,这时我们都是用守护线程去做,这样即使监控抛出异常,也不会影响到业务主线程,所以 JVM 也无需关注监控是否正在运行,该退出就退出,所以对业务不会产生任何影响。

3 线程的初始化的两种方式

无返回值的线程主要有两种初始化方式:

3.1 继承 Thread

看下 start 方法的源码:

代码语言:javascript
复制
    public synchronized void start() {
        /**
		 * 对于由VM创建/设置的主方法线程或“系统”组线程,不调用此方法。
		 * 将来添加到此方法中的任何新功能可能也必须添加到VM中。
		 * 
         * 零状态值对应于状态“NEW”。
         * 因此,如果没有初始化,直接抛异常
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* 
         * 通知组此线程即将start,以便可以将其添加到组的线程列表中
         * 并且可以减少组的unstarted线程的计数
		 */
        group.add(this);

		// started 是个标识符,在处理一系列相关操作时,经常这么设计
        // 操作执行前前标识符是 false,执行完成后变成 true
        boolean started = false;
        try {
        	// 创建一个新的线程,执行完成后,新的线程已经在运行了,即 target 的内容已在运行
            start0();
            // 这里执行的还是 main 线程
            started = true;
        } finally {
            try {
                // 若失败,将线程从线程组中移除
                if (!started) {
                    group.threadStartFailed(this);
                }
            // Throwable 可以捕捉一些 Exception 捕捉不到的异常,比如子线程抛出的异常    
            } catch (Throwable ignore) {
                /* 
                 * 什么也不做。
                 * 如果start0抛出一个Throwable,那么它将被传递到调用堆栈
                 */
            }
        }
    }
    
    // 开启新线程使用的是 native 方法
	private native void start0();

注意上面提到的的threadStatus变量 用于工具的Java线程状态,初始化以指示线程“尚未启动”

3.2 实现 Runnable 接口

这是实现 Runnable 的接口,并作为 Thread 构造器的入参,调用时我们使用了两种方式,可以根据实际情况择一而终

  • 使用 start 会开启子线程来执行 run 里面的内容
  • 使用 run 方法执行的还是主线程。

我们来看下 run 方法的源码:

  • 不会新起线程,target 是 Runnable

源码中的 target 就是在 new Thread 时,赋值的 Runnable。

4 线程的初始化

线程初始化的源码有点长,我们只看比较重要的代码 (不重要的被我删掉了),如下:

代码语言:javascript
复制
// 无参构造器,线程名字自动生成
public Thread() {
    init(null, null, "Thread-" + nextThreadNum(), 0);
}
// g 代表线程组,线程组可以对组内的线程进行批量的操作,比如批量的打断 interrupt
// target 是我们要运行的对象
// name 我们可以自己传,如果不传默认是 "Thread-" + nextThreadNum(),nextThreadNum 方法返回的是自增的数字
// stackSize 可以设置堆栈的大小
private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc) {
    if (name == null) {
        throw new NullPointerException("name cannot be null");
    }

    this.name = name.toCharArray();
    // 当前线程作为父线程
    Thread parent = currentThread();
    this.group = g;
    // 子线程会继承父线程的守护属性
    this.daemon = parent.isDaemon();
    // 子线程继承父线程的优先级属性
    this.priority = parent.getPriority();
    // classLoader
    if (security == null || isCCLOverridden(parent.getClass()))
        this.contextClassLoader = parent.getContextClassLoader();
    else
        this.contextClassLoader = parent.contextClassLoader;
    this.inheritedAccessControlContext =
            acc != null ? acc : AccessController.getContext();
    this.target = target;
    setPriority(priority);
    // 当父线程的 inheritableThreadLocals 的属性值不为空时
    // 会把 inheritableThreadLocals 里面的值全部传递给子线程
    if (parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    this.stackSize = stackSize;
    /* Set thread ID */
    // 线程 id 自增
    tid = nextThreadID();
}

从初始化源码中可以看到,很多属性,子线程都是直接继承父线程的,包括优先性、守护线程、inheritableThreadLocals 里面的值等等。

5 线程其他操作

5.1 join

join 的意思就是当前线程等待另一个线程执行完成之后,才能继续操作,我们写了一个 demo,如下:

代码语言:javascript
复制
@Test
public void join() throws Exception {
  Thread main = Thread.currentThread();
  log.info("{} is run。",main.getName());
  Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
      log.info("{} begin run",Thread.currentThread().getName());
      try {
        Thread.sleep(30000L);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      log.info("{} end run",Thread.currentThread().getName());
    }
  });
  // 开一个子线程去执行
  thread.start();
  // 当前主线程等待子线程执行完成之后再执行
  thread.join();
  log.info("{} is end", Thread.currentThread());
}

执行的结果,就是主线程在执行 thread.join (); 代码后会停住,会等待子线程沉睡 30 秒后再执行,这里的 join 的作用就是让主线程等待子线程执行完成,我们画一个图示意一下:

从图中可以看出,主线程一直等待子线程沉睡 30s 后才继续执行,在等待期间,主线程的状态也是 TIMED_WAITING。

5.2 yield

  • yield 是个 native 方法,源码如下:

令当前线程做出让步,放弃当前 cpu,让 cpu 重新选择线程,避免线程长时占用 cpu。

在写 while 死循环时,预计短时间内 while 死循环可结束的话,可在其中使用 yield 方法,防止 cpu 一直被占用。

让步不是绝不执行,重新竞争时,cpu 也有可能还重新选中自己。

5.3 sleep

  • sleep 也是 native 方法,可以接受毫秒的一个入参
  • 也可以接受毫秒和纳秒的两个入参

表示当前线程会沉睡多久,沉睡时不会释放锁资源,所以沉睡时,其它线程是无法得到锁的。

接受毫秒和纳秒两个入参时,如果给定纳秒大于等于 0.5 毫秒,算一个毫秒,否则不算。

5.4 interrupt

表示中断当前运行的线程,比如:

Object#wait ()、Thread#join ()、Thread#sleep (long) 这些方法运行后,线程的状态是 WAITINGTIMED_WAITING,这时候打断这些线程,就会抛出 InterruptedException 异常,使线程的状态直接到 TERMINATED; 如果 I/O 操作被阻塞了,我们主动打断当前线程,连接会被关闭,并抛ClosedByInterruptException

举一个例子

来说明如何打断 WAITING 的线程,代码如下:

代码语言:javascript
复制
@Test
public void testInterrupt() throws InterruptedException {
  Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
      log.info("{} begin run",Thread.currentThread().getName());
      try {
        log.info("子线程开始沉睡 30 s");
        Thread.sleep(30000L);
      } catch (InterruptedException e) {
        log.info("子线程被打断");
        e.printStackTrace();
      }
      log.info("{} end run",Thread.currentThread().getName());
    }
  });
  // 开一个子线程去执行
  thread.start();
  Thread.sleep(1000L);
  log.info("主线程等待 1s 后,发现子线程还没有运行成功,打断子线程");
  thread.interrupt();
}

例子主要说的是,主线程会等待子线程执行 1s,如果 1s 内子线程还没有执行完,就会打断子线程,子线程被打断后,会抛出 InterruptedException 异常,执行结束,运行的结果如下图:

6 总结

本文主要介绍了线程的一些常用概念、状态、初始化方式和操作,这些知识是工作及面试中必备的,也是深入多线程编程的基础。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020/02/02 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 类注释
  • 2 线程的基本概念
    • 2.1 线程的状态
      • 2.1.1 状态机说明
    • 2.2 线程的优先级
      • 2.3 守护线程
      • 3 线程的初始化的两种方式
        • 3.1 继承 Thread
          • 3.2 实现 Runnable 接口
          • 4 线程的初始化
          • 5 线程其他操作
            • 5.1 join
              • 5.2 yield
                • 5.3 sleep
                  • 5.4 interrupt
                    • 举一个例子
                • 6 总结
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档