无论继承Thread类还是实现Runnable接口,最终启动线程调用的都是Thread类的start方法
Thread类就是JVM描述管理线程的类,每个线程都对应唯一的一个Thread对象
1. 构造方法| 方法 | 说明 |
|---|---|
| Thread() | 创建线程对象 |
| Thread(Runnable target) | 使用 Runnable 对象创建线程对象 |
| Thread(String name) | 创建线程对象,并命名 |
| Thread(Runnable target, String name) | 使用 Runnable 对象创建线程对象,并命名 |
| Thread(ThreadGroup group, Runnable target) | 线程可以被用来分组管理,分好的组即为线程组,这 个目前我们了解即可 |
代码示例:
public static void main(String[] args) {
Thread t1 = new Thread();//一般搭配子类使用,需要有一个继承了Thread的子类,父类中的run方法什么都没干
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("传入Runnable对象");
}
});
Thread t3 = new Thread("Taylor线程");
Thread t4 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("传入Runnable对象和线程名字");
}
}, "Dua线程");
}
2. Thread类的核心属性
| 属性 | 获取方法 |
|---|---|
| ID | getId() |
| 名称 | getName() |
| 状态 | getState() |
| 优先级 | getPriority() |
| 是否后台线程 | isDaemon() |
| 是否存活 | isAlive() |
| 是否被中断 | isInterrupted() |
关于各个属性的解释:
- ID 是线程的唯一标识,不同线程不会重复
- 名称是各种调试工具用到
- 状态表示线程当前所处的一个情况,下面我们会进一步说明
- 优先级高的线程理论上来说更容易被调度到
- 关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行。
- 是否存活,可以简单的理解为 run 方法是否运行结束了
- 线程的中断问题,下面我们进一步说明
注意:
优先级越高的线程越有可能被CPU优先执行,注意Java程序只是建议先执行优先级高的线程,到底是否按照优先级执行,由OS说了算
代码测试:
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "I'm alive");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println(Thread.currentThread().getName() + "I'm going to die");
}
}, "TestThread");
// --------------------------以下为主线程--------------------------------
System.out.println(Thread.currentThread().getName() + "的ID: " + thread.getId());
System.out.println("状态:" + Thread.currentThread().getState());
System.out.println("优先级:" + Thread.currentThread().getPriority());
System.out.println("后台线程与否?:" + Thread.currentThread().isDaemon());
System.out.println("主线程存活与否?:" + Thread.currentThread().isAlive());
System.out.println("子线程存活与否?:" + thread.isAlive());
thread.start();
}
// 执行结果
main的ID: 11
状态:RUNNABLE // 表示该线程正在执行
优先级:5 // 一般优先级,一般线程的优先级都是用一个整数来代替
后台线程与否?:false
主线程存活与否?:true
子线程存活与否?:false // 此时子线程还没有启动
TestThreadI'm alive
...
TestThreadI'm going to die
3. 启动线程
启动线程调用的是Thread的start方法
问题:若同一个线程调用多次start方法会怎么样?
当线程已经启动之后再次调用该线程的start方法就会抛出此异常,说明该线程已经启动过了
4. 中断线程(重要)中断线程是线程间的通信的一种方式
普通线程会在run方法执行结束之后自动停止,而中断线程就是中断一个正在执行的线程(即run方法还没有执行结束),
中断线程有两种方式:
a. 通过共享变量进行中断
public class ThreadInterruptByVar {
private static class MyThread implements Runnable {
// 多个线程都会用到的变量加上volatile关键字
// 当前线程是否需要停止
volatile boolean isQuit = false;
@Override
public void run() {
while(!isQuit) {
System.out.println(Thread.currentThread().getName() + "Taylor的一对一演唱会进行中~");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println(Thread.currentThread().getName() + "被迫终止了!");
}
}
public static void main(String[] args) throws InterruptedException {
MyThread mt = new MyThread();
Thread thread = new Thread(mt, "Taylor的私人演唱会");
thread.start();
// 演唱会进行了3s之后,Dua中断了演唱会说:让我来!
Thread.sleep(3000);
System.out.println("Dua突然闯进来说想为我唱首歌!!!");
mt.isQuit = true;
}
}
// --------执行结果---------
Taylor的私人演唱会Taylor的一对一演唱会进行中~
Taylor的私人演唱会Taylor的一对一演唱会进行中~
Taylor的私人演唱会Taylor的一对一演唱会进行中~
Dua突然闯进来说想为我唱首歌!!!
Taylor的私人演唱会被迫终止了!
仔细观察会发现我们在子线程和主线程中都使用了sleep方法,而该方法是个静态方法,那么程序怎么知道是谁调用了该方法呢?
Thread类的所有静态方法都是在哪个线程中调用的,就生效在哪个线程,互不干扰
b. interrupt()方法结合 Thread.interrupted()静态方法或Thread对象的成员方法isInterrupted()的使用
调用线程对象的interrupt()方法,就会将指定线程对象的状态置为中断状态
第一种方法,Thread类内部包含了一个方法Thread.interrupted(),用于判断当前线程是否被中断
public class ThreadInterruptedByMethod {
private static class MyRunnable implements Runnable {
@Override
public void run() {
//静态方法,判断当前线程是否被中断了
while(!Thread.interrupted()) {
System.out.println(Thread.currentThread().getName() + "进行中!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//当线程被中断时,会抛出中断异常
//线程抛出中断异常之后,中断状态就会还原,这也是为什么我们在这里要调用break的原因
System.err.println("ED突然闯入说他也想高歌一曲");
break;// 如果不调用break,中断状态清除之后,再次进入死循环
}
}
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnable mt = new MyRunnable();
Thread thread = new Thread(mt, "Taylor的私人演唱会");
System.out.println("今天是个开心的日子,Taylor的私人演唱会要开始了~~");
System.out.println("哇哦,演唱会开始了,芜湖~");
thread.start();
Thread.sleep(5000);
//ED的突然闯入中断了演唱会
thread.interrupt();
}
}
第二种方法,把子线程中的静态方法替换为成员方法Thread.currentThread().isInterrupted()
//成员方法,判断当前线程对象是否被中断
while(!Thread.currentThread().isInterrupted()) {
System.out.println(Thread.currentThread().getName() + "进行中!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//当线程被中断时,会抛出中断异常
System.err.println("ED突然闯入说他也想高歌一曲");
break;
}
}
System.out.println(Thread.currentThread().getName() + "变成了ED和Taylor的二对一演唱会,芜湖~");
两种方法的执行结果都是一致的:
今天是个开心的日子,Taylor的私人演唱会要开始了~~ 哇哦,演唱会开始了,芜湖~ Taylor的私人演唱会进行中! Taylor的私人演唱会进行中! Taylor的私人演唱会进行中! Taylor的私人演唱会进行中! Taylor的私人演唱会进行中! ED突然闯入说他也想高歌一曲 Taylor的私人演唱会变成了ED和Taylor的二对一演唱会,芜湖~
既然执行的结果相同,那这静态方法和成员方法之间有什么区别?
这要从线程收到中断通知的方式说起
线程收到内置的中断通知(即执行interrupt方法)后有两种情况:
- 当线程调用sleep/wait/join等方法处在阻塞状态时,收到中断通知thread.interrupt()
收到中断通知后,就会抛出一个中断异常InterruptedException
当捕获这个异常后,无论是静态方法还是成员方法,当前线程的中断状态都会被重置
- 如果没有调用以上三种方法时,线程处于正常运行状态,收到中断通知thread.intrerupt() ,这种情况下调用静态方法和成员方法的区别就出来了
Thread.interrupted():判断当前线程是否被中断,若中断状态为true,清除中断标志
Thread.currentYhread.isInterrupted():判断指定线程对象是否状态为中断状态,若状态为true,不会清除中断标志
代码示例1:
public class ThreadDoubleMethod {
private static class MyRun implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
//通过静态方法输出中断状态
System.out.println(Thread.interrupted());
}
}
}
public static void main(String[] args) {
MyRun myRun = new MyRun();
Thread thread = new Thread(myRun);
thread.start();
thread.interrupt();
}
}
// 执行结果 - 使用静态方法时收到通知,第一次输出true,然后中断状态清除输出false
true
false
false
false
false
代码示例2:
public class ThreadDoubleMethod {
private static class MyRun implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
//通过普通成员方法输出中断状态
System.out.println(Thread.currentThread().isInterrupted());
}
}
}
public static void main(String[] args) {
MyRun myRun = new MyRun();
Thread thread = new Thread(myRun);
thread.start();
thread.interrupt();
}
}
// 执行结果 - 使用成员方法时收到通知,中断状态不会被清除,因此一直输出true
true
true
true
true
true
5. 线程的等待使用线程对象.join():在哪个线程中调用别的线程对象的join方法,意思就是这个线程要等待另一个线程执行完毕再继续执行本线程的后续代码
比如说:当主线程中调用了thread1.join(),主线程就会进入阻塞状态,直到thread1执行结束,主线程才会继续向后执行
代码示例1:
public class ThreadJoin {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ":还在学习JavaSE阶段...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "JavaSE线程");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ":进入数据结构阶段...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "数据结构线程");
// 串行执行
System.out.println("先学习JavaSE部分");
t1.start();
t1.join();
//此时走到这里,t1线程已经执行结束,再启动t2线程
t2.start();
t2.join();
//此时t2也执行结束了,恢复执行主线程
System.out.println("开始学习JavaEE");
}
}
// 执行结果 - 在哪个线程中调用join方法,就阻塞哪个线程(注意不是阻塞调用join方法的线程对象)
先学习JavaSE部分
JavaSE线程:还在学习JavaSE阶段...
JavaSE线程:还在学习JavaSE阶段...
JavaSE线程:还在学习JavaSE阶段...
JavaSE线程:还在学习JavaSE阶段...
JavaSE线程:还在学习JavaSE阶段...
数据结构线程:进入数据结构阶段...
数据结构线程:进入数据结构阶段...
数据结构线程:进入数据结构阶段...
数据结构线程:进入数据结构阶段...
数据结构线程:进入数据结构阶段...
开始学习JavaEE
join方法的重载方法:
| 方法 | 说明 |
|---|---|
| public void join() | 等待线程结束,等别人run方法执行完才继续执行 |
| public void join(long millis) | 等待线程结束,最多等 millis 毫秒,别的线程没跑完也不等了 |
| public void join(long millis, int nanos) | 同理,但可以更高精度 |
代码示例2:
public class ThreadJoin {
public static void main(String[] args) throws InterruptedException {
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ":进入数据结构阶段...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "数据结构线程");
t2.start();
//主线程只等t2等2000ms,无论t2在这时间内有没有结束,主线程都会恢复执行
t2.join(2000);
System.out.println("开始学习JavaEE");
}
}
// 执行结果
数据结构线程:进入数据结构阶段...
数据结构线程:进入数据结构阶段...
开始学习JavaEE
数据结构线程:进入数据结构阶段...
数据结构线程:进入数据结构阶段...
数据结构线程:进入数据结构阶段...
6. 获取当前正在执行的线程对象
Thread.currentThread()的作用:获取正在执行的线程对象,该方法我们在前面已经使用很多了
public static void main(String[] args) {
Thread thread = new Thread(() -> System.out.println(Thread.currentThread().getName()), "子线程");
thread.start();
System.out.println(Thread.currentThread().getName());
}
// 执行结果
main
子线程
7. 休眠当前线程
Thread.sleep(long millis) 在哪里调用,就休眠哪个线程



