Thread原理

发布时间 2023-04-03 10:32:45作者: 无虑的小猪

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源码分析完成。