首页 > 其他分享 >Thread原理

Thread原理

时间:2023-04-03 10:35:21浏览次数:36  
标签:当前 Thread 启动 线程 数组 原理 null

1、什么是线程

  线程是CPU调度执行的基本单元。

  JVM允许在一个程序中同时执行多个线程,在Java中,用java.lang.Thread这个类来表示线程。

  线程有优先级,高优先级的线程往往会比低优先级的线程先执行。

  守护线程(daemon Thread),主线程执行完,守护线程跟着结束。

2、Thread使用

  有两种方式创建执行的线程,一种是继承Thread,一种是实现Runable接口。

 1 public class TestThread {
 2     public static void main(String[] args) {
 3         // 继承Thread类
 4         MyThread myThread = new MyThread();
 5         myThread.start();
 6 
 7         // 实现Runable
 8         MyRunable myRunable = new MyRunable();
 9         Thread thread = new Thread(myRunable);
10         thread.start();
11     }
12 }
13 
14 // 继承Thread
15 class MyThread extends Thread {
16     @Override
17     public void run() {
18         System.out.println(Thread.currentThread().getName() + " MyThread...");
19     }
20 }
21 
22 // 实现Runable
23 class MyRunable implements Runnable {
24     @Override
25     public void run() {
26         System.out.println(Thread.currentThread().getName() + " MyRunable...");
27     }
28 }

  线程的启动调用Thread的start()方法。

3、Thread源码分析

1、Thread的优先级  

 

2、Thread的状态

2.1、线程的状态

  线程的状态定义在Thread中的State枚举中,有NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED六种状态。

  

 NEW:新建状态,线程创建完成,没有执行start()方法时的状态。

 RUNNABLE:运行状态,正在执行的线程。

 BLOCKED:阻塞状态,等待获取锁资源,等待进入synchorized代码块或方法。

 WAITING:等待状态,正在执行的线程执行Object#wait、Thread#join()、LockSupport#park()等方法,线程进入等待状态。

 TIMED_WAITING:超时等待状态,正在执行的线程执行Thread#sleep、Object#wait(long)、LockSupport.parkNanos等方法,线程进入超时等待状态。

 TERMINATED:线程执行完成,变成终止状态

2.2、线程的状态转换

 

3、Thread的构造函数

  Thread的构造利用了方法的重载,详情如下:

// 线程序号器,用来生成线程id
private static long threadSeqNumber;

// 默认的构造函数,设置默认的线程名称、栈大小
public Thread() {
    init(null, null, "Thread-" + nextThreadNum(), 0);
}

// Thread初始化,设置是否维护内部线程变量inheritableThreadLocals标识
private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize) {
    init(g, target, name, stackSize, null, true);
}

// 实际执行的初始化方法
private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
    // 线程名称非空校验
    if (name == null) {
        throw new NullPointerException("name cannot be null");
    }
    this.name = name;
    
    // 获取当前正在执行的线程作为父线程
    Thread parent = currentThread();
    // 获取安全管理器
    SecurityManager security = System.getSecurityManager();
    // 线程必须属于某个线程组,线程组为null,
    // 优先获取安全管理器的线程组,若安全管理器的线程组为空,将要创建的线程与当前正在执行线程的设置在同一线程组内
    if (g == null) {
        if (security != null) {
            g = security.getThreadGroup();
        }
        if (g == null) {
            g = parent.getThreadGroup();
        }
    }
    // ...
    // 线程组中未启动的线程数量 + 1
    g.addUnstarted();
    
    // 当前线程的线程组设置
    this.group = g;
    // 守护线程标识设置
    this.daemon = parent.isDaemon();
    // 优先级设置
    this.priority = parent.getPriority();
    // 获取类加载器
    if (security == null || isCCLOverridden(parent.getClass()))
        this.contextClassLoader = parent.getContextClassLoader();
    else
        this.contextClassLoader = parent.contextClassLoader;
        
    // 设置优先级
    setPriority(priority);
    // 初始化inheritableThreadLocals对象
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    
    // 线程栈大小设置
    this.stackSize = stackSize;

    // 线程id设置
    tid = nextThreadID();
}

// 线程id
private static synchronized long nextThreadID() {
    return ++threadSeqNumber;
}

  Thread必须属于某一线程组ThreadGroup,线程组中维护的有组内未启动的线程数nUnstartedThreads,创建的线程沿用主线程的优先级。在Thread内部维护线程序号器threadSeqNumber,来生成线程id。Thread默认维护ThreadLocalMap类型的inheritableThreadLocals。

4、线程组

  线程组ThreadGroup维护主线程及在主线程执行过程中创建的子线程,在ThreadGroup中维护已启动线程的数组,持有当前线程组中已经启动线程数量nthreads,未启动线程数量nUnstartedThreads。

 

   线程组内部结构:

 0

  线程组内部持有线程组数组:

   0

  通过这两个属性,说明线程组中可嵌套线程组。详细图如下:

 

  下面来看看线程组维护组内线程的方法:

