您的位置:首页 > 其它

Thread API 详细介绍

2019-05-23 23:22 197 查看

本文将从常用的Thread API 角度讲解线程相关的操作和用法

  • 线程sleep,yield
  • 线程优先级,获取线程ID,设置线程上下文加载器
  • 线程的interrupt,join
  • 如何关闭一个线程

线程sleep

sleep 是一个静态方法,有两个重载方法,一个需要传入毫秒数,另外一个既需要毫秒数也需要纳秒数

public static void sleep(long millis,  int nanos)  throws InterruptedException
public static void sleep(long millis)  throws InterruptedException

sleep方法会使当前线程进入指定毫秒数的休眠,暂停执行,虽然给定一个休眠时间但最终要以系统的定时器和调度器精度为准,需要注意此时线程不会放弃任何的关于“monitor”锁的所有权,每个线程之间进入睡眠互不影响

JDK1.5之后,通过TimeUnit 可以对sleep方法提供很好的封装,使用它可以省去时间单位的换算步骤,如需要线程休眠1个小时10分10秒10毫秒

TimeUnit.HOURS.sleep(1);
TimeUnit.MINUTES.sleep(10);
TimeUnit.SECONDS.sleep(10);
TimeUnit.MILLISECONDS.sleep(10);

线程 yield

yield方法属于一种启发式的方法,提醒调度器我愿意放弃CPU当前使用权,CUP的资源不紧张时候,则会忽略这种提醒
调用yield方法可能使当前线程从Running 状态切换到Runnable 状态,一般这个方法不太常用,

关于yield 和 sleep

  • sleep会导致当前线程暂停指定的时间,没有CPU时间的消耗,到指定时间之后会自动被系统唤醒
  • yield 只是对CPU调度器的一个提示,如果CPU没有忽略这个提示,它会导致线程上下文的切换
  • yield 会是Running 状态下的 Thread 进入 Runnable 状态(如果CPU调度器没有忽略这个提示的话)
  • sleep 会使线程短暂进入block,会在给定的时间内释放CPU资源
  • sleep几乎百分之百完成给定时间的休眠,而yield的提示并不能一定保证
  • 一个线程sleep,另一个线程调用interrupt 会捕获到中断信号,而yield 则不会

设置线程的优先级

public final void setPriority(int newPriority)     // 设置优先级
public final int getPriority()                              // 获取优先级

理论上优先级高的线程会优先被CPU调度,但事实并不是如此,设置线程的优先级也是一个hint操作,具体如下:

  • 对于root用户,它会hint操作系统你想要设置的优先级别,否则被忽略
  • 如果CPU比较忙,设置优先级可能会获得更多的CPU时间片,但是空闲时优先级的高低几乎不会有任何作用

因此在实际业务中,想通过设置优先级来绑定特殊的业务,可能往往效果不佳

线程优先级的源码分析

public final void setPriority(int newPriority) {
ThreadGroup g;
checkAccess();
if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {        // 优先级控制在 1-10之间,数字越大,优先级越高
throw new IllegalArgumentException();
}
if((g = getThreadGroup()) != null) {
if (newPriority > g.getMaxPriority()) {
newPriority = g.getMaxPriority();
}
setPriority0(priority = newPriority);
}
}

线程的优先级只能在1-10之间,如果指定的线程优先级大于线程池所在的优先级,那么指定的优先级则失效,取而代之的是线程池的最大优先级

优先级的总结
一般情况,不会对线程设定优先级别,更不会让业务严重依赖线程的优先级,一般定义线程采用默认的优先级就好了,默认一般为5

获取线程ID

public long getId() {
return tid;
}

获取线程的唯一ID,线程的ID在整个JVM进程中都是唯一的

获取当前线程

public static native Thread currentThread();

返回当前执行线程的引用

设置线程上下文类加载器

public ClassLoader getContextClassLoader()
public void setContextClassLoader(ClassLoader cl)

获取和设置上下文类加载器,简单来说就是这个线程由哪个类加载器加载的,如果没有修改线程上下文加载器的情况下,则保持和父线程同样的类加载器

设置类加载器,这个方法会打破Java类加载器的父委托机制,在后续文章中将具体讲解

线程interrupt

线程interrupt是一个非常重要的API,也是经常使用的API,与线程中断相关的API,有如下几个

public void interrupt()
public static boolean interrupted()
public boolean isInterrupted()

interrupt
如下的方法调用会使当前线程进入阻塞状态,而调用当前线程的interrupt方法,就可以中断阻塞

  • Object 的 wait方法
  • Object 的 wait(long) 方法
  • Object 的 wait() 方法
  • Thread 的 sleep(long) 方法
  • Thread 的 sleep(long, int) 方法
  • Thread 的 join 方法
  • Thread 的 join(long) 方法
  • Thread 的 join(long int) 方法
  • InterruptibleChannel 的 IO 操作
  • Selector 的 wakeup 方法
  • 其他方法

上述方法都会是当前进程进入阻塞状态,若另外的一个线程调用被阻塞线程的interrupt 方法,则会打断这种阻塞,因此这种方法有时也称为可中断方法,Note : 打断一个线程并不等于改线程的生命周期结束,仅仅是打断了当前线程的阻塞状态

一旦线程在阻塞的情况下被打断,都会抛出一个InterruptedException 的异常,这个异常相当于一个信号一样通知当前线程被打断了

public class ThreadInterrupt {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
TimeUnit.MINUTES.sleep(10);
} catch (InterruptedException e) {
System.out.println("i am be interrupted");
}
});

thread.start();
TimeUnit.MILLISECONDS.sleep(2);
thread.interrupt();
}
}

通过创建一个线程,并企图睡眠10分钟,但大约在2毫秒之后就被主线程调用interrupt 方法打断,打印 “i am be interrupted”

