> 文章列表 > 二、线程的Thread 类及常见方法【2/12】【多线程】

二、线程的Thread 类及常见方法【2/12】【多线程】

二、线程的Thread 类及常见方法【2/12】【多线程】

线程的Thread 类及常见方法

  • 2. Thread 类及常见方法
    • 2.1 Thread 的常见构造方法
    • 2.2 Thread 的几个常见属性
    • 2.3 启动一个线程-start()
            • ★★★start和run的区别★★★
            • run不会创建线程 是在原来的基础上 执行代码
            • start 创建线程,在新的线程 执行代码
    • 2.4 中断一个线程
      • ★★★1. 使用自定义的变量来作为标志
      • ★★★2. Thread.interrupted() 或者Thread.currentThread().isInterrupted() 代替自定义标志位
        • ★3. 标志位的清除
            • Thread.isInterrupted() 和 Tread.interrupted 清楚
            • 而加上 currentThread 表示不清楚
    • 2.5 等待一个线程-join()
            • ★★★哪个线程join 哪个线程先执行
    • 2.6 获取当前线程引用
    • 2.7 休眠当前线程

2. Thread 类及常见方法

Thread 类是 JVM 用来管理线程的一个类,换句话说,每个线程都有一个唯一的 Thread 对象与之关联。
用我们上面的例子来看,每个执行流,也需要有一个对象来描述,类似下图所示,而 Thread 类的对象就是用来描述一个线程执行流的,JVM 会将这些 Thread 对象组织起来,用于线程调度,线程管理
二、线程的Thread 类及常见方法【2/12】【多线程】

2.1 Thread 的常见构造方法

二、线程的Thread 类及常见方法【2/12】【多线程】二、线程的Thread 类及常见方法【2/12】【多线程】
二、线程的Thread 类及常见方法【2/12】【多线程】

Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("这是我的名字");
Thread t4 = new Thread(new MyRunnable(), "这是我的名字");

2.2 Thread 的几个常见属性

属性 获取方法
ID getId()
名称 getName()
状态 getState()
优先级 getPriority()
是否后台线程(主线程执行完便结束) isDaemon()
是否存活 isAlive()
是否被中断 isInterrupted()

二、线程的Thread 类及常见方法【2/12】【多线程】

  1. ID 是线程的唯一标识,不同线程不会重复
  2. 名称是各种调试工具用到
  3. 状态表示线程当前所处的一个情况,下面我们会进一步说明
  4. 优先级高的线程理论上来说更容易被调度到
  5. 关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行。
  6. 是否存活,即简单的理解,为 run 方法是否运行结了
  7. 线程的中断问题,下面我们进一步说明
