sleep
sleep是一个静态方法,只有两个重载方法,其中一个传入毫秒数, 另一个既需要毫秒数也需要纳秒数。
public static native void sleep(long millis) throws InterruptedException;
public static void sleep(long millis, int nanos) throws InterruptedException
sleep方法会使当前线程进入指定毫秒的休眠,暂停执行,虽然给定了休眠的时间,但是最终要以系统的定时器和调度器的精度为准,
sleep的休眠有一个很重要的特性,就是不会放弃monitor锁的所有权
JDK 1.5以后,引入了一个枚举TimeUnit 其对sleep方法进行了很好的封装
public void sleep(long timeout) throws InterruptedException {
if (timeout > 0) {
long ms = toMillis(timeout);
int ns = excessNanos(timeout, ms);
Thread.sleep(ms, ns);
}
}
使用实例
TimeUnit.SECONDS.sleep(10);
TimeUnit.MILLISECONDS.sleep(200);
TimeUnit.HOURS.sleep(1);
yield
yield方法属于一种启发式的方法,其会提醒调度器我愿意放弃当前的CPU资源,如果CPU资源不紧张,则会忽略这个提醒。即yield只是一个提示,CPU调度器并不会担保每次都能满足yield的提示。
yield VS sleep
- 是否导致线程上下文的切换
sleep会导致当前线程暂停指定的时间,没有CPU的时间片消耗
yield只会CPU调度器发一个提示,如果CPU调度器不紧张则会忽略这个提示,如果没有忽略这个提示,会导致线程上下文的切换
导致的线程状态切换
sleep 会导致线程短暂进入Block, 会在给定的时间内释放CPU资源
yield 会是Running状态的线程进入Runnable的状态(如果CPU调度器没有忽略这个提示的话)时间上
sleep 几乎百分百完成给定时间的休眠
yield 的提示并不能一定担保中断信号
一个线程sleep 另一个线程调用interrupt 会捕获到中断信号
yield则不会
priority
线程的优先级,理论上优先级比较高的线程会获得优先被CPU调度的机会。但是事实上并不会如你所愿,设置线程的优先级也是一个hint知操作:
- 对于root用户,它会hint操作系统你想要的设置的优先级别,否则它会被忽略
- 如果CPU太忙,设置优先级可能会获得更多CPU时间片,但是闲时优先级的高低几乎不会有任何作用。
所以,不要在程序设计中企图用线程的优先级来绑定某些特性业务,或者让业务严重依赖线程的优先级。
线程的默认优先级
/**
* The minimum priority that a thread can have.
*/
public static final int MIN_PRIORITY = 1;
/**
* The default priority that is assigned to a thread.
*/
public static final int NORM_PRIORITY = 5;
/**
* The maximum priority that a thread can have.
*/
public static final 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 , 如果指定线程的优先级大于线程所在组的最大优先级,则指定优先级将会失效,取而代之的是当前线程所在组的最大线程优先级。如果小于所在组的最大线程优先级, 则设置为需要设置的线程优先级。
线程的上下文类加载器
线程的上下文类加载器,就是这个线程是有那个类加载器加载的,如果在没有修改线程上下文类加载器的情况下,则保持与父线程同样的类加载器
如果设置该线程的类加载器,这个方法则可以打破Java类加载器的父委托机制,有时也称为Java类加载器的后门。
interrupt
public void interrupt();
/**
此方法是一个静态方法,虽然也用于对当前线程是否被中断,但是和成员方法isInterrupted方法的区别,
就是调用该方法会直接擦除掉线程的interrupt标识,
注意:如果当前线程被打断了,那么第一次调用interrupted方法会返回true,
并且立即擦除interrupt标识, 第二次包括以后调用永远都会返回false,
除非再次期间线程又一次被打断
*/
public static boolean interrupted();
/**
Thread的成员方法,主要是判断当前线程是否被中断,该方法
仅仅是对interrupt标识的一个判断,并不会影响标识发生任何改变
*/
public boolean isInterrupted();
调用如下方法会使当前线程进入阻塞状态
- Object 的wait()
- Object 的wait(long)
- Object 的wait(long, int)
- Thread 的sleep(long)
- Thread 的sleep(long, int)
- Thread 的join()
- Thread 的join(long)
- Thread 的join(long, int)
- InterruptibleChannel的IO操作
- Selector的wakeup方法
- 其他
而调用当前线程的Interrupt方法,则就可以打断阻塞, 因此此方法也称为可中断方法。
注意:
打断一个线程不等于该线程的生命周期结束,仅仅是打断了当前线程的阻塞状态。
如果一个线程执行可中断方法被阻塞时,调用interrupt方法将其中断,会导致其中断标识被清除。
如果一个线程已经试死亡状态,那么尝试对其调用interrupt方法会直接被忽略
join
与sleep一样, 它也是一个可中断方法。也即如果有其他线程执行了对当前线程的interrupt操作,它也会捕捉到中断信号,并且擦除线程的interrupt标识。
join某个线程A , 会使当前线程B(比如main线程)进入等待,知道线程A结束生命周期,或者到达指定的时间,那么在此期间线程B是Blocked的
实例:
比如需要你做一个接口请求,这个请求的返回需要调用多个接口的返回进行组合,这里用join来做并行多个请求,等都返回后再进行组合,当然也可用JDK自带的CountDownLatch 或者CyclicBarrier等,这里我们用join来实现一把。
public class ClientRequestTask extends Thread {
public static void main(String[] args) {
List<ClientRequestTask> tasks = IntStream.range(1, 3).boxed()
.map(url -> new ClientRequestTask("url:" + url)).collect(Collectors.toList());
tasks.forEach(Thread::start);
tasks.forEach(task -> {
try {
task.join();
} catch (InterruptedException e) {
}
});
List<String> list = new ArrayList<>();
tasks.stream().map(ClientRequestTask::getResults).forEach(list::addAll);
System.out.println(list);
}
private String url;
private final List<String> results = new ArrayList<>();
public ClientRequestTask(String url) {
this.url = url;
}
public List<String> getResults() {
return results;
}
@Override
public void run() {
try {
System.out.println(url + " start request data");
int i = ThreadLocalRandom.current().nextInt(10);
TimeUnit.SECONDS.sleep(ThreadLocalRandom.current().nextInt(10));
results.add(url + "---------" + i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
如何关闭一个线程
- JDK有一个Deprecated的方法stop, 但是该方法关闭线程时不会释放掉monitor锁,所以官方已经不推荐使用此方法了。
- 线程结束生命周期
- 捕获中断信号关闭线程
- 使用volatile开关控制
- 异常退出