【JavaEE初阶】 Thread类及常见方法

简介: 【JavaEE初阶】 Thread类及常见方法


🌴Thread类的概念

Thread 类是 JVM 用来管理线程的一个类,换句话说,每个线程都有一个唯一的 Thread 对象与之关联。

每个执行流,也需要有一个对象来描述,类似下图所示,而 Thread 类的对象就是用来描述一个线程执行流的,JVM 会将这些 Thread 对象组织起来,用于线程调度,线程管理

🌳Thread 的常见构造方法

使用如下:

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

命名操作:

  • 是将你在使用 jconsole 命令观察线程时的看到的线程名改为你自己的重命名

比如有以下程序:

public class Test1 {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            while (true) {
                System.out.println("我的名字叫遇事问春风乄");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"遇事问春分乄");
        thread.start();
        while (true) {
            System.out.println("我的名字叫main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

当我们用 jconsole 命令进行查看时,结果如下:

上面就会有我们自己的命名。

这样方便程序猿进行调试

🎄Thread 的几个常见属性

常见属性说明:

  • ID 是线程的唯一标识,不同线程不会重复
  • 名称是各种调试工具用到
  • 状态表示线程当前所处的一个情况,后面博主会一一介绍
  • 优先级高的线程理论上来说更容易被调度到
  • 关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行。
  • 是否存活,即简单的理解,为 run 方法是否运行结束了
  • 线程的中断问题,下面博主会进一步说明

程序举例,有以下程序(Thread.currentThread()表示返回当前线程对象的引用)

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());
    }
}

运行结果如下:

🍀start()-启动一个线程

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

  • 覆写 run 方法是提供给线程要做的事情的指令清单
  • 线程对象可以认为是张三把 李四、王五叫过来了
  • 而调用 start() 方法,就是张三喊一声:”行动起来!“,线程才真正独立去执行了

    调用 start 方法, 才真的在操作系统的底层创建出一个线程

🌲中断一个线程

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

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

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

🚩实例一

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

  • 需要给标志位上加 volatile 关键字(这个关键字的功能后面介绍,就暂时理解为一个共享的变量)

比如有以下程序:

public class ThreadDemo1 {
    private static class MyRunnable implements Runnable {
        public volatile boolean isQuit = false;
        @Override
        public 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()
                    + ": 啊!险些误了大事");
            System.out.println("停止转账");
        }
    }
    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;
    }
}

运行结果如下:

我们可以里看到当李四收到了受骗的通知后,停止了转账行为

🚩实例二

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

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

比如以下程序:

public class ThreadDemo2 {
    private static class MyRunnable 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();
                    System.out.println(Thread.currentThread().getName()
                            + ": 有内鬼,终止交易!");
                    // 注意此处的 break
                    break;
                }
            }
            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();
    }
}

运行结果如下:

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

  1. 如果线程因为调用 wait/join/sleep 等方法而阻塞挂起,则以InterruptedException 异常的形式通知,清除中断标志当出现InterruptedException 的时候, 要不要结束线程取决于 catch 中代码的写法. 可以选择忽略这个异常, 也可以跳出循环结束线程.

上述代码中,博主在catch里加了break,所以停止了交易,当我们去掉break时,效果如下:

我们再来看一下运行结果:

我们发现我们如果不去结束循环,该线程会在抛出异常后继续运行。

这就将主动权交到了我们程序员自己的手上,你通知归通知,停不停止是我自己的决定的

  1. 否则,只是内部的一个中断标志被设置,thread 可以通过
  • Thread.interrupted() 判断当前线程的中断标志被设置,清除中断标志
  • Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志

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

🚩实例三

观察标志位是否清除

标志位是否清除, 就类似于一个开关.

  • Thread.interrupted() 相当于按下开关, 开关自动弹起来了. 这个称为 “清除标志位”
  • Thread.currentThread().isInterrupted() 相当于按下开关之后, 开关弹不起来, 这个称为 “不清除标志位”
  • 使用 Thread.interrupted() , 线程中断会清除标志位
    代码如下:
public class ThreadDemo3 {
    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();
    }
}

运行结果为:

  • 使用 Thread.currentThread().isInterrupted() , 线程中断标记位不会清除.
    代码如下:
public class ThreadDemo4 {
    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();
    }
}

运行结果如下:

🎍join()-等待一个线程

有时,我们需要等待一个线程完成它的工作后,才能进行自己的下一步工作。

例如,张三只有等李四转账成功,才决定是否存钱,这时我们需要一个方法明确等待线程的结束

代码示例如下:

public class ThreadDemo5 {
    public static void main(String[] args) throws InterruptedException {
        Runnable target = () -> {
            for (int i = 0; i < 3; 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 static Thread currentThread(); 返回当前线程对象的引用

🚩休眠当前线程

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

public static void sleep(long millis) throws InterruptedException 休眠当前线程 millis毫秒
public static void sleep(long millis, int nanos) throwsInterruptedException 可以更高精度的休眠

关于 sleep,以后博主还会有一些知识会给大家补充的。

⭕总结

关于《【JavaEE初阶】 Thread类及常见方法》就讲解到这儿,感谢大家的支持,欢迎各位留言交流以及批评指正,如果文章对您有帮助或者觉得作者写的还不错可以点一下关注,点赞,收藏支持一下!

相关文章
|
11月前
|
Java
【零基础学Java】—this关键字的三种用法+Java继承的三个特点(二十一)
【零基础学Java】—this关键字的三种用法+Java继承的三个特点(二十一)
|
5月前
|
存储 Java
心得经验总结:浅谈Java类中的变量初始化顺序
心得经验总结:浅谈Java类中的变量初始化顺序
32 1
|
6月前
|
Java
【JAVA学习之路 | 提高篇】创建与启动线程之二(继承Thread类)(实现Runnable接口)
【JAVA学习之路 | 提高篇】创建与启动线程之二(继承Thread类)(实现Runnable接口)
|
6月前
|
Java 程序员
【JavaEE初阶】 Callable 接口
【JavaEE初阶】 Callable 接口
|
6月前
|
Java 程序员 编译器
【JavaEE初阶】 Synchronized 原理详解
【JavaEE初阶】 Synchronized 原理详解
|
6月前
|
存储 安全 Java
【JavaEE初阶】 synchronized关键字详解
【JavaEE初阶】 synchronized关键字详解
|
Java 程序员 API
Java源码类-Optional类源码分析与使用
Java源码类-Optional类源码分析与使用
77 0
|
6月前
|
Java Linux API
【JavaEE初阶】线程的概念与创建
【JavaEE初阶】线程的概念与创建
java202302java学习笔记第二十一天-多态调用成员的特点1
java202302java学习笔记第二十一天-多态调用成员的特点1
58 0
java202302java学习笔记第二十一天-多态调用成员的特点1