Thread原理
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。
线程组内部结构:

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

通过这两个属性,说明线程组中可嵌套线程组。详细图如下:
下面来看看线程组维护组内线程的方法:
// 启动时,线程组中添加线程 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); } }
销毁线程组,也需要将线程组内持有的线程组数组内的对象销毁。并将当前线程组从父线程组中移除。
7、线程执行源码分析
Thread的继承关系如下:

Thread实现了Runable接口,重写了run方法。如果使用Thread的有参构造方法创建Thread对象,在线程启动后,CPU调度该线程,会执行重写的run方法。源码分析 详情如下:
1 // Runnable属性 2 private Runnable target; 3 4 // Thread有参构造函数 5 public Thread(Runnable target) { 6 init(null, target, "Thread-" + nextThreadNum(), 0); 7 } 8 9 // 初始化target 属性 10 private void init(ThreadGroup g, Runnable target, String name, 11 long stackSize, AccessControlContext acc, 12 // ... 13 this.target = target; 14 // ... 15 } 16 17 // CPU调度执行run()方法,实际执行的是target变量的run方法 18 public void run() { 19 if (target != null) { 20 target.run(); 21 } 22 }
至此,Thread源码分析完成。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)