// 启动时,线程组中添加线程
void add(Thread t) {
    // 未保证原子操作,加锁
    synchronized (this) {
        // 线程组被销毁,抛出异常
        if (destroyed) {
            throw new IllegalThreadStateException();
        }
        // 存储启动线程的数组为null,初始化数组
        if (threads == null) {
            threads = new Thread[4];
        // 数组容量满了,扩容,2倍
        } else if (nthreads == threads.length) {
            threads = Arrays.copyOf(threads, nthreads * 2);
        }
        // 将正在启动的线程添加到数组中
        threads[nthreads] = t;
        // 线程组中启动线程数 + 1
        nthreads++;
        // 线程组中未启动线程数 -1
        nUnstartedThreads--;
    }
}

// 启动失败时处理
void threadStartFailed(Thread t) {
    synchronized(this) {
        // 移除数组中当前启动失败的线程
        remove(t);
        // 未启动线程 + 1
        nUnstartedThreads++;
    }
}

// 删除启动的线层
private void remove(Thread t) {
    synchronized (this) {
        // 线程组销毁,直接返回
        if (destroyed) {
            return;
        }
        // 遍历线程数组
        for (int i = 0 ; i < nthreads ; i++) {
            // 遍历当前线程数组
            if (threads[i] == t) {
                // 线程组中启动线程数 - 1,复制线程数组(移动数组实现)
                System.arraycopy(threads, i + 1, threads, i, --nthreads - i);
                // 启动失败的线程从线程数组中移除
                threads[nthreads] = null;
                break;
            }
        }
    }
}

  为什么需要这个线程组?个人理解,为了更好的管理创建的线程,比如线程的唤醒,线程挂起等。

5、线程启动 - start源码分析

  启动线程,Thread#start()核心代码:

// 启动线程,等待CPU调度,JVM会执行Thread的run()方法
public synchronized void start() {

    // 只有 NEW 新建状态的才能执行start方法
    if (threadStatus != 0)
        throw new IllegalThreadStateException();
    //  这个线程设置到线程组的启动线程列表threads中,同时未启动的线程数减1
    group.add(this);
    // 启动标识
    boolean started = false;
    try {
        // 调用native方法
        start0();
        // 线程已启动
        started = true;
    } finally {
        // 线程启动失败
        if (!started) {
            // 将当前线程从线程组的启动线程列表中移除,同时未启动的线程数加1
            group.threadStartFailed(this);
        }
    }
}

  启动的线程状态不为为NEW状态,抛出异常。

  将当前正在启动的线程添加到线程组ThreadGroup的线程数组中,调整当前线程组启动线程、新建状态线程的数量,若启动失败,执行ThreadGroup的threadStartFailed方法,将上述操作回滚。

  线程启动,实际上是调用本地方法start0,执行完毕,等待cpu调度,JVM执行重写的run方法。

6、线程退出 - exit源码分析

  线程退出,Thread#exit() 核心代码:

private void exit() {
    // 当前线程组不为null
    if (group != null) {
        // 线程组终止当前线程
        group.threadTerminated(this);
        // 当前线程所属线程组设置为null
        group = null;
    }
    // 将Thread的属性设置为null
    target = null;
    threadLocals = null;
    inheritableThreadLocals = null;
    inheritedAccessControlContext = null;
    blocker = null;
    uncaughtExceptionHandler = null;
}

  线程退出exit方法,将当前线程从所属线程组的线程数组中移除,将当前线程所有的属性设置为null。线程组结束当前线程后,如果线程组内无处于运行状态的线程,执行Object的notifyAll方法唤醒处于等待状态的线程获取锁资源。当线程组为守护线程,并且组内无线程资源要运行,销毁当前线程组。

  ThreadGroup#threadTerminated() 核心代码:

void threadTerminated(Thread t) {
    // 加锁处理
    synchronized (this) {
        // 从线程数组中移除当前线程
        remove(t);
        // 当前线程数组中已经没有处于运行状态的线程,唤醒所有线程
        if (nthreads == 0) {
            notifyAll();
        }
        // 守护线程 并且 线程组中的都执行结束,线程组内已经没有可运行的ThreadGroup
        if (daemon && (nthreads == 0) &&
            (nUnstartedThreads == 0) && (ngroups == 0))
        {
            // 销毁当前线程组
            destroy();
        }
    }
}

