首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Java多线程

Java多线程

作者头像
独断万古他化
发布2026-01-15 13:07:11
发布2026-01-15 13:07:11
830
举报
文章被收录于专栏:Java 攻略Java 攻略

一、认识线程

1.1 什么是线程和进程

进程:一个可执行程序;进程是操作系统资源分配的基本单位。 线程:一个线程就是一个执行单元;线程是操作系统调度执行的基本单位。

1.2 为什么要有线程

1.并发编程成为必需,线程也是为了实现并发编程效果,能够解决多进程涉及到的问题。 2.线程也叫做轻量级进程,创建销毁线程的开销比创建销毁进程快很多。轻量化体现在创建线程不需要申请那么多资源(进程中第一个线程创建时,所有资源都需要进行申请)。

1.3 进程和线程的区别与联系

联系:进程包含线程,每个进程至少包含一个线程,即主线程。 区别:

  1. 资源隔离与共享:每个进程都有自己独立的资源,进程和进程之间不能共享内存空间。而同一个进程里的线程之间可以共享相同的资源。
  2. 进程是操作系统资源分配的基本单位;线程是操作系统调度执行的基本单位。
  3. 进程之间通常不会有"资源冲突"情况;而一个进程中的线程之间,特别容易出现资源访问冲突的情况。
  4. 进程之间不会相互影响,当一个进程挂掉是,其他的进程不会有事;而同一个进程的线程挂掉,可能会将同进程内的其他线程一起挂掉,致使整个进程崩溃。

二、创建线程

2.1 继承Thread

编写一个类继承Thread方法,重写run方法。 创建线程对象,调用线程对象的start方法。

代码语言:javascript
复制
//创建一个类继承标准库的Thread类
class MyThread extends Thread{
    //重写父类的run方法
    @Override
    public void run() { //run就是线程的入口      
    }
}

public class Demo1 {
    public static void main(String[] args) {
        //1. 创建Thread 的实例
        MyThread myThread = new MyThread();

        // 2.启动线程 不是run();
        myThread.start();
    }
}

其中:

  • myThread.run() 不会启动线程,只是一个普通的方法调用。不会分配新的分支栈。
  • myThread.start() 作用是启动一个分支线程,启动成功的线程会自动调用run() 方法。run方法和main方法同时进行,属于平级。
2.2 实现Runnable接口

编写一个类实现Runnable接口,实现run方法

代码语言:javascript
复制
class MyRunnable implements Runnable{
    @Override
    public void run() {
        
    }
}

public class Demo2 {
    public static void main(String[] args) {
    	
        MyRunnable myRunnable = new MyRunnable();
        
        //创建线程对象,将Runnable对象作为参数传入
        Thread thread = new Thread(myRunnable);

		//运行线程
        thread.start();
    }
}
  • 两种方法创建线程的区别:实现Runnable 接口的写法更加的解耦合,并没有和线程概念绑定,这样的任务非常方便迁移到其他的载体上;而使用继承Thread方法,其中任务和线程是绑定在一起的,如果需要修改代码为其他形式,就需要大规模的修改。
2.3 继承Thread,使用匿名内部类
  • 创建Thread 的子类,这个子类没有名字(匿名)为了方便,一次性使用。
  • 重写了run方法
  • 创建了Thread子类的实例并且使用t引用指向。

eg:

代码语言:javascript
复制
public class Demo3 {
    public static void main(String[] args) {
        Thread t = new MyThread(){
            @Override
            public void run() {
                while (true){
                    System.out.println("hello thread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        };

        //调用start 方法才是真正创建线程
        t.start();
    }
2.4 实现Runnable,使用匿名内部类
代码语言:javascript
复制
public class Demo4 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {

            }
        });
    }
}
  • new Runnable() 是针对Runnable 定义匿名内部类的,返回结果是Runnable的引用,将这个引用传入Thread 。 也可以等价为下面代码的简化:
代码语言:javascript
复制
public static void main(String[] args) {

        Runnable runnable = new Runnable() {
            @Override
            public void run() {

            }
        };
        
        Thread t = new Thread(runnable);
}
2.5 使用lambda 表达式

eg:

代码语言:javascript
复制
public class Demo5 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
           while (true){
               System.out.println("hello thread");
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
           }
        });

        t.start();
    }
}

