首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【多线程】Thread类及常用方法

【多线程】Thread类及常用方法

作者头像
木井巳
发布2025-12-16 09:56:47
发布2025-12-16 09:56:47
770
举报

一、Java中的 Thread 类 与 操作系统线程 的关系

线程是操作系统中的概念。操作系统内核实现了线程这种机制,并对用户层提供了一些 API (Application Programming Interface)供用户使用。

但是,操作系统提供的原生线程API是C语言的,因此,Java对操作系统提供的API进行了封装并将其导入标准库(java.lang.)中供用户使用,这就是Thread类。

虽然 Thread 类对象和操作系统的线程存在一对一的关系,但是 Thread 类对象和操作系统的线程并不是“同生共死”的关系!!

  • 当我们成功创建一个 Thread 类对象时,如果没有调用 start 方法,那么操作系统的线程实际上并没有被创建;
  • 当操作系统的线程结束了,若 Thread 类对象生命周期还没结束,Thread 类对象也并没有随着其描述的线程而一起被销毁。

二、多线程程序

如何启动线程?

使用 start () 方法在操作系统层面创建线程并开始执行。

注意,创建 Thread 类对象并不意味着类对象所对应的在操作系统中的线程也被创建。

2.1 第一个多线程程序:继承自 Thread 类的子类

在Thread类中,run方法是可以被重写的,以供线程按照程序员自己规定的逻辑运行。

