Java并发知识体系持续更新:https://blog.csdn.net/m0_46144826/category_9881831.html
开始之前老规矩,把源码放上:
本文中线程的机制,其实说的就是 Thread 的底层方法,所以这一部分将会介绍 Java Thread 的部分源码。主要包括:
priority 优先级、daemon 守护线程、sleep() 线程休眠、yield() 状态声明、其他 native 方法。
线程中断以后单独开一篇。
这一部分介绍的都是 JDK8
中的 Thread
类的一些 native
方法和最基础的机制。
native
方法调用操作系统本地方法,也可以把它看做 Java 中最底层部分。
虽然不知道这些在啥时候能用到,但是看起来还是很高大上的…
在 Java doc
中有这样关于优先级的介绍:
每一条线程都有优先级。拥有更高优先级的线程在执行时优先于低优先级的线程。
当线程中执行的代码创建了一个新的线程对象,那么这个新的线程对象的优先级和创建它的线程的优先级相同。
这两句话就囊括了线程优先级的三个性质:继承性、规则性、随机性
首先看看 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 只能通过操作系统开放的方法来管理线程。
在 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;
}
这个没啥好解释的了。。。。然后继续看官方注释,可以发现:
实际上,Java GC 就是守护线程。
是不是绕的脑阔疼。。。。
守护线程是服务线程,准确地来说就是服务其他的线程,具有”陪伴“特性。若进程中没有用户线程,则守护线程自动销毁。
给个小栗子提供下测试:
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 方法测试线程池的时候,主线程运行结束,虚拟机没有停止。
当时没有在意,现在想想应该是非守护线程没有全部关闭。
现在来研究下,太具体的不讲了,主要确定下是因为非守护线程没有关闭这件事:
ThreadPoolExecutor
的实例对象,我现在 pool.execute()
提交了一个任务。Worker
内部类,专门负责处理任务。执行任务的方法里写了个死循环(为啥要死循环我就不知道了,以后再研究)final void runWorker(Worker w) {
......
//这里写了个死循环,会看这个 Worker有没有任务,如果没有就会去取
while (task != null || (task = getTask()) != null) {
......
}
Worker
创建的时候并没有设置为守护线程,所以根据线程守护属性的继承性,它是用户线程。又解决了以前的一个疑问,舒服。。。。。。抬走,下一个 ~~
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()
的区别。我就直接列了:
Thread.currentThread().sleep()
则是先返回当前正在执行的线程的引用。这也是一个 native
方法。/**
* @return 返回对当前正在执行的线程对象的引用。
*/
public static native Thread currentThread();
下面看第二个,Thread.sleep(0)
有什么作用直接引用大佬们的博客:
Thread.Sleep(0) 并非是真的要线程挂起0毫秒,意义在于这次调用Thread.Sleep(0)的当前线程确实的被冻结了一下,让其他线程有机会优先执行。
Thread.Sleep(0) 是你的线程暂时放弃cpu,让线程马上重新回到就绪队列而非等待队列,也就是释放一些未用的时间片给其他线程或进程使用,就相当于一个让位动作。
Thread 还提供了一个方法,这个方法的作用是建议 CPU 处理线程。看如下代码和注释:
/**
* 向处理器提出建议,当前线程愿意让出CPU给其他线程。处理器也可以忽略此提示。
* Yield 是一种启发式尝试,旨在提高线程之间的相对进展,否则将过度利用CPU。
* 应将其使用与详细的性能分析和基准测试结合起来,以确保它实际上具有所需的效果。
*
这是一个很少使用的方法。它可能在调试或者测试的时候,或者设计并发控制程序的时候很有用。
*/
public static native void yield();
确实在 Thread 源码内部没有这个方法的直接调用,JDK 内部也很少使用这个方法。
不过和上面的 Thread.sleep(0)
进行对比:
yield()
只是建议,CPU 并不一定采纳执行。就算 CPU 采纳,线程也依然是 RUNNABLE 状态。只是从运行状态变化到就绪状态。Thread.sleep()
时,则会立即从 RUNNABLE 转化为 TIMED_WAITING 状态。Thread 一些过时的 native 方法{ suspend0()挂起线程
,resume0()恢复挂起的线程
, stop0()停止线程
}就不介绍了。
上面已经介绍了绝大部分 native 方法,剩下的几个在这里统一做下简要介绍。
还有非常重要的中断机制,这个将会后面单独介绍。中断是线程中非常重要的内容,能写的很多。。。
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()
之前,其实并没有真正在操作系统中生成线程。
应该对吧,逻辑通畅。
锁的内容也是后面讲,瞟一眼这个代码注释:
/**
* 如果当前线程持有指定锁,则返回true
* 此方法旨在允许程序断言当前线程已持有指定的锁:
*
* assert Thread.holdsLock(obj);
*
* @param obj 测试锁所有权的对象
* @throws NullPointerException 如果obj为 null
* @return 如果当前线程持有指定锁,则返回true
* @since 1.4
*/
public static native boolean holdsLock(Object obj);
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