什么是进程?什么是线程?什么是协程?创建线程有几种方式?线程有哪些方法?都有什么作用?

一、进程、线程、协程的定义与区别

1. 进程(Process)

进程是操作系统资源分配的基本单位 ,是程序的一次动态执行过程。每个进程拥有独立的内存空间(代码、数据、栈等)、文件描述符等系统资源,进程之间相互隔离,通过进程间通信(IPC,如管道、 socket 等)交换数据。
特点:资源占用大,切换成本高(需切换内存映射、寄存器等内核状态),并发能力低(系统能同时运行的进程数量有限)。

2. 线程(Thread)

线程是操作系统调度的基本单位 ,隶属于进程,是进程内的一个执行单元。一个进程可以包含多个线程,所有线程共享进程的内存空间和资源(如堆内存、全局变量),但拥有独立的栈内存和程序计数器。
特点:资源占用小(共享进程资源),切换成本中等(需切换栈、程序计数器等用户态状态,无需切换内存映射),并发能力中等(支持数百至数千个线程)。

3. 协程(Coroutine)

协程是用户态的轻量级线程 ,由程序(而非操作系统内核)调度,本质是"可暂停、可恢复"的函数。多个协程通常运行在同一个线程中,通过主动让出 CPU(yield)实现切换,不依赖操作系统调度。
特点:资源占用极小(切换仅需保存用户态栈和状态),切换成本极低(用户态操作),并发能力极高(单线程可支持数万至数百万个协程),适用于 I/O 密集型任务。

用王者荣耀来理解进程与线程

❤1、线程在进程下进行 (单独的英雄角色、野怪、小兵肯定不能运行)

❤2、进程之间不会相互影响,主线程结束将会导致整个进程结束 (两把游戏之间不会有联系和影响。你的水晶被推掉,你这把游戏就结束了)

❤3、不同的进程数据很难共享 (两把游戏之间很难有联系,有联系的情况比如上把的敌人这把又匹配到了)

❤4、同进程下的不同线程之间数据很容易共享 (你开的那一把游戏,你可以看到每个玩家的状态------生死,也可以看到每个玩家的出装等等)

❤5、进程使用内存地址可以限定使用量 (开的房间模式,决定了你可以设置有多少人进,当房间满了后,其他人就进不去了,除非有人退出房间,其他人才能进)

三者核心区别

维度 进程 线程 协程
资源分配 独立内存空间 共享进程内存 共享线程内存
调度者 操作系统内核 操作系统内核 用户程序(语言 runtime)
切换成本 极高(内核态) 中(内核态) 极低(用户态)
并发能力 低(受系统资源限制) 中(数百至数千) 极高(数万至数百万)

二、Java 中创建线程的几种方式

Java 中创建线程的核心是定义线程要执行的任务(run()call() 方法),主要有 3 种方式:

1. 继承 Thread 类,重写 run() 方法

Thread 类本身实现了 Runnable 接口,继承后重写 run() 方法定义任务,通过 start() 启动线程。

java 复制代码
class MyThread extends Thread {
    @Override
    public void run() { // 定义线程任务
        System.out.println("线程执行");
    }
}

// 使用:创建实例并调用 start()
new MyThread().start();

特点:简单直接,但 Java 单继承限制,灵活性低,无返回值。

2. 实现 Runnable 接口,重写 run() 方法

Runnable 是函数式接口(仅 run() 方法),实现后将实例传给 Thread 类启动线程。

java 复制代码
class MyRunnable implements Runnable {
    @Override
    public void run() { // 定义线程任务
        System.out.println("线程执行");
    }
}

// 使用:包装到 Thread 中并启动
new Thread(new MyRunnable()).start();

特点:避免单继承限制,推荐优先使用,无返回值,可共享任务实例(多线程执行同一任务)。

3. 实现 Callable 接口,结合 Future 获取返回值

Callable 是带返回值的任务接口(call() 方法),通过 FutureTask 包装后传给 Thread,可获取任务结果。

java 复制代码
class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception { // 带返回值的任务
        return 1 + 1;
    }
}

// 使用:通过 FutureTask 获取结果
FutureTask<Integer> task = new FutureTask<>(new MyCallable());
new Thread(task).start();
int result = task.get(); // 阻塞获取结果(可能抛出异常)

特点:支持返回值和异常抛出,适合需要任务结果的场景,实现稍复杂。

三、线程的常用方法及作用

下面通过具体示例展示线程中常用方法的实际运用,涵盖线程启动、阻塞、同步、中断等核心场景。Java 中 Thread 类提供了多个方法用于控制线程行为,核心方法如下:

1. 线程启动与执行相关

  • start():启动线程,向操作系统申请创建新线程,线程进入就绪状态,获取 CPU 后自动执行 run() 方法。不可重复调用(多次调用抛异常)。
  • run():定义线程要执行的任务逻辑,直接调用时会在当前线程同步执行(不创建新线程)。

1. start()run():启动线程与执行任务

java 复制代码
public class StartAndRunDemo {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println("任务执行,线程名:" + Thread.currentThread().getName());
        }, "MyThread");

        // start():启动新线程,异步执行
        System.out.println("调用start()前,当前线程:" + Thread.currentThread().getName());
        thread.start(); // 新线程名为"MyThread"

        // run():直接调用,在当前线程(main)同步执行
        System.out.println("\n调用run()前,当前线程:" + Thread.currentThread().getName());
        thread.run(); // 复用main线程
    }
}

输出

scss 复制代码
调用start()前,当前线程:main
调用run()前,当前线程:main
任务执行,线程名:main
任务执行,线程名:MyThread

作用

  • start() 是真正启动新线程的唯一方式,会触发操作系统创建线程;
  • run() 只是普通方法,直接调用无并发效果。

2. 线程阻塞与等待相关

  • sleep(long millis):让当前线程暂停执行指定毫秒数,进入超时等待状态,期间不释放已持有的锁。超时后自动回到就绪状态。
  • join() / join(long millis):让当前线程等待目标线程执行完毕(或等待指定时间),期间当前线程进入等待/超时等待状态。常用于线程同步(如主线程等待子线程完成)。
  • wait() / wait(long timeout):(需在 synchronized 代码块中调用)让当前线程释放锁并进入等待/超时等待状态 ,需通过 notify()notifyAll() 唤醒。

2. sleep():让线程暂停执行

java 复制代码
public class SleepDemo {
    public static void main(String[] args) {
        new Thread(() -> {
            System.out.println("线程开始执行");
            try {
                // 暂停2秒(模拟耗时操作)
                Thread.sleep(2000); 
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程执行完毕");
        }).start();
    }
}

输出

复制代码
线程开始执行
(等待2秒后)
线程执行完毕

作用

让当前线程进入超时等待状态,释放CPU资源但不释放锁,常用于模拟延迟或控制执行节奏。

3. 线程状态与中断相关

  • interrupt():中断线程(设置中断标志),若线程处于 wait()sleep()join() 等阻塞状态,会抛出 InterruptedException 并清除中断标志。
  • isInterrupted():判断线程是否被中断(返回中断标志,不清除标志)。
  • interrupted():静态方法,判断当前线程是否被中断(返回标志后清除中断标志)。
  • isAlive():判断线程是否处于活动状态(已启动且未终止)。

3. join():等待其他线程完成

java 复制代码
public class JoinDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                Thread.sleep(1000);
                System.out.println("线程t1执行完毕");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread t2 = new Thread(() -> {
            try {
                Thread.sleep(2000);
                System.out.println("线程t2执行完毕");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        t1.start();
        t2.start();

        // 主线程等待t1和t2执行完毕后再继续
        t1.join(); 
        t2.join(); 
        System.out.println("所有线程执行完毕,主线程继续");
    }
}

输出

复制代码
线程t1执行完毕
线程t2执行完毕
所有线程执行完毕,主线程继续

作用

实现线程同步,让当前线程阻塞等待目标线程完成,常用于主线程等待子线程处理结果。

4. 线程调度相关

  • yield():当前线程主动让出 CPU 时间片,回到就绪状态,让优先级相同或更高的线程有机会执行(仅为建议,操作系统可能忽略)。
  • setPriority(int newPriority):设置线程优先级(1-10,默认 5),优先级高的线程更可能被调度,但不保证执行顺序。

4. interrupt() 与中断检测:优雅终止线程

java 复制代码
public class InterruptDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            // 循环执行,通过中断标志判断是否退出
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("线程运行中...");
                try {
                    Thread.sleep(500); // 若在sleep中被中断,会抛异常
                } catch (InterruptedException e) {
                    // 捕获异常后,需手动再次设置中断标志(因为异常会清除标志)
                    Thread.currentThread().interrupt();
                    System.out.println("线程被中断,准备退出");
                }
            }
            System.out.println("线程已退出");
        });

        thread.start();
        Thread.sleep(2000); // 主线程等待2秒
        thread.interrupt(); // 中断子线程
    }
}

输出

erlang 复制代码
线程运行中...
线程运行中...
线程运行中...
线程运行中...
线程被中断,准备退出
线程已退出