// 销毁当前线程组
public final void destroy() {
    int ngroupsSnapshot;
    ThreadGroup[] groupsSnapshot;
    synchronized (this) {
        checkAccess();
        // 当前线程组已经被销毁 || 当前线程组有正在运行的线程
        if (destroyed || (nthreads > 0)) {
            throw new IllegalThreadStateException();
        }
        // 当前线程组内持有的线程组数组
        ngroupsSnapshot = ngroups;
        // 持有线程组数组
        if (groups != null) {
            // 复制线程组数组到ngroupsSnapshot对象
            groupsSnapshot = Arrays.copyOf(groups, ngroupsSnapshot);
        // 当前线程组内没有线程组数组
        } else 
            groupsSnapshot = null;
        }
        // 当前线程组的父线程组不为null
        if (parent != null) {
            // 设置当前线程组销毁状态
            destroyed = true;
            // 设置当前线程组持有线程组数量
            ngroups = 0;
            // 设置当前线程组持有线程组数组
            groups = null;
            // 设置当前线程组运行的线程数
            nthreads = 0;
            // 设置当前线程组运行线程的数组
            threads = null;
        }
    }
    // 销毁当前线程组内包含的线程组对象
    for (int i = 0 ; i < ngroupsSnapshot ; i += 1) {
        groupsSnapshot[i].destroy();
    }
    // 将当前线程组从父线程组的数组中
    if (parent != null) {
        parent.remove(this);
    }
}

  销毁线程组,也需要将线程组内持有的线程组数组内的对象销毁。并将当前线程组从父线程组中移除。至此,Thread源码分析完成。

 

标签:当前,Thread,启动,线程,数组,原理,null
From: https://www.cnblogs.com/RunningSnails/p/17282277.html

相关文章

  • 【原理揭秘】Vite 是怎么兼容老旧浏览器的?你以为仅仅依靠 Babel?
    作者:京东科技孙凯一、前言对前端开发者来说,Vite应该不算陌生了,它是一款基于nobundle和bundleless思想诞生的前端开发与构建工具,官网对它的概括和期待只有一句话:“下一代的前端工具链”。Vite最早的版本由尤雨溪发布于3年前,经历了3年多的发展,Vite也已逐渐迭代成熟,它的稳定性......
  • Vue2依赖收集原理
    观察者模式定义了对象间一对多的依赖关系。即被观察者状态发生变动时,所有依赖于它的观察者都会得到通知并自动更新。解决了主体对象和观察者之间功能的耦合。Vue中基于Observer、Dep、Watcher三个类实现了观察者模式Observer类负责数据劫持,访问数据时,调用dep.depend()进行依......
  • Zookeeper Session原理
    我们的Leader已经选举出来了,那接下来该干什么呢?你或许很快能想到,那就是数据同步。通俗地讲,就是Leader选出来了,各自的角色都确定好了,那Follower和Observer自然要同Leader建立连接同步数据,这里就引入了ZooKeeper的另一个核心知识:Session。一、什么是Session?Session......
  • 【深度学习时间序列预测案例】零基础入门经典深度学习时间序列预测项目实战(附代码+数
    前言......
  • Elasticsearch 学习- 分片原理,倒排索引,文档搜索,文档分析,内置分析器,指定分词器,IK分词器
    Elasticsearch学习-分片原理4.6分片原理​ 分片是Elasticsearch最小的工作单元。但是究竟什么是一个分片,它是如何工作的?​ 传统的数据库每个字段存储单个值,但这对全文检索并不够。文本字段中的每个单词需要被搜索,对数据库意味着需要单个字段有索引多值的能力。最好的支......
  • “通信原理研讨会”之后的一些思考
    大家好,我是小枣君。上周六,东南大学联合人民邮电出版社,在北京共同主办了一场“通信原理”课程研讨会。该研讨会邀请了包括北邮、国防科大、成电、西电在内的国内众多高校通信原理课程一线教师,共同参与教学内容研讨、教学经验交流。研讨会的演讲嘉宾,都是国内顶尖高校的顶级专家,相信很......
  • Thanos工作原理及组件简介
    Thanos简介Thanos是一个「开源的,高可用的Prometheus系统,具有长期存储能力」。很多知名公司都在使用Thanos,也是CNCF孵化项目的一部分。Thanos的一个主要特点就是通过使用对象存储(比如S3)可以允许“无限”存储空间。对象存储可以是每个云提供商提供的对象存储也可以是c......
  • 生活中的常识与原理001-天文-基础
    相关英文词汇:latitude/ˈlætɪtjuːd/,纬度,记忆时可以与ladder相关联,因为纬度是标识南北的线,就像梯子的格子一样。赤道为0度,北极为90度。注意与高度altitude相区别。longitude/ˈlɔndʒɪtjuːd/,经度。从南到北,与赤道垂直。0度经线贯穿英国格林尼治天文台。经度和纬度可以标......
  • 生活中的常识与原理00000-缘起
    在2023年的春分前后,突然对于太阳在空中的运行轨迹有了兴趣,发现太阳东升西落这个让大多数人熟视无睹的现象里,也有很多误解,而且想真正弄清背后的原理还真的很费脑筋。比如在不同城市的人们看到太阳在空中的运动轨迹是一样的吗?一年中的每一天都是一样的吗?有什么规律?而且我也逐渐感......
  • java 中required_通过实例学习Spring @Required注释原理
    @Required注释应用于bean属性的setter方法,它表明受影响的bean属性在配置时必须放在XML配置文件中,否则容器就会抛出一个BeanInitializationException异常。下面显示的是一个使用@Required注释的示例。示例:让我们使EclipseIDE处于工作状态,请按照下列步骤创建一个......