Thread 源码解析

Thread 源码解析

类注释

  1. 每个线程都有优先级,高优先级的线程可能会优先执行;
  2. 父线程创建子线程后,优先级、是否是守护线程等属性父子线程是一致的;
  3. JVM 启动时,通常都启动 main 非守护线程,以下任意一个情况发生时,线程就会停止:
    1. 退出方法被调用,并且安全机制允许这么做(比如调用 Thread.interrupt 方法)
    2. 所有非守护线程都消亡,或者从允许的方法正常返回,或者允许的方法抛出了异常;
  4. 每个线程都有名字,多个线程可能具有相同的名字,Thread 有的构造器如果没有指定名字,会自动生成一个名字

线程的基本概念

线程的状态

img

初始状态(NEW)

NEW 表示线程创建成功,但没有运行,在 new Thread 之后,没有 start 之前,线程的状态都是 NEW;

就绪状态

Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为 RUNNABLE

READY
  • 就绪状态只是表示此线程能被运行,调度程序没有挑选到,就一直是就绪状态
  • 调用线程的 start 方法,线程进入就绪状态
  • 当前线程 sleep 方法结束、其他线程 join 结束、等待用户输入完毕、某个线程拿到对象锁,这些线程也将进入就绪状态
  • 当前线程时间片用完了,调用当前线程的 yield 方法,当前线程进入就绪状态
  • 锁池的线程拿到对象锁后,进入就绪状态
RUNNING

线程调度程序从可运行池中选择一个线程作为当前线程时,线程所处的状态。这也是线程进入运行状态的唯一一种方式。

阻塞状态(BLOCKED)

线程正好在等待获得 monitor lock 锁,比如在等待进入 synchronized 修饰的代码块或方法时,会从 RUNNABLE 变成 BLOCKED,BLOCKED 表示阻塞的意思;

等待(WAITING)

处于这种状态的线程不会被分配 CPU 执行时间,它们要等待被显示地唤醒,否则会处于无限等待状态

超时等待(TIME_WAITING)

处于这种状态的线程不会被分配 CPU 执行时间,也无需无限期等待被其他线程显示地唤醒,在到达一定时间后他们会自动唤醒

终止状态(TERMINATED)

当线程运行完成时,或者主线程的 main 方法完成时,被打断、被中止,状态都会从 RUNNABLE 变成 TERMINATED,我们就认为它终止了。这个线程对象也许是活的,但是它已经不是一个单独执行的线程。线程一旦终止了,就不能复生。

在一个终止的线程调用 start 方法,会抛出 java.lang.IllegalThreadStateException 异常

优先级

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

java.lang.Thread.java

/**
 * 最低优先级
 * The minimum priority that a thread can have.
 */
public final static int MIN_PRIORITY = 1;

/**
 * 普通优先级,也是默认的
 * The default priority that is assigned to a thread.
 */
public final static int NORM_PRIORITY = 5;

/**
 * 最大优先级
 * The maximum priority that a thread can have.
 */
public final static int MAX_PRIORITY = 10;

守护线程

我们创建的线程都是非守护线程。创建守护线程时,需要将 Thread 的 deamon 属性设置成 true,守护线程的优先级很低,当 JVM 退出时,是不关心有无守护线程的,即使还有很多守护线程, JVM 仍然会退出。

我们在工作中,可能会写一些工具做一些监控的工作,这时我们都是用守护子线程去做,这样即使监控抛出异常,但因为是子线程,所以也不会影响到业务主线程,因为是守护线程,所以 JVM 也无需关注监控是否正在运行,该退出就退出,所以对业务不会产生任何影响。