isInterrupted
isInterrupted 是 Thread 的一个成员方法,主要判断当前线程是否被中断,该方法仅仅是对interrupt 标识的一个判断,并不会影响标识发生任何改变,与interrupted 存在很大差别,需要注意的是可中断的方法捕获到中断信号后,也就是捕获到异常之后会擦除interrupt 标识,相当于标识复位

interrupted
interrupted 是一个静态方法,虽然也是判断线程是否被中断,但是它和成员方法isInterrupted 有很大的不同,调用该方法会直接擦除线程的interrupt 标识,需要注意的是,当前线程被打断了,第一次调用interrupted 方法会返回true,并且立即擦除interrupt标识,之后调用将返回false

public class ThreadInterrupted {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(){
@Override
public void run(){
while(true){
System.out.println(Thread.interrupted());   // 判断 thread 线程是否被中断
}
}
};

thread.setDaemon(true);
thread.start();

TimeUnit.MILLISECONDS.sleep(2);
thread.interrupt();
}
}

在返回结果中可以发现很多false包围一个true,也就是interrupted方法判断到了被中断

interrupt 注意事项

public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
public boolean isInterrupted() {
return isInterrupted(false);
}
private native boolean isInterrupted(boolean ClearInterrupted);

interrupted 和 isInterrupted 都是调用同一个方法,只是 isInterrupted 表示不想擦除,而 interrupted 表示想擦除 interrupt标识

public class ThreadInterruptTest {
public static void main(String[] args) {
System.out.println("main thread is interrupt ? " + Thread.interrupted());
Thread.currentThread().interrupt();
System.out.println("main thread is interrupt ? " + Thread.currentThread().isInterrupted());
try {
TimeUnit.MINUTES.sleep(1);
} catch (InterruptedException e) {

System.out.println("I will be interrupted still");
}
}
}

运行发现如果一个线程设置先interrupt 标识,接下来中断方法会立即中断

关于线程中断的总结

  • 当一个线程A调用可中断方法的时候, 通过线程B调用A线程的interrupt方法进行中断(相当于给A标记一个标识,表明线程A现在可以中断,此时线程A因为调用了中断方法(一般调用断方法后的线程都处于Blocked状态)会立即捕获到这个标识,抛出异常并清除中断标识)
  • 线程A调用 isInterrupted 不会做任何的擦除标识的操作,它唯一的作用就是查看线程A此时是否带有中断标识
  • 线程A调用interrupted 将会先查看自己状态,如果线程A带有中断标识将会被擦除
  • 线程的中断方法在执行的过程中会一直观察自己的中断标识状态,当发现有中断标识的时候 会立即抛出异常,并且清除中断标识
  • 如果线程A先设置通过interrupt设置为中断标识,接下来运行中断方法会立即被中断,抛出异常

线程 join

join是Thread同样是一个非常重要的方法,使用它可以实习很多强大的功能,与sleep一样它也是一个可中断的方法

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

线程join方法详解
join 某个线程A,会使当前线程B进入等待,直到线程A结束生命周期,或者到达给定时间,那么在此期间B线程将处于Blocked状态,而不是A线程

public class ThreadJoin {
public static void main(String[] args) throws InterruptedException {
List<Thread> threads = IntStream.range(1, 3).mapToObj(ThreadJoin::create).collect(Collectors.toList());
threads.forEach(Thread::start);
for(Thread thread : threads)
thread.join();

for(int i=0; i<10; i++){
System.out.println(Thread.currentThread().getName() +"#" + i);
shortSleep();
}
}

private static void shortSleep() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

private static Thread create(int seq) {
return new Thread(() -> {
for(int i=0; i<10; i++){
System.out.println(Thread.currentThread().getName() +"#" + i);
shortSleep();
}
});

}
}

运行发现,main线程一直等到前两个线程运行完之后才运行自己。join方法会使当前线程一直等待下去,直到被另外的线程打断,或者join的线程执行结束,当然也可以指定等待的时间

关闭一个线程

早期JDK提供stop方法,但是该方法可能会释放monitor的锁,所有强烈不建议使用该方法,下面介绍几种正常的关闭

线程结束生命周期正常结束
线程运行结束,完成任务后正常退出

捕获中断信号关闭线程
通过new Thread方式创建线程,尽管看似简单,但是它的派生成本还是很高的,因此在一些循环执行任务的时候,可以借助中断方式使其退出

public class InterruptTheradExit {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread() {
@Override
public void run() {
System.out.println("i will start work");
while (!isInterrupted()) {
// handle things
}
System.out.println("I will be exiting");
}
};
thread.start();
TimeUnit.MINUTES.sleep(1);
System.out.println("System will be shutDown");
thread.interrupt();
}
}

通过检查线程interrupt标识来决定是否退出,如果线程执行中断方法,则可以通过捕获退出

使用volatile 开关控制
由于线程的interrupt标识可能被擦除,或者逻辑单元不会调用任何可中断方法,所以常见的方法使用volatile修饰flag开关

import java.util.concurrent.TimeUnit;

public class FlagTheradExit {
static class MyTask extends Thread{
private volatile  boolean close = false;

@Override
public void run(){
System.out.println("I will start work");
while(!close && !isInterrupted()){ }
System.out.println("I will be exiting");
}

public void close(){
this.close = true;
this.interrupt();
}
}

public static void main(String[] args) throws InterruptedException {
MyTask myTask = new MyTask();
myTask.start();
TimeUnit.MINUTES.sleep(1);
System.out.println("System will be shutDown");
myTask.close();
}
}

Note :往往有时候线程因为死锁等因素,进入假死状态,需要我们通过一些辅助工具判断,这些内容我们将在以后讲解

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: