目录
1.Thread类概念
Thread 类是 JVM 用来管理线程的一个类,换句话说,每个线程都有一个唯一的 Thread 对象与之关联。
每个执行流,也需要有一个对象来描述,类似下图所示,而 Thread 类的对象 就是用来描述一个线程执行流的, JVM 会将这些 Thread 对象组织起来,用于线程调度,线程管理。
2.Thread的常见构造方法
方法 |
说明 |
Thread() |
创建线程对象 |
Thread(Runnable target) |
使用 Runnable 对象创建线程对象 |
Thread(String name) |
创建线程对象,并命名 |
Thread(Runnable target, String name) |
使用 Runnable 对象创建线程对象,并命名 |
Thread(ThreadGroup group, Runnable target) |
线程可被用来分组管理,分好的组为线程组, |
Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("这是我的名字");
Thread t4 = new Thread(new MyRunnable(), "这是我的名字");
3.Thread的几个常见属性
属性 |
获取方法 |
ID |
getId() |
名称 |
getName() |
状态 |
getState() |
优先级 |
getPriority() |
是否后台线程 |
isDaemon() |
是否存活 |
isAlive() |
是否被中断 |
isInterrupted() |
注意:
ID 是线程的唯一标识,不同线程不会重复
名称是各种调试工具用到
状态表示线程当前所处的一个情况,之后我会进一步说明
关于后台线程,需要记住一点: JVM会在一个进程的所有非后台线程结束后,才会结束运行。
是否存活,即简单的理解,为 run 方法是否运行结束了
线程的中断问题,下面我们进一步说明
4.启动一个线程—start( )
之前我们已经看到了如何通过覆写 run 方法创建一个线程对象,但线程对象被创建出来并不意味着线程就开始运行了。
覆写 run 方法是提供给线程要做的事情的指令清单
创建线程对象就可以认为是把张三、李四喊过来了
而调用start()方法则就是喊一声,行动起来!现成才去真正独立的执行了
5.中断一个线程
李四一旦进到工作状态,他就会按照行动指南上的步骤去进行工作,不完成是不会结束的。但有时我们需要增加一些机制,例如老板突然来电话了,说转账的对方是个骗子,需要赶紧停止转账,那张三该如 何通知李四停止呢?这就涉及到我们的停止线程的方式了。
目前常见的有以下两种方式:
1.使用自定义的变量来作为标志位
需要给标志位上加 volatile 关键字(保证内存可见性)
public class InterruptTest {
public static volatile boolean isQuit = false;
static class RunnableT implements Runnable {
@Override
public void run() {
while (!isQuit) {
System.out.println(Thread.currentThread().getName()
+ "张三正在操作转账!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("及时中断转账!");
}
}
public static void main(String[] args) throws InterruptedException {
RunnableT runnableT = new RunnableT();
Thread thread = new Thread(runnableT);
thread.start();
Thread.sleep(3000);
System.out.println("老板来电话,得知对面是骗子!");
isQuit = true;
}
}
运行结果:
2.使用interrupt()
Thread内部包含了一个boolean类型的变量作为线程是否被中断的标记
方法 |
说明 |
public void interrupt() |
中断对象关联的线程,如果线程正在阻塞,则以异常方式通知, 否则设置标志位 |
public static boolean interrupted() |
判断当前线程的中断标志位是否设置,调用后清除标志位 |
public boolean isInterrupted() |
判断对象关联的线程的标志位是否设置,调用后不清除标志位 |
public class InterruptTest {
public static volatile boolean isQuit = false;
static class RunnableT implements Runnable {
@Override
public void run() {
while (!Thread.interrupted()) {
//while (!Thread.currentThread().isInterrupted()) {
System.out.println(Thread.currentThread().getName()
+ "正在操作转账!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
System.out.println("及时中断转账!");
}
}
public static void main(String[] args) throws InterruptedException {
RunnableT runnableT = new RunnableT();
Thread thread = new Thread(runnableT,"张三:");
thread.start();
Thread.sleep(3000);
System.out.println("老板来电话,得知对面是骗子!");
thread.interrupt();
}
}
运行结果:
thread 收到通知的方式有两种:
1. 如果线程因为调用 wait/join/sleep 等方法而阻塞挂起,则以 InterruptedException 异常的形式通知,清除中断标志
当出现 InterruptedException 的时候, 要不要结束线程取决于 catch 中代码的写法. 可以选择忽略这个异常, 也可以跳出循环结束线程
2. 否则,只是内部的一个中断标志被设置,thread 可以通过Thread.interrupted() 判断当前线程的中断标志被设置,清除中断标志,Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志
这种方式通知收到的更及时,即使线程正在 sleep 也可以马上收到。
3.观察标志位是否被清除
interrupt()方法:表示可以中断线程,实际上只是给线程设置一个中断标志,但是线程依旧会执行。
interrupted()方法:Thread类的静态方法。检查当前线程的中断标志,返回一个boolean并清除中断状态,其连续两次调用的返回结果不一样,因为第二次调用的时候线程的中断状态已经被清除,会返回一个false。
isInterrupted()方法:测试线程是否被中断,不会清除中断状态。
使用Thrad.interrupted(),线程中断会清除标志位
public class InterruptTest2 {
private static class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.interrupted());
}
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnable target = new MyRunnable();
Thread thread = new Thread(target, "李四");
thread.start();
thread.interrupt();
}
}
只有一开始是true,后面都是false,因为标志位被清除
使用Thread.currentThread().isInterrupted(),线程中断标记位不会被清除
public class InterruptTest2 {
private static class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().isInterrupted());
}
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnable target = new MyRunnable();
Thread thread = new Thread(target, "李四");
thread.start();
thread.interrupt();
}
}
全部都是true,因为标志位没有被清除
6.等待一个线程-join()
有时,我们需要等待一个线程完成它的工作后,才能进行自己的下一步工作。这时我们需要一个方法明确等待线程的结束。
public class JoinTest {
public static void main(String[] args) throws InterruptedException {
Runnable target = () -> {
for (int i = 0; i < 10; i++) {
try {
System.out.println(Thread.currentThread().getName()
+ ": 我还在工作! ");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + ": 我结束了! ");
};
Thread thread1 = new Thread(target, "李四");
Thread thread2 = new Thread(target, "王五");
System.out.println("先让李四开始工作");
thread1.start();
thread1.join();
System.out.println("李四工作结束了,让王五开始工作");
thread2.start();
thread2.join();
System.out.println("王五工作结束了");
}
}
附录
方法 |
说明 |
public void join() |
等待线程结束 |
public void join(long millis) |
等待线程结束,最多等 millis 毫秒 |
public void join(long millis, intnanos) |
同理,但可以更高精度 |
7.获取当前线程引用
方法 |
说明 |
public static Thread currentThread(); |
返回当前线程对象的引用 |
8.休眠当前线程
也是我们比较熟悉一组方法,有一点要记得,因为线程的调度是不可控的,所以,这个方法只能保证实 际休眠时间是大于等于参数设置的休眠时间的。
方法 |
说明 |
public static void sleep(long millis) throws InterruptedException |
休眠当前线程 millis 毫秒 |
public static void sleep(long millis, intnanos) throws InterruptedException |
可以更高精度的休眠 |