public class ThreadDemo {public static void main(String[] args) {Thread thread = new Thread(() -> {for (int i = 0; i < 10; i++) {try {System.out.println(Thread.currentThread().getName() + ": 我还
活着");Thread.sleep(1 * 1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName() + ": 我即将死去");});System.out.println(Thread.currentThread().getName() + ": ID: " + thread.getId());System.out.println(Thread.currentThread().getName() + ": 名称: " + thread.getName());System.out.println(Thread.currentThread().getName() + ": 状态: " + thread.getState());System.out.println(Thread.currentThread().getName() + ": 优先级: " + thread.getPriority());System.out.println(Thread.currentThread().getName() + ": 后台线程: " + thread.isDaemon());System.out.println(Thread.currentThread().getName() + ": 活着: " + thread.isAlive());System.out.println(Thread.currentThread().getName() + ": 被中断: " + thread.isInterrupted());thread.start();while (thread.isAlive()) {}System.out.println(Thread.currentThread().getName() + ": 状态: " + thread.getState());}
}

2.3 启动一个线程-start()

之前我们已经看到了如何通过覆写 run 方法创建一个线程对象,但线程对象被创建出来并不意味着线程就开始运行了。

  • 覆写 run 方法是提供给线程要做的事情的指令清单
  • 线程对象可以认为是把 李四、王五叫过来了
  • 而调用 start() 方法,就是喊一声:”行动起来!“,线程才真正独立去执行了。
    二、线程的Thread 类及常见方法【2/12】【多线程】
    调用 start 方法, 才真的在操作系统的底层创建出一个线程
★★★start和run的区别★★★
run不会创建线程 是在原来的基础上 执行代码
start 创建线程,在新的线程 执行代码

二、线程的Thread 类及常见方法【2/12】【多线程】

2.4 中断一个线程

李四一旦进到工作状态,他就会按照行动指南上的步骤去进行工作,不完成是不会结束的。但有时我们需要增加一些机制,例如老板突然来电话了,说转账的对方是个骗子,需要赶紧停止转账,那张三该如何通知李四停止呢?这就涉及到我们的停止线程的方式了。

目前常见的有以下两种方式:

  1. 通过共享的标记来进行沟通
  2. 调用 interrupt() 方法来通知

★★★1. 使用自定义的变量来作为标志位

示例-1: 使用自定义的变量来作为标志位.

  • 需要给标志位上加 volatile 关键字(这个关键字的功能后面介绍).public volatile boolean isQuit = false;
public class ThreadDemo private static class MyRunnable implements Runnable {	public volatile boolean isQuit = false;@Overridepublic void run() {while (!isQuit) {System.out.println(Thread.currentThread().getName()+ ": 别管我,我忙着转账呢!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName()+ ": 啊!险些误了大事");}}public static void main(String[] args) throws InterruptedException {MyRunnable target = new MyRunnable();Thread thread = new Thread(target, "李四");System.out.println(Thread.currentThread().getName()+ ": 让李四开始转账。");thread.start();Thread.sleep(10 * 1000);System.out.println(Thread.currentThread().getName()+ ": 老板来电话了,得赶紧通知李四对方是个骗子!");target.isQuit = true;}
}

★★★2. Thread.interrupted() 或者Thread.currentThread().isInterrupted() 代替自定义标志位

示例-2: 使用 Thread.interrupted() 或者Thread.currentThread().isInterrupted() 代替自定义标志位.

Thread 内部包含了一个 boolean 类型的变量作为线程是否被中断的标记.

方法 说明
public void interrupt() 中断对象关联的线程,如果线程正在阻塞,则以异常方式通知,否则设置标志位
public static boolean interrupted() 判断当前线程的中断标志位是否设置,调用后清除标志位。
public boolean isInterrupted() 判断对象关联的线程的标志位是否设置,调用后不清除标志位

什么叫做清除标志位呢?
就是比如看教室里的灯是否关闭了,如果打开了,清除标志位就是,把灯关了,下次再看就是灯是关的,但第一次看的时候,灯是开得。不清除标志位就表示,不关灯,每次看都是灯是开的

使用 thread 对象的 interrupted() 方法通知线程结束.

public class ThreadDemo {private static class MyRunnable implements Runnable {@Overridepublic void run() {// 两种方法均可以while (!Thread.interrupted()) {//while (!Thread.currentThread().isInterrupted()) {System.out.println(Thread.currentThread().getName()+ ": 别管我,我忙着转账呢!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();System.out.println(Thread.currentThread().getName()+ ": 有内鬼,终止交易!");// 注意此处的 breakbreak;}}System.out.println(Thread.currentThread().getName()+ ": 啊!险些误了大事");}}public static void main(String[] args) throws InterruptedException {MyRunnable target = new MyRunnable();Thread thread = new Thread(target, "李四");System.out.println(Thread.currentThread().getName()+ ": 让李四开始转账。");thread.start();Thread.sleep(10 * 1000);System.out.println(Thread.currentThread().getName()+ ": 老板来电话了,得赶紧通知李四对方是个骗子!");thread.interrupt();}
}

thread 收到通知的方式有两种:

  1. 如果线程因为调用 wait/join/sleep 等方法而阻塞挂起,则以 InterruptedException 异常的形式通知,清除中断标志
  • 当出现 InterruptedException 的时候, 要不要结束线程取决于 catch 中代码的写法. 可以选择忽略这个异常, 也可以跳出循环结束线程.
  1. 否则,只是内部的一个中断标志被设置,thread 可以通过
  • Thread.interrupted() 判断当前线程的中断标志被设置,清除中断标志
  • Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志

这种方式通知收到的更及时,即使线程正在 sleep 也可以马上收到。

★3. 标志位的清除

Thread.isInterrupted() 和 Tread.interrupted 清楚
而加上 currentThread 表示不清楚

示例-3 观察标志位是否清除

标志位是否清除, 就类似于一个开关.
Thread.isInterrupted() 相当于按下开关, 开关自动弹起来了. 这个称为 “清除标志位”
Thread.currentThread().isInterrupted() 相当于按下开关之后, 开关弹不起来, 这个称为"不清除标志位".

  • 使用 Thread.isInterrupted() , 线程中断会清除标志位
public class ThreadDemo {private static class MyRunnable implements Runnable {@Overridepublic 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 // 只有一开始是 true,后边都是 false,因为标志位被清
false
false
false
false
false
false
false
false
false
  • 使用 Thread.currentThread().isInterrupted() , 线程中断标记位不会清除.
public class ThreadDemo {private static class MyRunnable implements Runnable {@Overridepublic 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 // 全部是 true,因为标志位没有被清
true
true
true
true
true
true
true
true
true

2.5 等待一个线程-join()

★★★哪个线程join 哪个线程先执行

有时,我们需要等待一个线程完成它的工作后,才能进行自己的下一步工作。例如,张三只有等李四转账成功,才决定是否存钱,这时我们需要一个方法明确等待线程的结束

public class ThreadDemo {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("王五工作结束了");}
}

join用于让主线程休眠

大家可以试试如果把两个 join 注释掉,现象会是怎么样的呢?

附录

方法 说明
public void join() 等待线程结束
public void join(long millis) 等待线程结束,最多等 millis 毫秒
public void join(long millis, int nanos) 同理,但可以更高精度

关于 join 还有一些细节内容,我们留到下面再讲解。

应用:
二、线程的Thread 类及常见方法【2/12】【多线程】

2.6 获取当前线程引用

这个方法我们以及非常熟悉了

方法 说明
public static Thread currentThread(); 返回当前线程对象的引用

二、线程的Thread 类及常见方法【2/12】【多线程】

public class ThreadDemo {public static void main(String[] args) {Thread thread = Thread.currentThread();System.out.println(thread.getName());}
}

2.7 休眠当前线程

也是我们比较熟悉一组方法,有一点要记得,因为线程的调度是不可控的,所以,这个方法只能保证实际休眠时间是大于等于参数设置的休眠时间的

方法 说明
public static void sleep(long millis) throws InterruptedException 休眠当前线程 millis毫秒
public static void sleep(long millis, int nanos) throws InterruptedException 可以更高精度的休眠
public class ThreadDemo {public static void main(String[] args) throws InterruptedException {System.out.println(System.currentTimeMillis());Thread.sleep(3 * 1000);System.out.println(System.currentTimeMillis());}
}

二、线程的Thread 类及常见方法【2/12】【多线程】

二、线程的Thread 类及常见方法【2/12】【多线程】

关于 sleep,以后我们还会有一些知识会给大家补充