三、Thread的常见方法

3.1 Thread 的常见构造方法

方法

说明

Thread()

创建线程对象

Thread(Runnable target)

使用 Runnable 对象创建线程对象

Thread(String name)

创建线程对象,并命名

Thread(Runnable target, String name)

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

Thread(ThreadGroup group, Runnable target)

线程可以被用来分组管理,分好的组即为线程组

3.2 Thread 的常见属性

属性

获取方法

ID

getId()

名称

getName()

状态

getState()

优先级

getPriority()

是否后台线程

isDaemon()

是否存活

isAlive()

是否被中断

isInterrupted()

线程没有创建名字时其默认名称为:thread-0,thread-1…

关于后台线程

后台线程:后台线程不会阻止整个进程结束。 前台线程:前台线程会阻止整个进程结束。

JVM会在一个进程的所有非后台线程结束之后,才会结束运行。 main中包括手动创建的线程都默认为前台线程。

四、Thread 的其他操作

4.1 启动一个线程 - start()

通过覆写run方法创建一个线程对象,但是创建出线程对象不意味着线程开始运行了,只有start方法被调用时,才会真的在操作系统底层创建出一个线程出来。

  • start 操作本质上会调用操作系统提供的API,在操作系统内部创建出一个线程出来。操作系统内部是通过PCB来描述,通过链表组织起来的。
  • 每个PCB描述了一个线程,在PCB上有一个特殊的id属性 - tgid 这个id的值相同就是同一个进程。
  • 当一个线程被创建,就会添加一个PCB,并将tgid的值设置为和链表中其他PCB上的tgid值一样,然后组织到链表中,操作系统再拿着PCB结构体进行调度执行,被调度起来执行的逻辑就是run方法里面设定的回调逻辑。
  • 此外,start 方法本身执行速度非常快
  • start 针对一个thread对象,只能调用一次。Java设定了要让thread对象和一个操作系统的线程一一对应,避免混乱。
4.2 中断一个线程 - Interrupt

对于Java来说,一个线程终止,就是这个线程的入口方法执行完毕。Java并不提供“强制终止”,因此所有让线程终止的做法都需要围绕着“让入口方法结束”的做法。

  • 强制终止的方法 是有危险的,例如,当A线程调用方法 终止B线程,此时无法确定B线程当前执行到哪个环节了,也许B做某个事情做了一半就被强制终止了。
4.2.1 自定义变量作为标志位

eg:

代码语言:javascript
复制
public class Demo8 {
    private static boolean running = true;

    public static void main(String[] args) {

        Thread t = new Thread(() -> {
           while (running){
               System.out.println("hello thread");
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
           }
            System.out.println("t 线程退出");
        });

        t.start();

        //主线程中,让用户进行输入
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入整数 0 表示让t线程终止");
        int n = scanner.nextInt();
        if(n == 0){
            running = false;
        }
    }
}

通过修改running的状态来使 t 线程结束 但是当sleep中的时间很长时,即使修改了标志位,也需要等待度过了sleep中的时间才会终止,这显然是不合适的。

4.2.2 使用Thread.currentThread().isInterrupted() 代替自定义标志位
代码语言:javascript
复制
public class Demo9 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
           while(!Thread.currentThread().isInterrupted()){
               System.out.println("hello thread");
               try {
                   Thread.sleep(100_000);
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
           }
        });

        t.start();

        Scanner scanner = new Scanner(System.in);
        System.out.println("输入整数 0 让线程 t 结束");
        int n = scanner.nextInt();
        if(n == 0){
            //不仅可以设置标志位,还可以处理sleep等导致线程阻塞的方法。
            t.interrupt();
        }
    }
}
  • Thread.currentThread()是用来获取到当前线程的引用,这个lambda在哪个线程中执行的,得到的引用就是哪个线程Thread引用。
  • Thread.currentThread() 和 t 其实是指向同一个对象的,但是却不能使用 t ,是因为执行这个代码时,一定是先针对Thread()中的参数,再进行求值,再执行Thread的构造方法,再将构造方法的结果赋值给 t 。所以t 此时还没有创建成功不能使用。
  • 使用 interrupt() 这个方法不仅可以修改标志位,还可以把sleep唤醒,不需要等待直接结束。但上述代码会出现异常信息,是因为catch中又抛出了一个异常,只作为例子使用。实际中catch中的代码需要根据实际有不同的作用。
