浅谈Thread类及常见方法与线程的状态(多线程编程篇2)

发布于:2025-03-30 ⋅ 阅读:(38) ⋅ 点赞:(0)

目录

 前言 

1.Thread类及常见方法

Thread类中常见的属性

1. getId()

2. getName()

3. getState()

4. getPriority()

5. isDaemon()

6. isAlive()

7. isInterrupted()

2.Thread类中常见的方法

Thread.interrupt() (中断线程)

Thread.start()(启动线程)

1. 覆写 run() 方法

2.调用start() 

Thread.join()(等待线程)

3.线程的状态

1. NEW(新建)

2. RUNNABLE(运行中)

3. BLOCKED(阻塞)

4. WAITING(无限等待)

5. TIMED_WAITING(限时等待)

6. TERMINATED(终止)

结尾

 前言 

如何简单的去使用jconsloe 查看线程 (多线程编程篇1)_如何查看weblogic当前线程-CSDN博客

距离笔者发布本系列第一篇博客已过去一月有余,本篇博客,笔者将继续介绍已经整理学习好的内容

既帮助未来的自己复习,也为正在阅读的你提供参考和思考.

本篇博客的内容正如标题所示,笔者主要将介绍一下内容

1.Thread类,并通过举例演示常见方法

2.向大家介绍线程的状态.

愿我们一起进步!

1.Thread类及常见方法

首先,需要说明的是,相比于多进程编程,多线程编程能够更有效地利用多核CPU资源。

线程作为进程中的执行单元,它们共享进程的内存空间,因此在创建、调度和销毁时所消耗的资源远少于进程。由于线程之间的开销较小,因此在多个线程之间的调度和切换效率较高。

操作系统内核实现了线程这样的机制 , 并且对用户层提供了一些 API 供用户使用 .
Java 标准库中 Thread 类可以视为是对操作系统提供的 API 进行了进一步的抽象和封装 .降低我们的学习成本
关于如何创建线程,笔者在上文已经介绍过了,笔者常用Lambda表达式去创建实例化一个Thread类

Thread类中常见的属性

如何简单的去使用jconsloe 查看线程 (多线程编程篇1)_如何查看weblogic当前线程-CSDN博客

1. getId()

  • 功能:获取线程的唯一标识符(ID),和PID类似。每个线程都有一个唯一的 ID,它是一个正整数。这个ID是JAVA给分配的,不是系统API提供的,更不是PCB中的ID.

  • 用途:可以用来标识不同的线程。

2. getName()

  • 功能:获取线程的名称。每个线程都有一个名字,默认是 Thread-0Thread-1 等,当然也可以通过构造方法手动设置线程名称。

  • 用途:帮助开发者区分不同的线程

3. getState()

  • 功能:获取线程的状态,返回一个 Thread.State 枚举值。

  • 用途:用于查看线程的当前状态

4. getPriority()

  • 功能:获取线程的优先级。

  • 用途:用于控制线程的调度顺序,优先级高的线程会比优先级低的线程更早得到 CPU 时间。

5. isDaemon()

  • 功能:检查线程是否为守护线程(也可以叫做后台线程)(Daemon thread)。守护线程是辅助性线程,在所有非守护线程结束后会自动退出。

  • 用途:判断线程是否为后台线程。守护线程一般用于系统服务或后台任务,如垃圾回收线程。

一般情况下,我们会默认一个线程为前台线程,一个JAVA进程中,如果前台线程没有执行结束,那么整个进程是一定不会结束的,但是后台进程是否结束了,不会影响整个进程的进行

 举一个简单的例子 :

public class Demo2
{
    public static void main(String[] args) {
        Thread y = new Thread(() -> {
            while (true) {
                System.out.println("Test");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    throw new RuntimeException(e);
                }
            }
        }, "线程一"); // lambda 表达式写法
        y.setDaemon(true); // 设置为后台线程
        y.start(); // 启动线程
    }
}

如果在这里设置成后台线程,那么运行结果如下 


进程已结束,退出代码0

或者 

Test
进程已结束,退出代码0

如果y是一个前台线程,那么该程序应该一直打印"Test",但是我们在这里把他设置为了后台线程, 

此时主线程立马就执行完了,由于没有其他前台线程了,因此进程结束了,y线程来不及执行,或者只能执行几次.

6. isAlive()

  • 功能:检查线程是否处于活动状态。如果线程已经启动并且还没有终止,则返回 true,否则返回 false

  • 用途:用于判断一个线程是否仍然在运行。

举个例子

public class Demo2
{
    public static void main(String[] args) throws InterruptedException {
        Thread y = new Thread(() -> {
                System.out.println("Test");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    throw new RuntimeException(e);
                }
        }, "线程一"); // lambda 表达式写法
        y.start(); // 启动线程
        System.out.println(y.isAlive());
        Thread.sleep(2000);
        System.out.println(y.isAlive());
    }
}

