Thread构造函数与API
线程的命名
Thread的构造函数中,有几个并没有提供为线程命名的参数,那么此时线程会有一个怎样的命名呢?
Thread()
Thread(Runnable target)
Thread(ThreadGroup group, Runnable target)
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
/* For autonumbering anonymous threads. */
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}
如果没有为线程显式地指定一个名字,那么线程将会以Thread-作为前缀与一个自增数字进行组合,这个自增数字在整个JVM进程中将会不断自增:
import java.util.stream.IntStream;
public class Test {
public static void main(String[] args) {
IntStream.range(0, 5).boxed().map(i -> new Thread(
() -> System.out.println(Thread.currentThread().getName())
)).forEach(Thread::start);
}
}
输出结果:
Thread-0
Thread-4
Thread-2
Thread-3
Thread-1
可以使用Thread的构造函数对线程进行命名:
Thread(String name)
Thread(Runnable target, String name)
Thread(ThreadGroup group, String name)
Thread(ThreadGroup group, Runnable target, String name)
Thread(ThreadGroup group, Runnable target, String name, long stackSize)
不论你使用的是默认的函数命名规则,还是指定了一个特殊的名字,在线程启动之前还有一个机会可以对其进行修改,一旦线程启动,名字将不再被修改。
import java.util.stream.IntStream;
public class Test {
private final static String PREFIX = "ThreadName-";
private static Thread createThread(final int name) {
return new Thread(
() -> System.out.println(Thread.currentThread().getName()), PREFIX + name
);
}
public static void main(String[] args) {
// 创建实例,覆写run方法(run方法中调用createThread方法)
IntStream.range(0, 5).mapToObj(Test::createThread).forEach(Thread::start);
}
}
输出结果:
ThreadName-0
ThreadName-3
ThreadName-4
ThreadName-2
ThreadName-1
线程的父子关系
Thread的所有构造函数,最终都会去调用一个静态方法init:
public class Thread implements Runnable {
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
/**
* Initializes a Thread.
*/
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name.toCharArray();
// 获取当前线程作为父线程
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
// ...
}
}
currentThread()是获取当前线程,在线程生命周期中,线程的最初状态为NEW,没有执行start方法之前,它只能算是一个Thread的实例,并不意味着一个新的线程被创建。因此currentThread()代表的将会是创建当前线程的那个线程:
- 一个线程的创建,是由另一个线程完成的。
- 被创建线程的父线程,是创建它的线程。
main函数所在的线程是由JVM创建的,也就是main线程,意味着前面创建的所有线程,其父线程都是main线程。
守护线程
当在一个JVM进程里面开多个线程时,这些线程被分成两类:守护线程和非守护线程。默认开的都是非守护线程!
- 线程分为用户线程和守护(daemon)线程;
-
虚拟机必须确保用户线程(如main线程)执行完毕;
-
虚拟机不用等待守护线程(如垃圾回收线程)执行完毕;
-
- 守护线程应用:后台记录操作日志、监控内存、垃圾回收等。
在没有调用System.exit()方法的前提下,若主线程结束后,JVM中没有一个非守护线程(全是守护线程),则JVM的进程会退出:
The Java Virtual Machine exits when the only threads running are all daemon threads.
public class DaemonThread {
public static void main(String[] args) throws InterruptedException {
// main线程开始,由JVM启动
// 1. 创建thread线程
/**
* 调用thread线程,程序不会退出
* 不退出是因为while (true),使得线程一直没有结束运行
*/
Thread thread = new Thread (
() -> {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
);
/**
* 调用thread1线程,main线程会先结束,程序等待线程结束后,才会退出
* 因为剩余都是守护线程
*/
Thread thread1 = new Thread() {
@Override
public void run() {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
// 2. 将thread设置为守护线程
thread.setDaemon(true);
// 3. 启动thread线程
thread1.start();
Thread.sleep(2_000L);
// 4. main 线程结束
System.out.println("main thread finished lifecycle.");
}
}
如果没有将thread设置为守护线程,则JVM进程永远不会退出,即使main线程正常地结束了自己的生命周期(main线程的生命周期是从注释0到注释4之间的那段代码),原因就是在JVM进程中还存在一个非守护线程在运行。
如果将thread设置为守护线程:thread.setDaemon(true);,那么main进程结束生命周期后,JVM也会随之退出运行,thread线程也会结束。
守护线程具备自动结束生命周期的特性,而非守护线程则不具备这个特点。守护线程经常用作执行一些后台任务,当你希望关闭某些线程,或者退出JVM进程的时候,一些线程能够自动关闭,此时就可以考虑用守护线程为你完成这样的工作。
线程sleep
public class Thread implements Runnable {
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锁的所有权!
import java.util.concurrent.TimeUnit;
/**
* 在主线程和自定义的线程中进行休眠
*/
public class ThreadSleep {
public static void main(String[] args) throws InterruptedException {
new Thread(
() -> {
long startTime = System.currentTimeMillis();
sleep(2_000L);
long endTime = System.currentTimeMillis();
System.out.println(String.format("Total spend %d ms", (endTime - startTime)));
}
).start();
long startTime = System.currentTimeMillis();
TimeUnit.SECONDS.sleep(3);
long endTime = System.currentTimeMillis();
System.out.println(String.format("main thread total spends %d ms", (endTime - startTime)));
}
private static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在JDK 1.5 以后,JDK引入了一个枚举TimeUnit,其对sleep方法提供了很好的封装,使用它可以省去时间单位的换算步骤:TimeUnit.SECONDS.sleep(3);
线程yield
yield方法属于一种启发式的方法,其会提醒调度器我愿意放弃当前的CPU资源,如果CPU的资源不紧张,则会忽略这种提醒。即,yield只是一个提示(hint),CPU调度器并不会担保每次都能满足yield提示。
调用yield方法会使当前线程从RUNNING状态切换到RUNNABLE状态,一般这个方法不太常用。
yield与sleep的区别
在JDK 1.5以前的版本中yield的方法事实上是调用了sleep(0),但是它们之间存在着本质的区别:
-
sleep会导致当前线程暂停指定的时间,没有CPU时间片的消耗;而yield只是对CPU调度器的一个提示,如果CPU调度器没有忽略这个提示,它会导致线程上下文的切换。
-
sleep会使线程短暂block,会在给定的时间内释放CPU资源;yield会使
RUNNING状态的Thread进入RUNNABLE状态(如果CPU调度器没有忽略这个提示的话)。 -
sleep几乎百分之百地完成了给定时间的休眠,而yield的提示并不能一定担保。
-
一个线程sleep,另一个线程调用interrupt会捕获到中断信号,而yield则不会。
设置线程优先级
// 设置优先级
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);
}
}
// 获取线程优先级
public final int getPriority() {
return priority;
}
理论上是优先级比较高的线程会获取优先被CPU调度的机会,但是事实上设置线程的优先级同样也是一个hint操作!
如果CPU比较忙,设置优先级可能会获得更多的CPU时间片,但是闲时优先级的高低几乎不会有任何作用!
一般情况下,不会对线程设定优先级别,更不会让某些业务严重地依赖线程的优先级别,比如权重,借助优先级设定某个任务的权重,这种方式是不可取的,一般定义线程的时候使用默认的优先级就好了,那么线程默认的优先级是多少呢?
线程默认的优先级和它的父类保持一致,一般情况下都是5,因为main线程的优先级就是5,所以它派生出来的线程都是5。
线程join
join方法与sleep一样也是一个可中断的方法, 如果有其他线程执行了对当前线程的interrupt操作,它也会捕获到中断信号,并且擦除线程的interrupt标识!
join某个线程A(线程A插队),会使当前线程B进入等待,直到线程A结束生命周期,或者到达给定的时间:
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class ThreadJoin {
private static Thread create(int seq) {
// Thread(Runnable target, String name)
return new Thread(
() -> {
System.out.println("线程" + Thread.currentThread().getName() + "状态:"
+ Thread.currentThread().getState());
shortSleep();
}, String.valueOf(seq)
);
}
private static void shortSleep() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
System.out.println("线程" + Thread.currentThread().getName() + "在其他线程调用join前。");
// 1.定义两个线程
List<Thread> threads = IntStream.range(1, 3).mapToObj(ThreadJoin::create).collect(Collectors.toList());
// 2.启动两个线程
threads.forEach(Thread::start);
// 3.在main线程中,两个线程调用join方法
for (Thread thread : threads) {
thread.join();
}
// 4.main线程输出
System.out.println("线程" + Thread.currentThread().getName() + "在其他线程调用join后。");
}
}
输出结果:
线程main在其他线程调用join前。
线程1状态:RUNNABLE
线程2状态:RUNNABLE
线程main在其他线程调用join后。
线程interrupt
在多线程编程中,中断是一种协同机制,怎么理解这么高大上的词呢?
例如你妈妈叫你吃饭,你收到了中断游戏通知,但是否马上放下手中的游戏(收到中断信息,但如何处理需要具体设置)去吃饭看你心情 。在程序中怎样演绎这个心情就看具体的业务逻辑。
在多线程的场景中,有的线程可能自旋浪费资源,这时就可以用其他线程在恰当的时机给它个中断通知,被“中断”的线程,可以选择在恰当的时机选择跳出怪圈,最大化的利用资源。
线程中断指的是唤醒轻量级阻塞,而不是中断一个线程!它相当于给线程发送了一个唤醒的信号:
- 如果线程此时恰好处于
WAITING或者TIMED_WAITING状态,就会抛出一个InterruptedException,并且线程被唤醒。 - 而如果线程此时并没有被阻塞,则线程什么都不会做。但在后续,线程可以判断自己是否收到过其他线程发来的中断信号,然后做一些对应的处理!
interrupt()方法是 唯一一个可以将中断标志设置为true的方法,它是一个Thread类public的对象方法,所以任何线程对象都可以调用该方法,可以一个线程interrupt其他线程,也可以interrupt自己。其中,中断标识的设置是通过native方法 interrupt0 完成的。
public class Thread implements Runnable {
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // Just to set the interrupt flag
b.interrupt(this);
return;
}
}
interrupt0();
}
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
public boolean isInterrupted() {
return isInterrupted(false);
}
private native boolean isInterrupted(boolean ClearInterrupted);
// 参数ClearInterrupted主要用来控制是否擦除线程interrupt的标识。
}
并不是说一个线程运行到一半,把它中断了,然后抛出了InterruptedException异常,只有那些声明了会抛出InterruptedException的函数才会抛出异常:
public static native void sleep(long millis) throws InterruptedException;
public final void join() throws InterruptedException{}
public final void wait() throws InterruptedException{}
可中断方法
Object#wait、Thread#sleep、Thread#join、InterruptibleChannel的IO操作、Selector#wakeup等,会使得当前线程进入轻量级阻塞状态,若另外的一个线程调用被阻塞线程的interrupt方法,则会打断这种阻塞,因此这种方法有时会被称为可中断方法,打断一个线程并不等于该线程的生命周期结束,仅仅是打断了当前线程的阻塞状态。
一旦线程在阻塞的情况下被打断,都会抛出一个称为InterruptedException的异常,这个异常就像一个signal(信号)一样通知当前线程被打断了:
import java.util.concurrent.TimeUnit;
public class ThreadInterrupt {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(
() -> {
try {
// 当前线程处于阻塞状态,异常必须捕捉处理,无法往外抛出
TimeUnit.MINUTES.sleep(1);
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " is interrupted !");
}
}
);
thread.setName("Thread1");
thread.start();
// Short block and make sure thread is started
TimeUnit.SECONDS.sleep(1);
thread.interrupt();
}
}
上面的代码创建了一个线程,并且企图休眠1分钟,不过大约在1秒之后就被主线程调用interrupt方法打断!
在一个线程内部存在着名为interrupt flag的标识,如果一个线程被interrupt,那么它的flag将被设置!
处于运行期且非阻塞的状态的线程,无法被中断(没有调用可中断方法):
import java.util.concurrent.TimeUnit;
public class InterruptThread {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(
() -> {
while (true) {
System.out.println("未被中断");
}
}
);
thread.start();
TimeUnit.SECONDS.sleep(1);
thread.interrupt();
}
}
一直输出未被中断!
isInterrupted
isInterrupted是Thread的一个成员方法(非静态函数),它主要判断当前线程是否被中断,该方法仅仅是对interrupt标识的一个判断,并不会影响标识发生任何改变(只读取中断状态,不修改状态)!
import java.util.concurrent.TimeUnit;
public class ThreadisInterrupted1 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread() {
@Override
public void run() {
while (true) {
/**
* sleep是可中断方法,会捕获到中断信号,从而干扰结果
* 因为可中断方法捕获到了中断信号(signal)之后会擦除掉interrupt的标识
* 所以这里暂不使用可中断方法
*/
}
}
};
// 在线程启动前,将其设置为守护线程。防止JVM无法退出
thread.setDaemon(true);
thread.start();
TimeUnit.SECONDS.sleep(1);
System.out.printf("Thread is interrupted ? %s\n", thread.isInterrupted());
System.out.println("线程thread调用interrupt前的状态:" + thread.getState());
thread.interrupt();
System.out.printf("Thread is interrupted ? %s\n", thread.isInterrupted());
System.out.println("线程thread调用interrupt后的状态:" + thread.getState());
}
}
输出结果:
Thread is interrupted ? false
线程thread调用interrupt前的状态:RUNNABLE
Thread is interrupted ? true
线程thread调用interrupt后的状态:RUNNABLE
处于运行期且非阻塞的状态的线程,无法被中断(没有调用可中断方法),但中断标志位显示:线程被调用了中断方法,只是线程状态没有发生改变!
中断方法捕获到了中断信号(signal) 之后,也就是捕获了InterruptedException异常之后会擦除掉interrupt的标识:
import java.util.concurrent.TimeUnit;
public class ThreadisInterrupted {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread() {
@Override
public void run() {
while (true) {
/**
* 因为sleep是可中断方法,会捕获到中断信号
*/
try {
TimeUnit.MINUTES.sleep(1);
} catch (InterruptedException e) {
System.out.printf(Thread.currentThread().getName() + " is interrupted ?(此时在catch语句) %s\n", isInterrupted());
}
}
}
};
// 在线程启动前,将其设置为守护线程。防止JVM无法退出
thread.setDaemon(true);
thread.setName("thread1");
thread.start();
TimeUnit.SECONDS.sleep(1);
System.out.printf(thread.getName() + " is interrupted ? %s\n", thread.isInterrupted());
thread.interrupt();
TimeUnit.SECONDS.sleep(1);
System.out.printf(thread.getName() + " is interrupted ? %s\n", thread.isInterrupted());
}
}
输出结果:
thread1 is interrupted ? false
thread1 is interrupted ?(此时在catch语句) false
thread1 is interrupted ? false
由于在run方法中使用了sleep这个可中断方法,它会捕获到中断信号,并且会擦除interrupt标识,因此程序的执行结果都会是false!
可中断方法捕获到了中断信号之后(已经抛出异常,“重新做人了”——如果线程此时恰好处于WAITING或者TIMED_WAITING状态,就会抛出一个InterruptedException,并且线程被唤醒。),为了不影响线程中其他方法的执行,将线程的interrupt标识复位是一种很合理的设计。
interrupted
interrupted是一个静态方法,虽然其也用于判断当前线程是否被中断,但是它和成员方法isInterrupted有很大的区别的,调用该方法会直接擦除掉线程的interrupt标识。
如果当前线程被打断了,那么第一次调用interrupted方法会返回true,并且立即擦除interrupt标识;第二次包括以后的调用永远都会返回false,除非在此期间线程又一次地被打断。
import java.util.concurrent.TimeUnit;
public class Threadinterrupted {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread() {
@Override
public void run() {
while (true) {
System.out.printf(Thread.currentThread().getName() + " is interrupted ? %s\n", interrupted());
}
}
};
thread.setDaemon(true);
thread.start();
// Short block makes sure that the thread is started
TimeUnit.SECONDS.sleep(1);
thread.interrupt();
}
}
输出结果:
...
Thread-0 is interrupted ? false
Thread-0 is interrupted ? false
Thread-0 is interrupted ? true
Thread-0 is interrupted ? false
Thread-0 is interrupted ? false
Thread-0 is interrupted ? false
...
当你可能要被大量中断,并且你想确保只处理一次中断时,就可以使用这个方法。
如果一个线程,在没有执行可中断方法之前就被打断,那么其接下来将执行可中断方法,比如sleep会发生什么样的情况呢?
import java.util.concurrent.TimeUnit;
public class Test {
public static void main(String[] args) {
// 1. 判断当前线程是否被中断
System.out.println("main thread is interrupted ? " + Thread.interrupted());
// 2. 中断当前线程
Thread.currentThread().interrupt();
// 3. 判断当前线程是否被中断
// 此时线程设置的interrupt标识为true
System.out.println("main thread is interrupted ? " + Thread.currentThread().isInterrupted());
try {
// 4. 当前线程执行可中断方法
// 如果一个线程设置了interrupt标识,那么接下来的可中断方法会立即中断
TimeUnit.MINUTES.sleep(3);
} catch (InterruptedException e) {
// 5. 捕获中断信号
System.out.println("捕获中断信号");
}
}
}
输出结果:
main thread is interrupted ? false
main thread is interrupted ? true
捕获中断信号 // 立马输出而不是执行TimeUnit.MINUTES.sleep(3)
import java.util.concurrent.TimeUnit;
public class Test {
public static void main(String[] args) {
// 1. 判断当前线程是否被中断
System.out.println("main thread is interrupted ? " + Thread.interrupted()); // false
// 2. 中断当前线程
Thread.currentThread().interrupt();
// 3. 判断当前线程是否被中断
System.out.println("main thread is interrupted ? " + Thread.currentThread().isInterrupted()); // true
System.out.println("main thread is interrupted ? " + Thread.interrupted()); // true
System.out.println("main thread is interrupted ? " + Thread.interrupted()); // false
try {
// 10秒后程序结束
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
System.out.println("捕获中断信号");
}
}
}
中断机制的使用场景
- 点击某个桌面应用中的关闭按钮时(比如你关闭 IDEA,不保存数据直接中断好吗?);
- 某个操作超过了一定的执行时间限制需要中止时;
- 多个线程做相同的事情,只要一个线程成功其它线程都可以取消时;
- 一组线程中的一个或多个出现错误导致整组都无法继续时;
ThreadPoolExecutor中的shutdownNow方法会遍历线程池中的工作线程并调用线程的interrupt方法来中断线程。
ThreadGroup
Java中用ThreadGroup来表示线程组,可以使用线程组对线程进行批量控制。
ThreadGroup和Thread的关系:每个Thread必然存在于一个ThreadGroup中,Thread不能独立于ThreadGroup存在。执行main()方法线程的名字是main,如果在new Thread时没有显式指定,那么默认将父线程(当前执行new Thread的线程)线程组设置为自己的线程组。
在Thread的构造函数中,可以显式地指定线程的Group,也就是ThreadGroup:
public class Thread implements Runnable {
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) {
// ...
SecurityManager security = System.getSecurityManager();
if (g == null) {
/* Determine if it's an applet or not */
/* If there is a security manager, ask the security manager what to do. */
if (security != null) {
g = security.getThreadGroup();
}
/* If the security doesn't have a strong opinion of the matter use the parent thread group. */
if (g == null) {
g = parent.getThreadGroup();
}
}
}
}
如果在构造Thread的时候没有显示地指定一个ThreadGroup,那么子线程将会被加入父线程所在的线程组:
public class ThreadGroupConstruction {
public static void main(String[] args) {
// 创建线程thread1,不指定其所在ThreadGroup
Thread thread1 = new Thread("thread1");
// 创建ThreadGroup
ThreadGroup group = new ThreadGroup("TestGroup");
// 创建线程thread2,并指定其所在ThreadGroup
Thread thread2 = new Thread(group, "thread2");
ThreadGroup mainThreadGroup = Thread.currentThread().getThreadGroup();
System.out.println("main thread belongs to group: " + mainThreadGroup.getName());
System.out.println("thread1 belongs to group: " + thread1.getThreadGroup());
System.out.println("thread2 belongs to group: " + thread2.getThreadGroup().getName());
System.out.println();
System.out.println("thread1 and main thread belong to the same ThreadGroup ? " + (thread1.getThreadGroup() == mainThreadGroup));
System.out.println("thread2 and main thread belong to the same ThreadGroup ? " + (thread2.getThreadGroup() == mainThreadGroup));
System.out.println("thread1 and thread2 belong to the same ThreadGroup ? " + (thread1.getThreadGroup() == thread2.getThreadGroup()));
}
}
结果输出:
main thread belongs to group: main
thread1 belongs to group: java.lang.ThreadGroup[name=main,maxpri=10]
thread2 belongs to group: TestGroup
thread1 and main thread belong to the same ThreadGroup ? true
thread2 and main thread belong to the same ThreadGroup ? false
thread1 and thread2 belong to the same ThreadGroup ? false
默认情况下,新的线程都会被加入到main线程所在的group中,main线程的group名字同线程名:
- main线程所在的ThreadGroup称为main;
- 构造一个线程的时候如果没有显式地指定ThreadGroup,那么它将会和父线程同属于一个ThreadGroup。
import java.util.Arrays;
public class ThreadGroupActive {
public static void main(String[] args) {
Thread thread = new Thread(
() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
);
thread.start();
ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
System.out.println(threadGroup.activeCount());
Thread[] threads = new Thread[threadGroup.activeCount()];
threadGroup.enumerate(threads);
Arrays.asList(threads).forEach(System.out::println);
}
}
输出结果:
3
Thread[main,5,main]
Thread[Monitor Ctrl-Break,5,main]
Thread[Thread-0,5,main]

浙公网安备 33010602011771号