Thread API详解
一、线程sleep
sleep方法是一个静态方法。他有两个重载方法。
public static native void sleep(long millis) 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方法会使当前线程进人指定毫秒数的休眠,暂停执行,虽然给定了一个休眠的时间,但是最终要以系统的定时器和调度器的精度为准,休眠有一个非常重要的特性,那就是其不会放弃monitor锁的所有权。用法:
//当前线程休眠1s
Thread.sleep(1000);
1.1 用TimeUtil代替Thread.sleep
在JDK1.5以后,JDK引入了一个枚举TimeUnit,其对sleep方法提供了很好的封装,使用它可以省去时间单位的换算步骤,比如线程想休眠1小时23分12秒44毫秒,使用TimeUnit来实现就非常的简便优雅了:
TimeUnit.HOURS.sleep(1);
TimeUnit.MINUTES.sleep(23);
TimeUnit.SECONDS.sleep(12);
TimeUnit.MILLISECONDS.sleep(44);
同样的时间表达,TimeUnit显然清晰很多,所以,在使用Thread.sleep的地方,完全使用TimeUnit来代替,因为sleep能做的事,TimeUnit全部都能完成,并且功能更加的强大。
二、线程yield
yield方法属于一种启发式的方法,其会提醒调度器我愿意放弃当前的CPU资源,如果CPU的资源不紧张,则会忽略这种提醒。
调用yield方法会使当前线程从RUNNING状态切换到RUNNABLE状态,一般这个方法不太常用。使用方法:
Thread.yield();
注意:yield只是一个提示,CPU并不会每次都满足他。
2.1 yield 和sleep的区别
在JDK1.5以前的版本中yield的方法事实上是调用了sleep(0), 但是它们之间存在着本质的区别,具体如下。
- sleep会导致当前线程暂停指定的时间,没有CPU时间片的消耗。
- yield只是对CPU调度器的一个提示,如果CPU调度器没有忽略这个提示,它会导致线程上下文的切换。
- sleep会使线程短暂block,会在给定的时间内释放CPU资源。
- yield会使RUNNING状态的Thread进人RUNNABLE状态(如果CPU调度器没有忽略这个提示的话)。
- sleep几乎百分之百地完成了给定时间的休眠,而yield的提示并不能一定担保。
- 一个线程sleep另一个线程调用interrupt 会捕获到中断信号,而yield则不会。
三、线程优先级介绍
进程有进程的优先级,线程同样也有优先级,理论上是优先级比较高的线程会获取优先被CPU调度的机会,但是事实上往往并不会如你所愿,设置线程的优先级同样也是一个(提示)hint操作,具体如下。
- 对于root用户,它会hint操作系统你想要设置的优先级别,否则它会被忽略。
- 如果CPU比较忙,设置优先级可能会获得更多的CPU时间片,但是闲时优先级的高低几乎不会有任何作用。
所以,不要在程序设计当中企图使用线程优先级绑定某些特定的业务,或者让业务严重依赖于线程优先级。
设置优先级的方法是setPriority
。MAX_PRIORITY
和MIN_PRIORITY
分别等于10
和 1
。
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);
}
}
四、获取线程 ID
public long getId()
获取线程的唯一ID, 线程的ID在整个JVM进程中都会是唯一的,并且是从0开始逐次递增。如果你在main线程( main函数)中创建了一个唯一的线程, 并且调用getld()后发现其并不等于0,因为在一个JVM进程启动的时候,实际上是开辟了很多个线程,自增序列已经有了一定的消耗,因此我们自己创建的线程绝非第0号线程。
线程在创建的时候,会执行init方法,在最后会执行tid = nextThreadID();
在调用getId()
方法后会返回一个tid
。
public long getId() {
return tid;
}
五、获取当前线程
public static Thread currentThread()
用于返回当前执行线程的引用,这个方法虽然很简单,但是使用非常广泛,来看一段示例代码:
System.out.println(Thread.currentThread());
输出Thread[main,5,main]
。
六、设置线程上下文类加载器
public ClassLoader getContextClassLoader()
获取线程上下文的类加载器,简单来说就是这个线程是由哪个类加器加载的,如果是在没有修改线程上下文类加载器的情况下,则保持与父线程同样的类加载器。
public void setContextClassLoader(ClassLoader cI)
设置该线程的类加载器,这个方法可以打破JAVA类加载器的父委托机制,有时候该方法也被称为JAVA类加载器的后门。
七、线程 interrupt
线程interrupt,是一个非常重要的API,也是经常使用的方法,与线程中断相关的API有如下几个
1. public void interrupt()
2. public static boolean interrupted()
3. public boolean isInterrupted()
7.1 interrupt
如下方法的调用会使得当前线程进人阻塞状态,而调用当前线程的interrupt方法,就可以打断阻塞。
- 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方法,则会打断这种阻塞,因此这种方法有时会被称为可中断方法,记住,打断一个线程并不等于该线程的生命周期结束,仅仅是打断了当前线程的阻塞状态。
一旦线程在阻塞的情况下被打断,都会抛出一个称为InterruptedException的异常,这个异常就像一个signal (信号)一样通知当前线程被打断了。
interrupt这个方法到底做了什么样的事情呢?
在一个线程内部存在着名为interrupt flag的标识,如果-一个线程被interrupt,那么它的flag将被设置,但是如果当前线程正在执行可中断方法被阻塞时,调用interrupt方法将其中断,反而会导致flag被清除,关于这点我们在后面还会做详细的介绍。另外有一点需要注意的是,如果一个线程已经是死亡状态,那么尝试对其的interrupt会直接被忽略。
7.2 isInterrupted
isInterrupted是Thread的一个成员方法,它主要判断当前线程是否被中断,该方法仅仅是对interrupt标识的一一个判断,并不会影响标识发生任何改变。
7.3 interrupted
interrupted是一个静态方法,虽然其也用于判断当前线程是否被中断,但是它和成员方法isInterrupted还是有很大的区别的,调用该方法会直接擦除掉线程的interrupt标识,需要注意的是,如果当前线程被打断了,那么第一次调用interrupted方法会返回true,并且立即擦除了interrupt标识;第二次包括以后的调用永远都会返回false,除非在此期间线程又一次地被打断。
八、线程join
Thread的join方法同样是一个非常重要的方法,使用它的特性可以实现很多比较强大的功能,与sleep一样它也是一个可中断的方法,也就是说,如果有其他线程执行了对当前线程的interrupt操作,它也会捕获到中断信号,并且擦除线程的interrupt标识,Thread的API为我们提供了三个不同的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
package com.example.demo.thread;
/**
* @author : pengweiwei
* @date : 2020/2/2 8:19 下午
*/
public class Thread1 extends Thread {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread1 runing");
}
}
package com.example.demo.thread;
/**
* @author : pengweiwei
* @date : 2020/2/2 8:19 下午
*/
public class Thread2 extends Thread {
@Override
public void run() {
System.out.println("thread2 runing");
}
}
package com.example.demo.thread;
/**
* @author : pengweiwei
* @date : 2020/2/2 5:35 下午
*/
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
Thread1 t1 = new Thread1();
Thread2 t2 = new Thread2();
t1.start();
//主线程到这里会阻塞3秒,等待t1执行完成,主线程才会继续执行
t1.join();
t2.start();
}
}
join方法会使当前线程永远地等待下去,直到期间被另外的线程中断,或者join的线程执行结束,当然你也可以使用join的另外两个重载方法,指定毫秒数,在指定的时间到达之后,当前线程也会退出阻塞。