作用

  • interrupt() 用于通知线程"需要中断"(设置中断标志),而非强制终止;
  • 线程需通过 isInterrupted() 检测标志或捕获 InterruptedException 实现优雅退出。

5. 线程销毁相关

  • stop()(已废弃):强制终止线程,可能导致资源未释放(如锁未释放),不安全,不推荐使用。
  • 正常终止:线程执行完 run() 方法或因未捕获异常退出,进入终止状态,无法再次启动。

5. wait()notify():线程间通信(生产者-消费者模型)

java 复制代码
public class WaitNotifyDemo {
    private static final Object lock = new Object();
    private static int count = 0;
    private static final int MAX = 5;

    public static void main(String[] args) {
        // 生产者线程:生产数据
        new Thread(() -> {
            while (true) {
                synchronized (lock) {
                    // 若数据满了,等待消费者消费
                    while (count == MAX) {
                        try {
                            lock.wait(); // 释放锁并等待
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    count++;
                    System.out.println("生产后, count = " + count);
                    lock.notify(); // 通知消费者
                }
            }
        }, "生产者").start();

        // 消费者线程:消费数据
        new Thread(() -> {
            while (true) {
                synchronized (lock) {
                    // 若数据空了,等待生产者生产
                    while (count == 0) {
                        try {
                            lock.wait(); // 释放锁并等待
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    count--;
                    System.out.println("消费后, count = " + count);
                    lock.notify(); // 通知生产者
                }
            }
        }, "消费者").start();
    }
}

输出(循环执行)

ini 复制代码
生产后, count = 1
生产后, count = 2
消费后, count = 1
消费后, count = 0
生产后, count = 1
...

作用

  • wait():让当前线程释放锁并进入等待状态,需在同步代码块中调用;
  • notify():唤醒一个等待该锁的线程,常用于线程间协作(如生产消费模型)。

6. yield():主动让出CPU

java 复制代码
public class YieldDemo {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("线程t1:" + i);
                Thread.yield(); // 主动让出CPU,给t2执行机会
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("线程t2:" + i);
                Thread.yield(); // 主动让出CPU,给t1执行机会
            }
        });

        t1.start();
        t2.start();
    }
}

输出(可能的结果)

erlang 复制代码
线程t1:0
线程t2:0
线程t1:1
线程t2:1
线程t1:2
线程t2:2
...

作用

当前线程主动放弃CPU时间片,回到就绪状态,让优先级相同或更高的线程有机会执行(仅为"建议",操作系统可能忽略)。

总结

1.线程方法的核心作用是控制线程的生命周期和协作方式

  • 启动与执行:start()(异步)、run()(同步);
  • 阻塞与等待:sleep()(不释放锁)、join()(等待线程完成)、wait()(释放锁,需配合锁使用);
  • 中断与退出:interrupt()(设置标志)、isInterrupted()(检测标志);
  • 协作通信:wait()/notify()(线程间传递信号);
  • 调度优化:yield()(主动让出CPU)。

实际开发中,需根据场景选择合适的方法,尤其注意线程安全和状态转换的正确性。

  • 进程是资源单位,线程是调度单位,协程是轻量级用户态线程,三者在资源占用和并发能力上差异显著。
  • Java 创建线程的核心是定义任务,常用 3 种方式(继承 Thread、实现 RunnableCallable),各有适用场景。
  • 线程方法主要用于启动、阻塞、同步、中断等控制,需理解其对线程状态的影响(如 start() 触发就绪,sleep() 进入超时等待)。
相关推荐
jakeswang1 小时前
去哪儿StarRocks实践
starrocks·后端
字节逆旅2 小时前
从一次爬坑看前端的出路
前端·后端·程序员
Chan163 小时前
【智能协同云图库】第七期:基于AI调用阿里云百炼大模型,实现AI图片编辑功能
java·人工智能·spring boot·后端·spring·ai·ai作画
mitt_3 小时前
go语言变量
开发语言·后端·golang
无限大63 小时前
二维数组搜索:从暴力地毯到二分神技的奇幻之旅
后端
bobz9654 小时前
最近玩了好多把 LOL
后端
爱欲无极4 小时前
基于Flask的微博话题多标签情感分析系统设计
后端·python·flask
cwkiller5 小时前
heapdump深度利用之信息泄露篇
后端
Olrookie7 小时前
若依前后端分离版学习笔记(五)——Spring Boot简介与Spring Security
笔记·后端·学习·spring·ruoyi
小白的代码日记7 小时前
基于 Spring Boot 的小区人脸识别与出入记录管理系统实现
java·spring boot·后端