代码语言:javascript
复制
// 继承Thread类的子类,并重写run方法
class MyThread extends Thread {
    @Override
    public void run() {
        while (true) {
            System.out.println("This is MyThread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

public class Demo01_subclass {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new MyThread();
        t.start();      // 创建线程
        
        // 主线程
        while (true) {
            System.out.println("This is main");
            Thread.sleep(1000);
        }
    }
}

在上面的代码中,我们通过创建继承Thread类的子类MyThread来创建一个线程并运行,与此同时,main方法中的主线程也在运行,这就形成了一个“并发”执行的多线程程序。

这里使用sleep语句时的异常处理使用 try...catch语句而不是用throws语句的原因是父类Thread的run方法并没有使用throws,因此只能使用 try...catch语句来处理异常。

执行代码之后,发现MyThread类和main两个内部的循环都在进行中,也就是说,主线程main和线程t是“并发”执行的。

可以看到,控制台输出的看似是main线程先执行然后再执行t线程,实际上操作系统对线程的调度采用“抢占式执行”,其顺序是随机的,而这种随机调度是无法被程序员所干预的。

我们可以通过第三方工具(java进程)来观察进程的详细情况:

通过这个程序我们可以连接正在IDE运行的程序中进程的详细情况

这里看到t线程被自动命名为 “Thread-0” 了,实际上Thread类是可以自定义线程名称的,以便程序员辨认自己写的线程。当程序员没有提供自定义的线程名称时,JVM就会自动给线程按照顺序命名。

2.2 实现 Runnable 接口并重写run方法

创建线程的写法不止有通过子类这一种方法,还可以通过实现Runnable接口来创建线程。

代码语言:javascript
复制
// 实现runnable接口,解耦合
class MyRunnable implements Runnable {
    @Override
    public void run() {
        while (true) {
            System.out.println("This is Thread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

public class Demo02_runnable {
    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = new MyRunnable();
        Thread t = new Thread(runnable);
        t.start();      // 创建线程

        // 主线程
        while (true) {
            System.out.printf("This is main");
            Thread.sleep(1000);
        }
    }
}

我们实现的Runnable类中重写了run方法,将线程所要执行的任务放在run方法中,而Thread类则只负责创建线程,当将来线程要执行的任务发生改变时直接修改runnable中的ren方法即可。

这种方法是是一种 解耦合 方法。

在代码中,我们一般希望 高内聚,低耦合 ,这样代码的可维护性就会比较高(较方便修改)。

(高内聚:在一个模块之内,把有关联的事物放在一块,比如功能相类似的函数等;低耦合:每个模块之间的影响和依赖尽可能的小)

2.3 利用匿名内部类创建线程

我们这次在 main 方法中创建一个匿名内部类,并在其中重写 run 方法:

代码语言:javascript
复制
public class Demo03_anoInnerClass {
    public static void main(String[] args) throws InterruptedException {
        // 创建对象的时候使用了Thread的匿名(一次性)子类,并重写run方法
        Thread t = new Thread() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("This is Thread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        };
        t.start();      // 创建线程

        // 主线程
        while (true) {
            System.out.println("This is main");
            Thread.sleep(1000);
        }
    }
}

这样就可以少定义一些类。

2.4 匿名内部类解耦合创建线程

考虑到降低耦合,我们可以做出一些调整:实现一个 Runnable 的匿名内部类并创建实例,然后再将 runnable 作为参数传给 Thread 创建线程对象,以实现解耦合。

代码语言:javascript
复制
public class Demo04_anoInnerClassRunnable {
    public static void main(String[] args) throws InterruptedException {
        // 在Runnable这里创建匿名内部类,将任务和线程执行分离
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("This is Thread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        };

        Thread t = new Thread(runnable);
        t.start();      // 创建线程

        // 主线程
        while (true) {
            System.out.println("This is main");
            Thread.sleep(1000);
        }
    }
}

2.5 通过 Lambda 表达式创建线程

lambda表达式本质是一个“回调函数”,通过实现函数式接口创建子类和对应的实例(编译器自动重写了方法),这样不仅简洁、清晰,还可以利用编译器加快效率。

代码语言:javascript
复制
public class Demo05_lambda {
    public static void main(String[] args) throws InterruptedException {
        // 使用lambda表达式
        Thread t = new Thread(() -> {
            while (true) {
                System.out.println("This is Thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t.start();      // 创建线程

        // 主线程
        while (true) {
            System.out.println("This is main");
            Thread.sleep(1000);
        }
    }
}

三、Thread 类的常见方法

在Java中,Thread 类是 JVM 专门用来管理线程的一个类。

Thread 类对象是用来描述一个线程执行流的,每一个线程都有唯一的 Thread 类对象与之对应,而 JVM 会将这些 Thread 类对象组织起来用于线程调度和线程管理。

3.1 Thread 类常见构造方法

构造方法

说明

Thread ()

创建线程对象,需要重写 run 方法

Thread (Runnable runnable)

使用 Runnable 对象创建线程对象,不需要重写 run 方法

Thread (String name)

创建线程对象并命名

Thread (Runnable runnable, String name)

使用 Runnable 对象创建线程对象并命名

【了解】Thread (ThreadGroup group, Runnable runnable)

用于对线程进行分组管理

  1. Thread t1 = new Thread();
  2. Thread t2 = new Thread(new MyRunnable());
  3. Thread t3 = new Thread("MyThread");
  4. Thread t4 = new Thread(new MyRunnable(), "MyThread");

3.2 Thread 类常见属性

属性

说明

获取方法

ID

ID是线程的唯一标识,不可重复

getId ()

名称

名称方便程序员进行调试

getName ()

状态

状态表示线程当前所处的情况

getState ()

优先级

优先级高的线程理论上更易被调度执行

getPriority ()

是否为后台线程

JVM 在一个进程的所有非后台线程结束之后才停止执行

isDaemon ()

是否存活

当前线程是否执行结束

isAlive ()

是否被中断

终止线程

isInterrupted ()

3.2.1 isDaemon 方法

后台线程也可以理解为“守护线程”,这些“守护线程”只会默默地在非后台线程运行的时候陪伴:

  • 当非后台线程全部结束,他们也随之结束;
  • 当非后台线程没全部结束时,“守护线程”结不结束都不会影响非后台线程。

非后台线程其实就是我们所创建的线程(包括主线程),而后台线程则是 JVM 在执行进程时的一些自带线程,当进程结束(非后台线程全部结束),后台进程也就结束了。

当我们想将自己创建的线程改成后台线程,可以使用 setDaemon () 方法。

代码语言:javascript
复制
public class Demo07_daemonThread {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while (true) {
                System.out.println("thread t");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        // 使用 setDaemon 方法可以将我们创建的线程修改为后台线程
        // 但是需要在 start 方法之前使用
        t.setDaemon(true);
        t.start();

        // 主线程
        for (int i = 0; i < 3; i++) {
            System.out.println("main");
            Thread.sleep(1000);
        }
        // 线程 t 已被修改为后台线程,当主线程结束,线程 t 也随之结束
        System.out.println("main-结束");
    }
}
3.2.2 isAlive 方法

虽然 java 中,一个系统线程和 Thread 类对象存在一对一的对应关系,但是 Thread 类对象的生命周期和系统中线程的生命周期是不同的。

代码语言:javascript
复制
public class Demo08_isAliveFunc {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            for (int i = 0; i < 3; i++) {
                System.out.println("thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        System.out.println(t.isAlive());
        // 当还未调用 start 方法时,线程 t 还没有被创建出来(但是 t 对象存在),输出的结果为 false

        t.start();
        // 当调用 start 方法之后才真正创建线程

        while (true) {
            System.out.println(t.isAlive());
            Thread.sleep(1000);
        }
        // 3秒过后线程 t 已被销毁,但是 t 对象还在(能够调用 isAlive 方法)
    }
}
3.2.3 终止线程的方法

1. 我们可以自己定义一个布尔类型的变量作为线程是否中断的标志:

代码语言:javascript
复制
class Test {
    public int value = 0;
}

public class Demo10_isFinishedFunc {
    private static boolean isFinished = false;

    public static void main(String[] args) throws InterruptedException {
        // 若 isFinished 定义在此处,会编译报错
        // 因为 lambda 是有“变量捕获”的——即使用自身所在类之外的变量
        // 此处的 isFinished 是局部变量,由于 lambda 是回调函数,在线程创建之后才执行
        // 等到 lambda 执行,此时主线程早已执行完毕,isFinished 已被销毁
        //boolean isFinished = false;
        
        // 将 isFinished 改成成员变量,就不会触发“变量捕获”语法

        Test test = new Test();

        Thread t = new Thread(() -> {
            while (!isFinished) {
                System.out.println("thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("thread-结束");
        });
        t.start();

        Thread.sleep(3000);
        isFinished = true;
    }
}

2. 我们可以使用 interrupted 方法,Thread 类中含有一个布尔类型的变量充当线程是否中断的标志。

方法

说明

public void interrupt ()

主动中断 Thread 类对象关联的线程,若线程处于阻塞状态,就以异常的方式通知,否则设置标志位

public static boolean interrupted ()

判断当前线程的中断标志位,调用后清除标志位

public boolean isInterrupted ()

判断对象关联的线程的标志位,调用后不清除标志位

需要注意 interrupted () 和 isInterrupted () 的区别:

特性

interrupted()

isInterrupted()

方法类型

静态方法

实例方法

检测对象

当前线程(类名调用)

调用方法的线程对象 (对象调用)

清除中断状态

典型场景

当前线程中断后需清除状态

监控其他线程或保留中断标志

1. 当我们使用 interrupted () :

若线程因 wait/join/sleep 方法处于阻塞状态时,抛出 InterruptedException 异常通知并清除中断标志(当抛出异常后线程需要执行 catch 语句:可以抛出异常包装类异常终止线程;也可以 break 跳出循环正常终止线程)

2. 当我们使用 isInterrupted () :

通过 Thread.currentThread().isInterrupted() 来判断线程的中断标志且不清除中断标志。(这种方式通知更及时)

代码语言:javascript
复制
public class Demo11_isInterruptedFunc {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            //while (t.isInterrupted())  -> 此处 lambda 中使用 t 的时机是在 Thread 创建 t 对象之前
            // 使用 currentThread 方法获取到当前线程的引用 t ,其作用相当于 this 关键字
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    //throw new RuntimeException(e);
                    // 1. 当主线程调用 interrupt 方法强制终止 t 线程,此时 t 线程正在 sleep,sleep 被中断就抛出异常
                    // 然后执行“throw new RuntimeException(e)”抛出包装异常,由于没有外层异常处理器(run方法没有try...catch处理异常语句),线程 t 异常终止
                    // 2. (空语句)
                    // 当什么都不写,t 线程不会被终止:当调用 interrupt 方法时 sleep 被提前唤醒并抛出异常,JVM 此时将终止标志改回 false
                    break;  // 3. 若添加 break 语句,当 sleep 在执行过程中被打断并抛出异常后,异常被 catch 语句捕获,跳出循环,不再检查终止标志(此时终止标志已被重置为 false)
                    // 程序将正常终止
                }
            }
            System.out.println("thread-结束");
        });
        t.start();

        Thread.sleep(3000);
        System.out.println("main线程尝试终止t线程");
        t.interrupt();
    }
}

3.3 等待一个线程

由于多线程之间是并发执行的、线程的调度是随机的。有时候我们希望某一个线程等待另一个线程先执行完成后再继续执行,这时候需要人为干预线程的调度。

Thread 类中提供了 join () 方法能够等待线程:

方法

说明

public void join ()

让当前线程等待调用该方法的线程

public void join (long millis)

等待线程,限定时间为 millis 毫秒

public void join (long millis, int nanos)

等待线程,限定时间更精确

具体例子的代码如下:

代码语言:javascript
复制
public class Demo12_joinFunc {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            for (int i = 0; i < 3000; i++) {
                System.out.println("Thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("Thread-结束");
        });
        t.start();

        t.join();
        // 在主线程中通过 t 线程调用 join 方法
        // 意思是 t 线程要参与 cpu 的调度执行(要插队),此时要让原本正在执行的 main 线程等待
        // 当没有传入参数时,主线程要一直等待 t 线程
        // 可以提供“超时时间”,当超过该指定时间时主线程就会继续往下执行,不会一直等待
        System.out.println("main-结束");
    }
}

3.4 获取当前线程的引用

有时候我们需要脱离类对象来获取当前线程的引用,Thread 类提供了一个方法可以获取:

方法

说明

public static Thread currentThread ()

返回当前线程的引用

currentThread () 方法的作用相当于 this 关键字。

3.5 休眠当前线程

有时候我们需要暂时让线程休眠一段时间,这时候可以使用 sleep () 方法:

方法

说明

public static void sleep (long millis) throws InterruptedException

休眠当前线程 millis 毫秒,当线程在休眠中被强制中断时会抛出 InterruptedException 异常

public static void sleep (long millis, int nanos) throws InterruptedException

以更高精度的时间休眠当前线程

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-12-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、Java中的 Thread 类 与 操作系统线程 的关系
  • 二、多线程程序
    • 如何启动线程?
    • 2.1 第一个多线程程序:继承自 Thread 类的子类
    • 2.2 实现 Runnable 接口并重写run方法
    • 2.3 利用匿名内部类创建线程
    • 2.4 匿名内部类解耦合创建线程
    • 2.5 通过 Lambda 表达式创建线程
  • 三、Thread 类的常见方法
    • 3.1 Thread 类常见构造方法
    • 3.2 Thread 类常见属性
      • 3.2.1 isDaemon 方法
      • 3.2.2 isAlive 方法
      • 3.2.3 终止线程的方法
    • 3.3 等待一个线程
    • 3.4 获取当前线程的引用
    • 3.5 休眠当前线程
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档