线程的两种初始化方式

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

  • 继承 Thread,成为 Thread 的子类

    public class MyThread extends Thread{
        @Override
        public void run() {
            System.out.println("do something");
        }
    
        // 调用start方法即可
        public static void main(String[] args) {
            new MyThread().start();
        }
    }
    

    我们来看一下 start 方法的源码:

    public synchronized void start() {
    	// 如果线程没有初始化,抛异常
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
        group.add(this);
    	// started标识符
        boolean started = false;
        try {
            // 这里会创建一个新的线程,执行完成之后,新的线程已经在运行了
            // 动作发生之前标识符是 false,发生完成之后变成 true
            start0();
            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 */
            }
        }
    }
    
    // 开启新线程使用的是native方法
    private native void start0();
    
  • 实现 Runnable 接口,作为 Thread 的参数

    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable(){
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        });
        // 开启一个新的子线程执行
        thread.start();
        // 使用主线程执行
        thread.run();
    }
    

    这种就是实现 Runnable 接口,并作为 Thread 构造器的入参,我们调用时使用了两种方式,可以根据情况使用 start 或 run 方法,使用 start 方法会开启子线程来执行 run 方法里面的内容,使用 run 方法则执行的还是主线程。

线程初始化

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

// 无参构造器,线程名字自动生成
public Thread() {
    init(null, null, "Thread-" + nextThreadNum(), 0);
}

/**
 * Initializes a Thread.
 *
 * @param g代表线程组,线程组可以对组内的线程进行批量的操作,比如批量的打断 interrupt
 * @param target是我们要运行的对象
 * @param name如果不传默认是 "Thread-" + nextThreadNum()
 * @param stackSize 可以设置堆栈的大小
 * @param acc the AccessControlContext to inherit, or
 *            AccessController.getContext() if null
 * @param inheritThreadLocals if {@code true}, inherit initial values for
 *            inheritable thread-locals from the constructing thread
 */
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;
    // 当前线程作为父线程
    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();
}
 

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

线程的其他操作

join

join 的意思就是当前线程等待另一个线程执行完成之后,才能继续操作。

public class ThreadJoin {
    public static void main(String[] args) throws InterruptedException {
        Thread main = Thread.currentThread();
        System.out.println(main.getName() + "线程 is run");

        Thread thread = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "线程 is run");
            try {
                Thread.sleep(5000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "线程 is end");
        });
        thread.start();
        thread.join();
        System.out.println(Thread.currentThread().getName() + "线程 is end");
    }
}

输出:

main线程 is run
Thread-0线程 is run
Thread-0线程 is end
main线程 is end

执行的结果,就是主线程在执行 thread.join (); 代码后会停住,会等待子线程沉睡 30 秒后再执行,这里的 join 的作用就是让主线程等待子线程执行完成

图片描述

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

yield

yield 是个 native 方法

public static native void yield();

意思是当前线程做出让步,放弃当前 cpu,让 cpu 重新选择线程,避免线程过度使用 cpu,我们在写 while 死循环的时候,预计短时间内 while 死循环可以结束的话,可以在循环里使用 yield 方法,防止 cpu 一直被 while 死循环霸占。

有点需要说明的是,让步不是不执行,重新竞争时,cpu 也有可能重新选中这个线程。

sleep

sleep 也是 native 方法,参数是时间数,作用是使当前线程沉睡多久,沉睡时不会释放锁资源。

interrupt

interrupt 中文是打断的意思,意思是可以打断中止正在处于等待的线程,比如:

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

我们举一个例子来说明如何打断 WAITING 的线程,代码如下:

public static void main(String[] args) throws InterruptedException {
    Thread main = Thread.currentThread();
    System.out.println(main.getName() + "线程 is run");

    Thread thread = new Thread(() -> {
        System.out.println(Thread.currentThread().getName() + "线程 is run");
        try {
            Thread.sleep(5000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "线程 is end");
    });
    thread.start();
    // 主线程等待1s后,打断子线程
    Thread.sleep(1000L);
    thread.interrupt();
}
main线程 is run
Thread-0线程 is run
java.lang.InterruptedException: sleep interrupted
Thread-0线程 is end
	at java.lang.Thread.sleep(Native Method)
	at com.study.interview.ThreadJoin.lambda$main$0(ThreadJoin.java:11)
	at java.lang.Thread.run(Thread.java:748)

但是不能中断正在运行的线程

public static void main(String[] args) throws InterruptedException {
    Thread main = Thread.currentThread();
    System.out.println(main.getName() + "线程 is run");

    Thread thread = new Thread(() -> {
        int i = 0;
        while (true){
            System.out.println(i++);
        }
    });
    thread.start();
    // 主线程等待1s后,打断子线程
    Thread.sleep(1000L);
    thread.interrupt();
}

这样是打断不了的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值