Thread API
Thread API
1 sleep
正常Thread中的sleep方法时将当前线程进入休眠,暂停执行,虽然给定了一个休眠的时间,但是最终要以系统的定时时间和调度器的精度为准,休眠不会放弃monitor锁的所有权。
也可以看看jdk1.8中官方文档中的解释:
static void sleep(long millis)
使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。
static void sleep(long millis, int nanos)
导致正在执行的线程以指定的毫秒数加上指定的纳秒数来暂停(临时停止执行),这取决于系统定时器和调度器的精度和准确性。
接下来看看java 源码中是如何实现的
public static native void sleep(long millis) throws InterruptedException;
没有具体实现的原因,可以查看navive和JNI的解释(个人理解:就是通过调用jdk实现,说白了就是有人给你实现好了,你直接使用就行,)
通过构造函数,可以知道sleep是通过毫秒和纳秒来初始化暂停时间的,这也就是说,你需要进行时间的转换。
替换方法:TimeUnit封装了sleep的使用
列子:
TimeUnit.HOURS.sleep(1);
TimeUnit.SECONDS.sleep(5);
2 yield
首先来看看在java源码中的注释:
向调度程序提示当前线程愿意放弃其当前对处理器的使用。调度程序可以随意忽略此提示。 Yield 是一种启发式尝试,旨在改善线程之间的相对进展,否则会过度使用 CPU。它的使用应与详细的分析和基准测试相结合,以确保它确实具有预期的效果。很少适合使用这种方法。它对于调试或测试目的可能很有用,它可能有助于重现由于竞争条件引起的错误。在设计并发控制结构(例如 java.util.concurrent.locks 包中的结构)时,它也可能很有用。
也就是说yield是一种启发式的方法,会提示调度器愿意放弃当前cpu的资源,如果cpu资源不紧张,会忽略这种提示。
调用yield会使当前线程从RUNNING状态切换至RUNNABLE状态,一般不常用。
yield和sleep对比:
- sleep会导致当前线程暂停指定的时间,这段时间是没有CPU的消耗;
- yield只是对CPU调度器的一个提示,如果没有忽略这个提示,它会导致线程的上下文的切换。
- sleep会使线程短暂block,会在给定的时间释放CPU资源。
- 当CPU调度器没有忽略yield的命令,则会使线程从RUNNING状态切换到RUNNABLE状态。
- sleep是一定会保证休眠一定时间,但是yield不一定。
- 一个线程sleep另一个线程调用interrupt会捕获到中断信号,而yield不会。
3 线程优先级
理论上线程优先级会优先获取被CPU调度的机会,但是,事实上这也是提示操作,当cpu资源比较紧张的时候,可能会如你所愿,但是空闲的CPU对此操作,可以说是毫无作用。
线程的优先级设置源码
/**
* 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;
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);
}
}
可以看出来,线程的优先级不能超过10,不能小于1;
线程的优先级依赖于父类线程,一般main线程的优先级是5,所以派生出来的线程一般优先级也是5;
4 线程ID
public long getId() {
return tid;
}
获取线程id,线程ID是整个JVN进程中都会是唯一的,从0开始自增。
5 获取当前线程
JDK文档:
static Thread currentThread()
返回对当前正在执行的线程对象的引用。
java中代码:
public static native Thread currentThread();
用于放回当前线程的引用,方法简单,但是比较常用。
6 设置上下文类加载器
public void setContextClassLoader(ClassLoader cl)
设置类加载器,这个方法可以打破JAVA类加载机制的父委托机制,有时候也称之为java类加载器的后门。
public ClassLoader getContextClassLoader()
获取线程上下文的类加载器,就是线程是由那个类加载器加载的,如果没有修改类加载器的情况下,则保持与父线程同样的类加载器
7 线程interrupt **
方法
- public void interrupt()
- public static boolean interrupted()
- public boolean isInterrupted()
interrupt
调用以下方法,会使线程进入阻塞状态:
- Object的wait
- Object的wait(long)
- Object的wati(long,int)
- Thread的sleep(long)
- Thread的sleep(long,int)
- Thread的join
- Thread的join(long)
- Thread的join(long,int)
- InterruptibleChannel的io操作
- Selector的wakeup方法
- 其他方法
在这种进入阻塞情况下,调用线程的interrupt方法,就可打断阻塞。
这种方法有时会被成为可中断方法,这个打断不是结束当前线程的生命周期,而是打断当前线程的阻塞状态。
一个线程阻塞的情况下被打断,会抛出一个InterruptedException的异常,这个异常像是一个signal(信号)一样通知当前线程被打断了
isInterrupted
主要判断当前线程是否被中断
interrupted
interrupted是一个静态方法,虽然也用与判断当前线程是否被中断,但是他与成员方法isInterrupted有很大区别,调用该方法会直接擦出掉线程的interrupt表示,需要注意的是,如果当前线程被打断,那么第一次调用interrupted方法会返回true,并且立即擦除interrupt表示;第二次以及之后的调用都会返回false,除非此期间线程又一次被打断。
对比:
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
public boolean isInterrupted() {
return isInterrupted(false);
}
private native boolean isInterrupted(boolean ClearInterrupted);
可以看出来,interrupted()和isInterrupted()都是调用isInterrupted方法来实现的,区别就是是否擦除interrupt标识;
8 join
- public final void join() throws InterruptedException
- public final synchronized void join(long millis, int nanos)
throws InterruptedException - public final synchronized void join(long millis)
throws InterruptedException
与sleep相同,同样是一个可中断的方法。
join理解:
join某个线程A, 还是其他线程进入等待,直到线程A生命周期结束,或者到达给定的时间,那么此期间其他线程处于BLOCKED,而不是A线程;
个人理解:
join A线程,相当于,屏蔽其他线程的执行,当前只执行A线程的代码。
当然,这其中如果同时启动了两个线程之后,在join,两个线程会交替运行之后,在运行其他线程;
例子:
Thread05
package com.spring.zcl.study.springbootstudy.thread;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* @Author: zcl
* @Date: 2021-12-17 10:04
*/
public class Thread05Join {
public static void main(String[] args) throws InterruptedException {
List<Thread> collect = IntStream.range(1, 4).mapToObj(Thread05Join::create).collect(Collectors.toList());
collect.forEach(Thread::start);
for (Thread thread : collect) {
thread.join();
}
for (int i = 0 ; i < 10 ; i++) {
System.out.println(Thread.currentThread().getName() + "######" + i);
shortSleep();
}
}
private static Thread create(int seq) {
return new Thread(() -> {
for (int i = 0 ; i < 10 ; i++) {
System.out.println(Thread.currentThread().getName() + "######" + i);
shortSleep();
}
}, String.valueOf(seq));
}
private static void shortSleep() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
1######0
3######0
2######0
1######1
3######1
2######1
3######2
1######2
2######2
1######3
3######3
2######3
1######4
3######4
2######4
2######5
3######5
1######5
1######6
3######6
2######6
1######7
2######7
3######7
1######8
3######8
2######8
1######9
3######9
2######9
main######0
main######1
main######2
main######3
main######4
main######5
main######6
main######7
main######8
main######9
从结果可以看出来,join调用之后,线程虽然堵塞了,但是由于其他线程也已经strat启动了,所以仍然在执行,主线程是等其余线程完成之后,才开始执行;
我们修改一下,看看效果,启动其中一个线程,然后join的效果:
List<Thread> collect = IntStream.range(1, 4).mapToObj(Thread05Join::create).collect(Collectors.toList());
collect.get(0).start();
for (Thread thread : collect) {
thread.join();
}
collect.get(1).start();
collect.get(2).start();
for (int i = 0 ; i < 10 ; i++) {
System.out.println(Thread.currentThread().getName() + "######" + i);
shortSleep();
}
我们将main方法中的start部分代码进行修改如上,执行如下:
1######0
1######1
1######2
1######3
1######4
1######5
1######6
1######7
1######8
1######9
main######0
2######0
3######0
2######1
3######1
main######1
main######2
3######2
2######2
2######3
3######3
main######3
3######4
main######4
2######4
3######5
2######5
main######5
main######6
3######6
2######6
main######7
3######7
2######7
3######8
2######8
main######8
2######9
main######9
3######9
可以看出只有Thread1执行之后,其余线程与主线程才开始执行,并且是乱序输出。
9 如何关闭一个线程
JDK有一个Deprecated方法stop,但是该方法存在一个问题,JDK官方不建议使用,后续版本中有可能会被移除,根据官方描述,该方法在关闭线程的时候不会释放monitor的锁。
正常关闭
-
线程结束生命周期 正常结束
-
捕获中断信号关闭线程
-
使用volatile开关控制
异常退出
在一个线程的执行单元中,不允许抛出checked异常的,无论Thread中的run方法,还是Runnable中的run方法,如果线程运行过程中需要捕获checked异常并且判断是否还有运行下去的必要,那么可以将checked异常封装成unchecked异常(RuntimeException)抛出进而结束线程的生命周期。
进程假死
假死就是进程虽然存在,但是没有日志输出,程序不进行任何的作业,看起来就像死了一样,但事实是没有死,程序出现这样的情况,大多数的情况都是因为某个线程阻塞了,或者线程出现了死锁的情况。