线程的核心原理
前言
看源码真的是一种享受又恍然大悟的感受,我曾不止一次赞叹过如Spring、SpringMVC、SpringBoot等源码设计的优美,我在学习线程、偏向锁和轻量级锁等JAVA内置锁、CAC原理、自旋锁 公平锁和读写锁等JUC显式锁的时候,总是从源码中来解答我的疑惑,而JDK关于线程的源码的设计思想也让我大加赞叹。由于笔者最近在看书的时候,有点不上心,特别是在吃完饭后经常看10几集蜡笔小新(虽然一集只有7分钟,第五季还加上了片头曲),为了调整自己的学习状态我认为分享知识是最好的途径之一了。
线程的调度和时间片
CPU时间片
CPU的计算频率非常高,就拿英特尔的的酷睿i9-10900X处理器来说,其基本频率是3.70GHz,也就是说平均一秒钟CPU可计算接近40亿次,因此我们可以将CPU的时间以毫秒的维度来进行划分,每一个小段就叫做CPU时间片。这里不同的CPU、不同的操作系统其CPU的时间片都不一样。但是就算以10毫秒来划分CPU时间片,一个CPU时间片的计算次数也高达4亿次,其计算量是非常大的。
目前操作系统主流的调度方式是以分配给线程CPU时间片的方式来调度线程,当线程获取到CPU时间片时,线程便开始运行,如果线程没有获取到CPU时间片的话,就依旧处于就绪的状态等到获取到时间片后执行,而由于对于CPU时间片我们是以毫秒维度进行划分的,所以对于我们个人来说,其时间是微乎其微的,在这微乎其微的时间内,多个线程快速地切换运行,也就给我们一种多个线程在并发执行的感觉。
线程调度的策略
目前线程调度的策略有两种,一种是分时调度,另一种是抢占式调度。
什么是分时调度?
所谓的分时调度即是所有线程轮流分配CPU时间片来进行线程的调度。这和大同社会很像,每个线程谁也不多,谁也不少,都让它捞着,实现CPU时间片分配的线线平等。
什么是抢占式调度?
现代的操作系统一般采取的策略是抢占式调度,所谓的抢占式调度指的是操作系统根据线程的优先级来分配CPU时间片,但不是绝对的,线程优先级高的仅是获取到CPU时间片的概率要高,与线程优先级低的线程抢占CPU时间片时,不一定是线程优先级高的获取到CPU时间片。
现如今JAVA的线程调度是委托给操作系统完成的:它会将线程注册到操作系统的本地线程中,让操作系统完成线程调度。因此JAVA的线程调度也是抢占式的调度。
线程的优先级
对于抢占式调度策略来说,线程的优先级很重要,Thread类提供了一个实例属性和两个实例方法(看上去是这样的)来操作线程的优先级。
我们进入Thread类来看一下吧:
-
实例属性: priority (优先级,为int类型,范围为1~10,越大优先级越高。)
-- 源码定义:
private int priority;
-
获取线程优先级实例方法源码:
public final int getPriority() { return priority; }
-
设置线程优先级实例方法源码:
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();
}
setPriority0(priority = newPriority);
}
}
我们解读一下设置线程优先级的源码:
- 首先它会检查一下你有没有这个权限去设置,点进去checkAccess()会发现它会获取你的安全管理器文件进而来判断你有没有权限,详情看: https://www.cnblogs.com/yaowen/p/10117893.html
- 然后它会判断你所给的优先级是不是在1至10之间的,若不是便会抛出一个IllegalArgumentException异常。
- 接着它会获取线程组,所谓的线程组其实是由线程组成的管理线程的类,如果设置线程的优先级要大于线程组的优先级的话,就将重置设置线程的优先级,使其等于线程组的优先级,最后调用setPriority0去真正的设置线程的优先级,也就是说setPriority方法设置线程优先级其实内部是调用了setPriority0方法,这里我们可以看到非业务逻辑和业务方法的分离,很美妙的一种写法。
接着我们写一个例子来证明是不是优先级高的获取CPU时间片的机率大
/**
* 线程的优先级
*/
public class PriorityDemo {
public static final int SLEEP_GAP = 1000;
static class PrioritySetThread extends Thread{
static AtomicInteger threadNo = new AtomicInteger(1);
public PrioritySetThread() {
super("thread-"+threadNo.get());
threadNo.incrementAndGet();
}
@Override
public synchronized void start() {
super.start();
}
public volatile long amount = 0;
@Override
public void run() {
for (int i = 0;; i++) {
this.amount++;
}
}
}
public static void main(String[] args) throws InterruptedException {
PrioritySetThread[] threads = new PrioritySetThread[10];
for (int i = 0; i < threads.length; i++) {
threads[i] = new PrioritySetThread();
threads[i].setPriority(i+1);
}
for (int i = 0; i < threads.length; i++) {
threads[i].start();
}
//睡眠一秒
ThreadUtil.sleepMilliSeconds(SLEEP_GAP);
for (int i = 0; i < threads.length; i++) {
threads[i].stop();//停止线程
}
for (int i = 0; i < threads.length; i++) {
System.out.println(threads[i].getName()+"-优先级为-:"+threads[i].getPriority()+"-机会值为-"+threads[i].amount);}
}
}
结果为:
从结果我们可以看出,线程优先级大的线程较于线程优先级小的线程获取CPU时间片越多,执行得到的值越大。但优先级大的如thread-8执行的值要比thread-7的值要小,这也意味着线程优先级高的仅是获取到CPU时间片的概率要高,与线程优先级低的线程抢占CPU时间片时,不一定是线程优先级高的获取到CPU时间片。
线程的生命周期
线程的生命周期有6种,也分别代表着线程的不同的状态。Thread类提供了一个实例属性和实例方法可以来获取线程当前的状态,分别是:
-
threadStatus(实例属性),定义是
private volatile int threadStatus;
-
获取线程状态的实例方法:
public State getState() { // get current thread state return jdk.internal.misc.VM.toThreadState(threadStatus); }
jdk.internal.misc.VM 类一般是直接操控操作系统,不受JVM管制。如其提供的Unsafe类。
状态有六种,从枚举类Status我们可以得知:
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called {@code Object.wait()}
* on an object is waiting for another thread to call
* {@code Object.notify()} or {@code Object.notifyAll()} on
* that object. A thread that has called {@code Thread.join()}
* is waiting for a specified thread to terminate.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}
-
NEW 表示是新建线程,此时并没有使用thread.start()启动线程去运行用户代码逻辑块。
-
RUNNABLE 是JAVA定义的,它在操作系统其实有两种意思,一种是线程处于就绪状态,随时等着操作系统分配CPU时间片,另一种则是操作系统已分配CPU时间片,该线程处于执行状态。
-
BLOACKED 是阻塞的意思,当线程正在执行,执行到获取锁的代码的时候,发现锁已经被其他线程占有,此时线程就会进入阻塞状态,阻塞状态期间操作系统不分配CPU时间片,当其他线程释放锁时,会唤醒该线程,状态变为Runnable(就绪),然后去争抢锁。
-
TIMED_WAITING 表示当前的线程处于限时等待状态,在等待状态时,操作系统也不会给该线程分配CPU时间片。一般进入该状态都是调用了线程的sleep(int n)、wait(time)、join(time)等方法
-
WAITING 与TIME_WAITING类似,只不过不是限时等待,是一直处于等待状态中,一般进入这种状态是调用了sleep()、wait()、join()等不限时的方法。
-
TERMINATED 当线程执行完任务或者线程在执行任务中途发生异常使得线程关闭时,线程的状态为转为此状态。
JVM自带jstack工具使用
Jstack工具是JAVA虚拟机自带的一种堆栈跟踪工具。Jstack用于生成或导出JVM虚拟机运行实例当前时刻的线程快照。有了这个工具,我们可以定位线程出现阻塞,停顿,和长时间运行的原因。下面我简单的演示一下jstack工具的使用。