Java Thread线程基础机制,源码解读 - 优先级、守护这些玩意儿

线程基础机制

Java并发知识体系持续更新:https://blog.csdn.net/m0_46144826/category_9881831.html

开始之前老规矩,把源码放上:

  • Thread 源码解读注释:https://github.com/qianwei4712/JDK1.8.0.25-read/blob/master/src/main/java/java/lang/Thread.java

本文中线程的机制,其实说的就是 Thread 的底层方法,所以这一部分将会介绍 Java Thread 的部分源码。主要包括:

priority 优先级、daemon 守护线程、sleep() 线程休眠、yield() 状态声明、其他 native 方法。

线程中断以后单独开一篇。

这一部分介绍的都是 JDK8 中的 Thread 类的一些 native 方法和最基础的机制。

native 方法调用操作系统本地方法,也可以把它看做 Java 中最底层部分。

虽然不知道这些在啥时候能用到,但是看起来还是很高大上的…

Java Thread线程基础机制,源码解读 - 优先级、守护这些玩意儿_第1张图片


priority 优先级

Java doc 中有这样关于优先级的介绍:

每一条线程都有优先级。拥有更高优先级的线程在执行时优先于低优先级的线程。

当线程中执行的代码创建了一个新的线程对象,那么这个新的线程对象的优先级和创建它的线程的优先级相同。

这两句话就囊括了线程优先级的三个性质:继承性、规则性、随机性

  • 规则性优先级高的线程获取 CPU 的资源概率比较高 ;线程最终是由操作系统来分配 CPU 资源的,Java 只能为这条线程设置较高优先级,使其更有可能尽早获得运行。
  • 随机性在操作系统层面,就算线程设置了更高的优先级,也无法绝对保证优先执行,只是拥有更大的概率获得资源。
  • 继承性当线程中执行的代码创建了一个新的线程对象,那么这个新的线程对象的优先级和创建它的线程的优先级相同。 这个好理解,而且代码里也非常明确。

首先看看 Thread 关于优先级的参数:

// 线程优先级,int类型,范围为1-10,默认为5
private int priority;
// 线程可以具有的最低优先级。
public final static int MIN_PRIORITY = 1;
// 分配给线程的默认优先级
public final static int NORM_PRIORITY = 5;
// 线程可以具有的最高优先级。
public final static int MAX_PRIORITY = 10;

在一条线程初始化时,请删去其余代码,可以发现线程的 继承性 ,如下:

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
    ...
    Thread parent = currentThread();
    ...
    this.priority = parent.getPriority();
    ...
}

省略的其他多余代码,初始化线程,将启动线程的优先级传递到新线程。

然后,我们还能通过调用线程的 setPriority() 方法设置线程的优先级( 优先级的设定最好在 start() 之前 ):

public final void setPriority(int newPriority) {
    ThreadGroup g;
    //判定当前运行的线程是否有权修改该线程
    checkAccess();
    if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
        throw new IllegalArgumentException();
    }
    //获取该线程所属的线程组
    if((g = getThreadGroup()) != null) {
        // 判断是否高于此线程组的最高优先级
        if (newPriority > g.getMaxPriority()) {
            newPriority = g.getMaxPriority();
        }
        // 调用 native 方法设置优先级
        setPriority0(priority = newPriority);
    }
}

最后实际设置优先级的方法为:

private native void setPriority0(int newPriority);// 设置优先级

调用操作系统本地方法,所以 Java 只能通过操作系统开放的方法来管理线程。


daemon 守护线程

Java 中有两类线程:用户线程 (User Thread)守护线程 (Daemon Thread)

在 JDK8 的 Thread 源码的官方注释中,关于守护线程有以下几句描述:

每一条线程都可能被标注为守护线程。当创建它的线程是一个守护线程时,新线程才是守护线程。

当 Java 虚拟机启动时,通常已经存在了一个非守护线程(这个线程通常会调用某指定类的名为main的方法)。

Java 虚拟机将继续执行,直到发生以下任一情况,发生这两种情况,Java 虚拟机将结束:

  • Runtime类的exit方法被调用,并且安全管理器已经允许进行退出操作。
  • 所有非守护线程都已经消亡,消亡原因要么是从run方法返回了,要么是抛出异常了。

先说简单的,与线程优先级相同,守护线程的 继承性 也在线程创建 init 方法中:

this.daemon = parent.isDaemon();
//判断就是直接返回属性值
public final boolean isDaemon() {
    return daemon;
}

这个没啥好解释的了。。。。然后继续看官方注释,可以发现:

  • main 方法启动的线程是非守护线程(也就是用户线程)。
  • 发生上述的两种情况时,Java 虚拟机将会退出。翻译一下其实就是,所有非守护线程结束时,Java 虚拟机才会停止。所以, Java 虚拟机中必定包含守护线程。