结果如下:

true
Test
false

进程已结束,退出代码0

 启动y线程后,线程存活,等待两秒后,线程的活动被终止

7. isInterrupted()

  • 功能:检查线程是否被中断。如果线程被中断,返回 true,否则返回 false

  • 用途:判断线程是否接收到中断信号。

2.Thread类中常见的方法

Thread.interrupt() (中断线程)

  • 作用:中断线程的执行。

  • 用途:通过调用该方法来中断一个线程的执行,线程可以通过 interrupted()isInterrupted() 方法判断是否被中断。

举个例子

结合我们刚刚提到的isInterrupted(),我们举一个例子

下面是一个线程执行的示例,线程会不断打印 "正在工作",直到被 interrupt() 发送中断信号后,它会检查自身的 isInterrupted() 状态并退出。

public class Demo4 {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) { 
                System.out.println("正在工作");

                try {
                    Thread.sleep(1000); // 线程进入休眠状态
                } catch (InterruptedException e) {
                    System.out.println("线程被中断,抛出异常终止");

                    //  如果选择抛出异常,线程会立即终止
                    // 当捕获到 InterruptedException 时,异常被重新抛出,
                    // 这会导致当前线程的执行停止,不会继续执行 while 循环后面的代码。
                    // 也就是说,抛出 RuntimeException 可能会导致线程提前结束。
                   // throw new RuntimeException(e);

                    // ✅ 如果使用 break,则线程可以优雅退出
                    // break;
                }
            }
            // ⚠ 只有在没有抛出异常时,这行代码才会执行
            System.out.println("工作结束"); 
        });

        thread.start();

        try {
            Thread.sleep(3000); // 主线程等待 3 秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        thread.interrupt(); // 发送中断信号
        System.out.println("已通知线程中断");
    }
}

值得注意的就是这里要不要  throw new RuntimeException(e)

而关于Thread.currentThread()

在 Java 中,Thread.currentThread() 返回当前正在执行的线程对象。因为 thread 变量指向的是主线程创建的线程实例,而 Thread.currentThread() 总是指向当前正在运行的线程,因此在线程内部调用 Thread.currentThread().isInterrupted() 可以正确获取线程的中断状态。

并且,即使线程内部的逻辑出现阻塞,也可以用这个方法唤醒,正常来说,sleep会休眠到时间到了才唤醒,但是interrupt()方法 可以使sleep内部出发异常,从而被提前唤醒,如果我们自己定义标注为,就做不到

效果如下:

 但是sleep方法抛出异常,同时也会自动清除刚才设置的标志位,所以我们的线程会继续工作,所以我们需要break优雅地去中断.

我们再总结一下

1.如果只捕获异常,不做处理

try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    e.printStackTrace(); // 仅打印异常
}

标志位会被清除,但是线程不会停止,将继续工作

2.使用break;

try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    e.printStackTrace(); // 仅打印异常
    break;
}
  • 线程不会抛出异常,而是优雅退出循环

  • while 结束后,线程会执行 "工作结束" 语句并结束。

 3.你选择抛出异常

try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    throw new RuntimeException(e);
}
  • 线程会立即终止,不会执行 while 循环后面的代码

  • "工作结束" 也不会被打印,因为 while 之后的代码永远不会执行。

读者们可以把代码拷到自己的环境里去试验一下,笔者下的结论建立在试验过的基础之上.

Thread.start()(启动线程)

这里笔者主要是谈一下覆写RUN方法和调用start()方法的区别,它们有什么区别呢?

1. 覆写 run() 方法

run() 方法是 Thread 类的一个普通方法,如果我们直接调用 run(),它不会启动一个新的线程,而是作为当前线程中的一个普通方法执行。例如:

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("线程正在执行: " + Thread.currentThread().getName());
    }
}

public class Main {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        t1.run(); // 直接调用 run()
        System.out.println("主线程执行完毕");
    }
}

效果如下:

 

 可以看到,run() 方法是在 主线程 中执行的,而不是在一个新的线程中。

2.调用start() 

start() 方法是 Thread 类的核心方法,调用 start() 后,JVM 会创建一个新的线程,并调用该线程的 run() 方法,从而实现真正的并发执行。例如:

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("线程正在执行: " + Thread.currentThread().getName());
    }
}

public class Main {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        t1.start(); // 启动新线程
        System.out.println("主线程执行完毕");
    }
}

代码效果:

主线程执行完毕
线程正在执行: Thread-0

进程已结束,退出代码0

通俗来说,假设我是经理,我把我的下属员工张三喊过来,我告诉张三他这周的工作任务现在,我让他行动起来.