4.3 线程等待

线程之间是随机调度的,无法确定哪个线程先执行,而有时需要等待某个线程结束再进行下一个线程,此时需要干预两个线程的结束顺序。 eg: 首先让 t 线程计算,主线程等待 t 线程结束后打印其结果。

代码语言:javascript
复制
public class Demo10 {

    private static int result = 0;

    public static void main(String[] args) throws InterruptedException {
        //创建一个线程计算1+ 2+ 3+ ...+ 1000;
        //主线程在这个计算线程完毕后,打印此处结果
        Thread t = new Thread(() -> {
           for(int i = 0; i <= 1000; i++){
               result += i;
           }
            System.out.println("t 线程结束");
        });


        t.start();

        //Thread.sleep(1000);

        System.out.println(result);
    }
}

发现打印结果为0,是因为不确定先执行打印还是先执行循环,也可能循环执行一半再执行打印。不代表执行"概率均等",先执行打印的概率更大,start方法需要调用系统api,系统创建线程,新线程参与调度,都需要花费时间。但是当加上sleep给t一点执行时间时,t线程一定是先执行完的,因为这段计算逻辑执行速度极快,即使sleep(1)也是t线程先执行完毕,但当逻辑很复杂,如何评估计算时间呢?

  • 相比于使用sleep方法,使用join方法为更优解。
代码语言:javascript
复制
public class Demo10 {

    private static int result = 0;

    public static void main(String[] args) throws InterruptedException {
        //创建一个线程计算1+ 2+ 3+ ...+ 1000;
        //主线程在这个计算线程完毕后,打印此处结果
        Thread t = new Thread(() -> {
           for(int i = 0; i <= 1000; i++){
               result += i;
           }
            System.out.println("t 线程结束" + result);
        });


        t.start();
        //join等待 t 线程结束
        t.join();

        System.out.println(result);
    }
}

此时在main线程中使用t.join()就是让main线程等待 t 线程执行完毕在执行。 join 的阻塞等待时间是不确定的,取决于 t 线程何时退出。 任何两个线程都是可以进行等待的,t 线程也是可以等待main线程的。

  • join 的几个方法

方法

说明

public void join()

等待线程结束

public void join(long millis)

等待线程结束,最多等 millis 毫秒

public void join(long millis, int nanos)

同理,但可以更高精度,精确到纳秒

4.4 获取当前线程引用

方法

说明

public static Thread currentThread()

返回当前线程对象的引用

eg:

代码语言:javascript
复制
public class Demo12 {
    public static void main(String[] args) {
        Thread t = Thread.currentThread();
        System.out.println(t.getName());
    }
}
4.5 线程休眠

方法

说明

public static void sleep(long millis) throws InterruptedException

休眠当前线程 millis 毫秒

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

可以更高精度的休眠

特殊情况:sleep(0) - 线程主动放弃 cpu 注:某个线程调度到cpu上,可能会一口气执行很多逻辑,正常情况下,每个线程都会有一定的执行时间(时间片)。可能线程1执行一段时间,线程2再执行一段时间… 而sleep(0) 使线程从就绪状态调度到阻塞状态再立刻调度到就绪状态,虽然时间可能极短,但起到的效果可以降低CPU的使用率。

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、认识线程
    • 1.1 什么是线程和进程
    • 1.2 为什么要有线程
    • 1.3 进程和线程的区别与联系
  • 二、创建线程
    • 2.1 继承Thread
    • 2.2 实现Runnable接口
    • 2.3 继承Thread,使用匿名内部类
    • 2.4 实现Runnable,使用匿名内部类
    • 2.5 使用lambda 表达式
  • 三、Thread的常见方法
    • 3.1 Thread 的常见构造方法
    • 3.2 Thread 的常见属性
      • 关于后台线程
  • 四、Thread 的其他操作
    • 4.1 启动一个线程 - start()
    • 4.2 中断一个线程 - Interrupt
      • 4.2.1 自定义变量作为标志位
      • 4.2.2 使用Thread.currentThread().isInterrupted() 代替自定义标志位
    • 4.3 线程等待
    • 4.4 获取当前线程引用
    • 4.5 线程休眠
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档