sleep 方法、yield 方法、currentThread 方法、setPriority 方法、interrupt 相关方法、join 方法
sleep 方法
线程的 sleep 方法会使线程休眠指定的时间长度。休眠的意思是,当前逻辑执行到此不再继续执行,而是等待指定的时间。但在这段时间内,该线程持有的 monitor 锁(可以认为对共享资源的独占标志)并不会被放弃。我们可以认为线程只是工作到一半休息了一会,但它所占有的资源并不会交还。这样设计很好理解,因为线程在 sleep 的时候可能是处于同步代码块的中间位置,如果此时把锁放弃,就违背了同步的语义。所以 sleep 时并不会放弃锁,等过了 sleep 时长后,可以确保后面的逻辑还在同步执行。
sleep 方法有两个重载,分别是:
public static native void sleep(long millis) throws InterruptedException;
public static void sleep(long millis, int nanos) 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);
}
可以看到,最终调用的还是第一个毫秒级别的 sleep 方法。而传入的纳秒会被四舍五入。如果大于 50 万,毫秒 ++,否则纳秒被省略。
yield 方法
yield 方法我们平时并不常用。yield 单词的意思是让路,在多线程中意味着本线程愿意放弃 CPU 资源,也就是可以让出 CPU 资源。不过这只是给 CPU 一个提示,当 CPU 资源并不紧张时,则会无视 yield 提醒。如果 CPU 没有无视 yield 提醒,那么当前 CPU 会从 RUNNING 变为 RUNNABLE 状态,此时其它等待 CPU 的 RUNNABLE 线程,会去竞争 CPU 资源。讲到这里有个问题,刚刚 yield 的线程同为 RUNNABLE 状态,是否也会参与竞争再次获得 CPU 资源呢?经过我大量测试,刚刚 yield 的线程是不会马上参与竞争获得 CPU 资源的。
我们看下面测试代码:
public class YieldExampleClient {
public static void main(String[] args) {
Thread xiaoming = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println("小明--" + i);
if (i == 2) {
Thread.yield();
}
}
});
Thread jianguo = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println("建国--" + i);
}
});
xiaoming.start();
jianguo.start();
}
}
yield 方法为了提升线程间的交互,避免某个线程长时间过渡霸占 CPU 资源。但 yield 在实际开发中用的比较少,源码的注解也提到这一点:“It is rarely appropriate to use this method."
currentThread 方法
这是一个静态方法,用于获取当前线程的实例。用法很简单,如下:
Thread.currentThread();
拿到线程的实例后,我们还可以获取 Thread 的 名称:
Thread.currentThread().getName();
我们还可以获取线程 ID :
Thread.currentThread().getId();
setPriority 方法
此方法用于设置线程的优先级。每个线程都有自己的优先级数值,当 CPU 资源紧张的时候,优先级高的线程获得 CPU 资源的概率会更大。请注意仅仅是概率会更大,并不意味着就一定能够先于优先级低的获取。这和摇车牌号一个道理,我现在中签概率是标准的 9 倍,但摇中依然摇摇无期。而身边却时不时的出现第一次摇号就中的朋友。如果在 CPU 比较空闲的时候,那么优先级就没有用了,人人都有肉吃,不需要摇号了。
优先级别高可以在大量的执行中有所体现。在大量数据的样本中,优先级高的线程会被选中执行的次数更多。
我们看下 setPriority 的源码:
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);
}
}
Thread 有自己的最小和最大优先级数值,范围在 1-10。如果不在此范围内,则会报错。另外如果设置的 priority 超过了线程所在组的 priority ,那么只能被设置为组的最高 priority 。最后通过调用 native 方法 setPriority0 进行设置。
interrupt 相关方法
interrupt 的意思是打断。第一感觉是让线程中断,其实,并不是这样。inerrupt 方法的作用是让可中断方法,比如让 sleep 中断。也就是说其中断的并不是线程的逻辑,中断的是线程的阻塞。这一点要彻底搞清池,否则带着错误的想法会影响学习的效果。
那么 interrupt 方法调用后,对未使用可中断方法的线程有影响吗?我们做个简单的实验,代码如下:
public class InterruptClient {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
for(int i=0; i<100 ;i++){
System.out.println("I'm doing my work");
System.out.println("I'm interrupted?"+Thread.currentThread().isInterrupted());
}
});
thread.start();
Thread.sleep(1);
thread.interrupt();
}
}
线程 run 方法中没有调用可中断方法,只是输出 “I’m doing my work”,另外还会输出自己的中断状态。而主线程会 sleep 一毫秒,留时间给 thread 线程启动,然后调用 thread 线程的 interrupt 方法。我截取其中关键一段输出如下:
I'm doing my work
I'm interrupted?false
I'm doing my work
I'm interrupted?true
I'm doing my work
I'm interrupted?true
这段后面的输出一直到结束,都在重复 “I’m doing my work I’m interrupted?true“,这说明两个问题:
- 调用 interrupt 方法,并不会影响可中断方法之外的逻辑。线程不会中断,会继续执行。这里的中断概念并不是指中断线程;
- 一旦调用了 interrupt 方法,那么线程的 interrupted 状态会一直为 ture(没有通过调用可中断方法或者其他方式主动清除标识的情况下);
通过上面实现我们了解了 interrupt 方法中断的不是线程。它中断的其实是可中断方法,如 sleep 。可中断方法被中断后,会把 interrupted 状态归位,改回 false 。
join 方法
这个方法功能强大,也很实用。我们用它能够实现并行化处理。比如主线程需要做两件没有相互依赖的事情,那么可以起 A、B 两个线程分别去做。通过调用 A、B 的 join 方法,让主线程 block 住,直到 A、B 线程的工作全部完成,才继续走下去。我们来看下面这段代码:
public class JoinClient {
public static void main(String[] args) throws InterruptedException {
Thread backendDev = createWorker("backed dev", "backend coding");
Thread frontendDev = createWorker("frontend dev", "frontend coding");
Thread tester = createWorker("tester", "testing");
backendDev.start();
frontendDev.start();
// backendDev.join();
// frontendDev.join();
tester.start();
}
public static Thread createWorker(String role, String work) {
return new Thread(() -> {
System.out.println("I finished " + work + " as a " + role);
});
}
}
这段代码中,我们把 join 方法去掉。执行结果如下:
I finished backend coding as a backed dev
I finished testing as a tester
I finished frontend coding as a frontend dev
我们期望的是前端和后端开发完成工作后,测试才开始测试。但从输出结果看并非如此。要想实现这个需求,我们只需把注释打开,让 backendDev 和 frontendDev 先做 join 操作,此时主线程会被 block 住。直到 backendDev 和 frontendDev 线程都执行结束,才会继续往下执行。输出如下:
I finished backend coding as a backed dev
I finished frontend coding as a frontend dev
I finished testing as a tester
可见调用 join 方法后 block 的并不是被调用的 backendDev 和 frontendDev 线程,而是调用方线程。
总结
讲解了 Thread 的几个常用的方法,这些方法在我们实际开发中会经常用到的,需要我们认真学习和理解。有些已经被弃用的方法没有再讲解,比如 stop 方法。关于更多的方法,可以直接阅读 Thread 源代码,Thread 类的注解写得相当详细。其实很多时候我们自己动手直接阅读源代码和注解,是更为快捷的学习方式,而且也更为权威。