在这段文字中:

  • "我是经理" → 你是主线程 (main 线程)。

  • "把张三喊过来" → 你创建了一个线程对象 (new Thread())。

  • "我告诉张三他的工作任务" → 你覆写了 run() 方法,定义了线程要执行的内容。

  • "我让张三行动起来"==start() → 你调用了 start() 方法,让张三真正开始工作,并且他是独立工作的,你自己(主线程)也可以去做别的事了。

  • "如果我只是告诉张三他的工作任务,而没让他行动"==run() → 你只是调用了 run() 方法,这样张三不会真正开始工作,而是你自己(经理)亲自去做他的工作(主线程执行 run() 里面的内容)。

Thread.join()(等待线程)

在 Java 线程编程中,join() 方法用于让当前线程等待另一个线程执行完成,然后才继续执行。它的作用是同步线程,确保某个线程执行完毕后,其他线程才能继续运行。

例如现在有一个线程t,如果主线程调用t.join(),那么主线程就会进入阻塞状态,等待t线程先执行完

笔者再给一个示例代码:

class Worker extends Thread {
    private String name;

    public Worker(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        System.out.println(name + " 开始工作...");
        try {
            Thread.sleep(2000);  // 模拟任务执行 2 秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(name + " 工作完成!");
    }
}

public class JoinExample {
    public static void main(String[] args) {
        Worker worker1 = new Worker("张三");
        Worker worker2 = new Worker("李四");

        worker1.start();
        worker2.start();

        try {
            worker1.join();  // 等待 worker1 执行完
            worker2.join();  // 等待 worker2 执行完
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("所有工作完成,主线程继续执行!");
    }
}

 效果如下:

张三 开始工作...
李四 开始工作...
张三 工作完成!
李四 工作完成!
所有工作完成,主线程继续执行!

又或者,我们希望多个线程按照一定顺序进行,也可以使用join()方法

例如:

public class JoinExample {
    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            System.out.println("线程 1 执行");
        });

        Thread thread2 = new Thread(() -> {
            System.out.println("线程 2 执行");
        });

        Thread thread3 = new Thread(() -> {
            System.out.println("线程 3 执行");
        });

        try {
            thread1.start();
            thread1.join();  // 等待 thread1 结束

            thread2.start();
            thread2.join();  // 等待 thread2 结束

            thread3.start();
            thread3.join();  // 等待 thread3 结束
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("所有线程执行完毕!");
    }
}

在main线程中那个线程调用了join()方法, main线程就一定要等待该线程执行完,然后才能接着执行

效果如下:

线程 1 执行
线程 2 执行
线程 3 执行
所有线程执行完毕!

感兴趣的读者们可以把代码拷到自己的环境中去尝试 !

3.线程的状态

我们如果想涉及多线程的安全问题, 那我我们就必须先知道多线程有哪些状态?

前面我们提到过 getState() 可以获取当前线程的状态

public class ThreadState {
    public static void main(String[] args) {
        // 遍历 Thread.State 枚举中的所有状态
        for (Thread.State state : Thread.State.values()) {
            // 输出每种状态
            System.out.println(state);
        }
    }
}
NEW
RUNNABLE
BLOCKED
WAITING
TIMED_WAITING
TERMINATED

进程已结束,退出代码0

1. NEW(新建)

  • 线程刚刚被创建,还 没有启动,此时线程对象已经被分配内存,但还未执行 start() 方法。

  • 特征:未执行任何代码。

Thread thread = new Thread(() -> System.out.println("Hello"));
System.out.println(thread.getState()); // NEW

2. RUNNABLE(运行中)

  • 线程已经 调用 start() 方法,并且 可能正在运行,也可能在等待 CPU 调度(就绪)。

  • 这个状态表示线程 可运行,但 不一定正在运行(因为可能在 CPU 时间片等待中)。

Thread thread = new Thread(() -> {
    while (true) {} // 无限循环,保持线程存活
});
thread.start();
System.out.println(thread.getState()); // RUNNABLE

3. BLOCKED(阻塞)

  • 线程 等待获取锁,但该锁 被其他线程持有,导致线程无法继续执行。

  • 特征只能用于同步代码块synchronized),等待进入临界区

4. WAITING(无限等待)

  • 线程 无限期等待,直到 被其他线程显式唤醒notify()notifyAll())。

  • 特征:不会自动恢复,必须 由其他线程唤醒

5. TIMED_WAITING(限时等待)

  • 线程进入 限时等待状态,会在指定时间后自动恢复

  • 特征:等待 超时后自动唤醒,无需其他线程干预。

6. TERMINATED(终止)

  • 线程执行完 run() 方法,或者 抛出未捕获异常 导致终止。

  • 线程生命周期结束,不能再 start()

结尾

写到这里,笔者就暂时停笔了,知识简单但是汇总不易,实验的结果都是笔者自己写代码自己试验出来的,希望对读者有用,可以的话,请您投个票