实际上,Java GC 就是守护线程。

是不是绕的脑阔疼。。。。

Java Thread线程基础机制,源码解读 - 优先级、守护这些玩意儿_第2张图片


守护线程是服务线程,准确地来说就是服务其他的线程,具有”陪伴“特性。若进程中没有用户线程,则守护线程自动销毁。

给个小栗子提供下测试:

public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread(() -> {
        try {
            for (int i = 0; i < 10000; i++) {
                Thread.sleep(1000);
                System.out.println("守护线程尚未停止");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
    thread.setDaemon(true);
    thread.start();
    //主线程等待3秒
    Thread.sleep(3000);
    System.out.println("主线程(非守护线程)结束。。。守护线程即将关闭");
}

测试结果很明显了,就不贴了。。。。。。

还有就是:

设置守护线程必须在线程开始执行之前:

    /**
     * 将此线程标记为{@linkplain #isDaemon 守护线程}或用户线程。
     * 当仅运行的线程都是守护程序线程时,Java虚拟机将退出。
     * 

必须在线程启动之前调用此方法 * @param on 如果{@code true},则将该线程标记为守护线程 * @throws IllegalThreadStateException 如果此线程是 {@linkplain #isAlive alive} * @throws SecurityException 如果 {@link #checkAccess} 确定当前线程无法修改此线程 */ public final void setDaemon(boolean on) { checkAccess(); if (isAlive()) { throw new IllegalThreadStateException(); } daemon = on; }

/**
 * @return 测试此线程是否仍然存在。如果一个线程已经启动并且尚未死亡,则该线程是活动的。
 */
public final native boolean isAlive();

看,这里用到了个 native 方法,最终决定线程的并不是 Java。


到这里忽然想起以前在 main 方法测试线程池的时候,主线程运行结束,虚拟机没有停止。

当时没有在意,现在想想应该是非守护线程没有全部关闭。


现在来研究下,太具体的不讲了,主要确定下是因为非守护线程没有关闭这件事:

  1. 比如已经一个线程池 ThreadPoolExecutor 的实例对象,我现在 pool.execute() 提交了一个任务。
  2. 线程池内部有个 Worker 内部类,专门负责处理任务。执行任务的方法里写了个死循环(为啥要死循环我就不知道了,以后再研究)
final void runWorker(Worker w) {
	......
    //这里写了个死循环,会看这个 Worker有没有任务,如果没有就会去取
    while (task != null || (task = getTask()) != null) {
    ......
}
  1. 最后就是这个 Worker 创建的时候并没有设置为守护线程,所以根据线程守护属性的继承性,它是用户线程。

Java Thread线程基础机制,源码解读 - 优先级、守护这些玩意儿_第3张图片

又解决了以前的一个疑问,舒服。。。。。。抬走,下一个 ~~


sleep() 线程休眠

sleep(long millis) 方法就算是没学过并发也肯定见过。在刚开始学习的时候肯定见过这个方法的使用。

而且,不是还有段子,二期性能优化全靠这段代码嘛。。。

 /**
  * 使当前正在执行的线程进入休眠状态(暂时停止执行),以毫秒为单位,取决于系统定时器和调度程序的精度和准确性。
  * 并且线程不会丢失监视器锁。
  * @param  millis 睡眠时间(以毫秒为单位)
  * @throws  IllegalArgumentException 如果{@code millis}的值为负
  * @throws  InterruptedException 如果有任何线程中断了当前线程。抛出此异常时,将清除当前线程的中断状态。
  */
  public static native void sleep(long millis) throws InterruptedException;

这里有两个异常,虽然不知道 native 是如何抛出异常的,但是了解下,有这两异常。

这是一个静态方法,作用于当前使用这个方法的线程。

Thread 中还有一个更的睡眠方法:

    /**
     * @param  millis 睡眠时间(以毫秒为单位)
     * @param  nanos {@code 0-999999} 额外的纳秒睡眠
     * @throws  IllegalArgumentException 如果{@code millis}的值为负,或者{@code nanos}的值不在{@code 0-999999}范围内
     * @throws  InterruptedException 如果有任何线程中断了当前线程。抛出此异常时,将清除当前线程的中断状态。
     */
    public static void sleep(long millis, int nanos) throws InterruptedException {
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                    "nanosecond timeout value out of range");
        }
        if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
            millis++;
        }
        sleep(millis);
    }

仔细一读简直瞎了我的狗眼。。。。。。意思就是,超过 500 微秒,算 1 毫秒????源码还能这么随意???

不过这里其实也能发现一个知识点:

操作系统对线程的管理只能精确到毫秒级别。

然后这里补充两个有点意思冷门知识:

  • Thread.currentThread().sleep()Thread.sleep() 的区别
  • Thread.sleep(0) 有什么作用

先看第一个, Thread.currentThread().sleep()Thread.sleep() 的区别。我就直接列了:

  1. 从上面 sleep 方法的代码以及注释中可以发现,它是作用于当前线程。
  2. 因为它是一个静态方法,所有线程共用一个方法。所以当当前线程调用这个方法时,并没有创建新的 Thread 对象。
  3. Thread.currentThread().sleep() 则是先返回当前正在执行的线程的引用。这也是一个 native 方法。
/**
 * @return  返回对当前正在执行的线程对象的引用。
 */
 public static native Thread currentThread();
  1. 所以实际上,两者并没有区别。只是一个是直接使用静态方法,一个是实例调用静态方法。

下面看第二个,Thread.sleep(0) 有什么作用直接引用大佬们的博客:

Thread.Sleep(0) 并非是真的要线程挂起0毫秒,意义在于这次调用Thread.Sleep(0)的当前线程确实的被冻结了一下,让其他线程有机会优先执行。

Thread.Sleep(0) 是你的线程暂时放弃cpu,让线程马上重新回到就绪队列而非等待队列,也就是释放一些未用的时间片给其他线程或进程使用,就相当于一个让位动作。


yield() 状态声明

Thread 还提供了一个方法,这个方法的作用是建议 CPU 处理线程。看如下代码和注释:

    /**
     * 向处理器提出建议,当前线程愿意让出CPU给其他线程。处理器也可以忽略此提示。
     * 

Yield 是一种启发式尝试,旨在提高线程之间的相对进展,否则将过度利用CPU。 * 应将其使用与详细的性能分析和基准测试结合起来,以确保它实际上具有所需的效果。 *

这是一个很少使用的方法。它可能在调试或者测试的时候,或者设计并发控制程序的时候很有用。 */ public static native void yield();

确实在 Thread 源码内部没有这个方法的直接调用,JDK 内部也很少使用这个方法。

不过和上面的 Thread.sleep(0) 进行对比:

  • yield() 只是建议,CPU 并不一定采纳执行。就算 CPU 采纳,线程也依然是 RUNNABLE 状态。只是从运行状态变化到就绪状态。
  • 调用 Thread.sleep() 时,则会立即从 RUNNABLE 转化为 TIMED_WAITING 状态。

其他 native 方法

Thread 一些过时的 native 方法{ suspend0()挂起线程resume0()恢复挂起的线程stop0()停止线程 }就不介绍了。

上面已经介绍了绝大部分 native 方法,剩下的几个在这里统一做下简要介绍。

还有非常重要的中断机制,这个将会后面单独介绍。中断是线程中非常重要的内容,能写的很多。。。

  1. 设置线程名

Thread 中对操作系统进行线程名操作是这个方法:

private native void setNativeName(String name);// 设置线程名

但是在 Thread 类中,也有自己维护的线程名。

首先 Thread 维护了一个 char[] 作为线程名。

private volatile char  name[]; //线程名

同时提供了 get/set 方法,对于其中的 set 方法:

    /**
     * 更改线程名
     * @param      name  此线程的新名称。
     */
    public final synchronized void setName(String name) {
        checkAccess();
        this.name = name.toCharArray();
        // 如果线程状态不为0(初始状态),说明线程已经启动
         // 那就需要调用 native 方法进行更改。
        if (threadStatus != 0) {
            setNativeName(name);
        }
    }

如果线程已经启动,那么需要调用本地方法进行更改。。

那么如果没启动呢,就不需要调用了?。。。。那这样是不是说明:

Java 中 new Thread() 创建了一条线程,在 start() 之前,其实并没有真正在操作系统中生成线程。

应该对吧,逻辑通畅。


  1. 持有锁

锁的内容也是后面讲,瞟一眼这个代码注释:

    /**
     * 如果当前线程持有指定锁,则返回true
     * 

此方法旨在允许程序断言当前线程已持有指定的锁: *

     *     assert Thread.holdsLock(obj);
     * 
* @param obj 测试锁所有权的对象 * @throws NullPointerException 如果obj为 null * @return 如果当前线程持有指定锁,则返回true * @since 1.4 */
public static native boolean holdsLock(Object obj);

  1. 还有几个不知道什么作用的方法
private native static Thread[] getThreads();
private native static StackTraceElement[][] dumpThreads(Thread[] threads);

这两静态方法在 Thread 类没用到,好像和 JVM 栈有关。

既然没用到,那就不管了。。。


参考文章

https://blog.csdn.net/qq_22771739/article/details/82529874

https://blog.csdn.net/weixin_33782386/article/details/92423372

https://segmentfault.com/a/1190000016056471

https://www.cnblogs.com/JianGuoWan/p/9139698.html

你可能感兴趣的:(#,并发知识体系)