Thread API 详细介绍
本文将从常用的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 :往往有时候线程因为死锁等因素,进入假死状态,需要我们通过一些辅助工具判断,这些内容我们将在以后讲解
- (转)对Thread.interrupt()方法很详细的介绍
- 多线程(二)~Thread类相关的API介绍
- 6.UiWatcher API 详细介绍
- 03Android UiAutomator UiDevice API 详细介绍
- UiSelector API 详细介绍
- 九、UiWatcher API 详细介绍
- webdriver API中文版详细介绍
- Thread线程系列之线程池介绍API的简介
- mysql api for C详细介绍
- 对Thread.interrupt()方法很详细的介绍
- API全功略(API编程的详细介绍)
- MariaDB中的thread pool详细介绍和使用方法
- 常见的七款API聚合平台对比和详细介绍
- API编程的详细介绍(转)
- API全功略(API编程的详细介绍)
- HTML5全屏(Fullscreen)API详细介绍
- HTML5全屏(Fullscreen)API详细介绍
- 1.UiDevice API 详细介绍
- MariaDB中的thread pool详细介绍和使用方法
- SimpleCommand(三) ImageLoader API详细介绍