当前位置: 首页 > news >正文

Java基础-多线程

一、线程创建方式(两种写法,解决“怎么开启多任务”问题 )

Java 里想让程序同时干多件事(比如一边下载、一边播放音乐 ),就得用多线程。而创建线程,最基础的就是继承 Thread 类实现 Runnable 接口这两种方式,本质都是定义“线程要做的事(run() 方法 )”,再启动线程让它执行。

1. 继承 Thread 类方式

// 第一步:自定义类继承 Thread,重写 run() 方法
class MyThread extends Thread {// run() 里写的是“这个线程要执行的任务”@Overridepublic void run() {// 这里简单打印线程名称,实际可以是复杂逻辑(比如文件下载、数据处理等)System.out.println("线程执行:" + Thread.currentThread().getName());}
}// 第二步:在 main 方法(主线程)里启动线程
public class Main {public static void main(String[] args) {// 创建自定义线程对象MyThread t1 = new MyThread();// 启动线程!注意:必须用 start(),不能直接调 run()!// start() 会让 JVM 给线程分配资源(比如栈空间),然后自动调用 run() 执行任务// 如果直接调 run(),就和普通方法调用一样,不会开启新线程,还是在主线程里执行t1.start();}
}
  • 关键点
    • 继承 Thread 后,run() 是线程的“任务入口”,JVM 会在启动线程后自动执行它。
    • start() 是真正“启动新线程”的关键方法,它会触发 JVM 的线程调度机制,给线程分配 CPU 时间片。
    • 缺点:Java 是单继承,要是你的类已经继承了其他类(比如 MyClass extends OtherClass ),就没法再继承 Thread 了,所以这种方式灵活性稍差。

2. 实现 Runnable 接口方式

// 第一步:自定义类实现 Runnable 接口,重写 run() 方法
class MyRunnable implements Runnable {@Overridepublic void run() {// 同样,这里定义线程要做的事,比如打印线程名System.out.println("线程执行:" + Thread.currentThread().getName());}
}// 第二步:在 main 方法里,用 Thread 类“包装” Runnable 对象,再启动
public class Main {public static void main(String[] args) {// 创建 Runnable 任务对象(只是定义了任务,还没线程执行它)MyRunnable runnableTask = new MyRunnable();// 把任务丢给 Thread,让 Thread 来管理线程的启动、调度Thread t2 = new Thread(runnableTask);// 启动线程,和之前一样,start() 会触发 JVM 执行 run()t2.start();}
}
  • 关键点
    • Runnable 是个接口,只规定了 run() 方法,本身不涉及线程的“启动”能力。所以必须借助 Thread 类的构造方法,把 Runnable 任务“包装”进去,才能启动线程。
    • 优点:解决了单继承限制!比如MyRunnable 还能继承其他类(像 class MyRunnable extends OtherClass implements Runnable ),更灵活;而且,多个线程可以共享同一个 Runnable 对象(比如多个线程一起处理同一个任务队列 )。

举个共享的例子:

MyRunnable sharedTask = new MyRunnable();
// 线程 A 和 线程 B 共享同一个任务对象
Thread tA = new Thread(sharedTask, "线程A");
Thread tB = new Thread(sharedTask, "线程B");
tA.start();
tB.start();

这样,线程 A 和 B 会基于同一个 sharedTask 执行任务,适合处理“多个线程协作完成同一份工作”的场景(比如共同处理一个计数器、一个文件写入操作 )。

二、线程的生命周期(理解线程“一生”的状态变化 )

线程从创建到销毁,会经历一系列状态变化,图里总结的流程是:

新建(New)→ 可运行(Runnable)→ 运行(Running)→(阻塞/等待/超时等待)→ 可运行 → 运行 → 终止(Terminated)

1. 新建(New)

  • 场景:刚用 new 创建线程对象,还没调用 start() 的时候。比如 MyThread t1 = new MyThread(); 这一步,线程只是个“空壳”,JVM 还没给它分配真正的线程资源(比如栈、程序计数器等 )。
  • 此时程序没有任何变化,必须调 start() 才会进入下一步。

2. 可运行(Runnable)

  • 场景:调用 start() 后,线程进入这个状态。此时,线程已经“准备好执行了”,但得等 CPU 调度(JVM 有个“线程调度器”,决定哪个线程先执行 )。
  • 注意:“可运行”不代表真的在执行,只是在“就绪队列”里排队,等 CPU 时间片。比如电脑同时开了很多程序,CPU 同一时间只能干一件事(多核 CPU 可以并行,但调度逻辑更复杂 ),所以得排队。

3. 运行(Running)

  • 场景:CPU 选中了这个线程,开始执行 run() 方法里的代码,真正“干活”的状态。
  • 能持续多久:取决于 CPU 时间片!时间片用完,线程会回到可运行状态,重新排队;如果 run() 里的代码很快执行完(比如就打印一句话 ),也会直接进入终止状态。

4. 阻塞/等待/超时等待(Blocked / Waiting / Timed Waiting)

这三类状态都是线程“暂停执行”的情况,原因不同,处理方式也不同:

  • 阻塞(Blocked)

    • 场景:线程想进入 synchronized 修饰的代码块/方法,但锁被其他线程占用了,就会进入阻塞状态,一直等到锁释放。

    • 例子: 线程 B 会卡在进入 synchronized 的地方,直到线程 A 释放锁。

      synchronized (lock) {// 假设线程 A 先拿到锁,线程 B 想进来就会阻塞
      }
  • 等待(Waiting)

    • 场景:线程调用了 wait()(必须在 synchronized 里调用 )、join() 等方法,会进入“无限期等待”,直到被其他线程唤醒

    • 例子:

      synchronized (lock) {// 线程调用 wait(),释放锁,进入等待lock.wait();// 必须等其他线程调用 lock.notify() 或 notifyAll(),才会被唤醒,回到可运行状态
      }
  • 超时等待(Timed Waiting)

    • 场景:线程调用了 sleep(long 毫秒)wait(long 毫秒)join(long 毫秒) 等方法,会进入“有限期等待”,到时间自动唤醒,或者提前被其他线程唤醒。

    • 例子: 或者:

      // 线程睡 3 秒,期间不占用 CPU,到时间自动回到可运行状态
      Thread.sleep(3000);
      synchronized (lock) {// 最多等 5 秒,要是没人唤醒,到时间也会自己醒lock.wait(5000);
      }
  • 回到可运行状态

    不管是阻塞、等待还是超时等待,只要“阻碍因素消失”(比如锁释放了、被其他线程唤醒了、超时时间到了 ),线程就会回到 可运行(Runnable) 状态,重新排队等 CPU 调度。

5. 终止(Terminated)

  • 场景:run() 方法执行完毕(正常跑完所有代码 ),或者线程被强制中断(比如抛了未捕获的异常 ),线程就会进入终止状态,生命周期结束,不能再重启
  • 注意:线程终止后,就算调用 start() 也没用,会报错!线程对象一旦终止,就“报废”了,想再执行任务,得重新 new 一个。

三、线程常用方法

这些方法是 Thread 类或 Object 类(waitnotify 等是 Object 的方法 )提供的,用来控制线程的状态、行为,解决“线程之间怎么配合、怎么调度”的问题。

方法作用 & 细节注意事项
start()启动线程!让 JVM 为线程分配资源(栈、PC 等 ),并把线程放入“可运行”队列,等待 CPU 调度。必须用这个方法启动线程,不能直接调 run()(直接调 run() 就是普通方法调用,不会开新线程 )。线程只能 start() 一次,第二次调会抛 IllegalThreadStateException 异常。
run()线程的“任务逻辑”入口,JVM 会在 start() 后自动调用它。你在这方法里写线程要做的事(比如循环、IO 操作等 )。直接调用 run() 不会开新线程,就是普通方法执行,谨慎使用!
sleep(long millis)让当前线程“休眠”指定毫秒数,不释放锁(如果当前线程拿着锁,sleep 期间锁不会放 ),到时间后回到“可运行”状态。静态方法,直接 Thread.sleep(1000); 调用;可能会抛出 InterruptedException(比如休眠时被其他线程中断 ),需要处理异常。
join()当前线程等待另一个线程执行完毕。比如线程 A 调用 threadB.join(),线程 A 会进入“等待”,直到 threadB 的 run() 执行完,线程 A 才会继续跑。常用来“协调线程执行顺序”,比如主线程等子线程跑完再汇总结果;也会抛 InterruptedException
wait() / notify() / notifyAll()- wait():让当前线程释放锁,进入“等待”状态,必须在 synchronized 代码块/方法里调用。<br>- notify():唤醒一个等待在同一个锁上的线程(随机选一个 )。<br>- notifyAll():唤醒所有等待在同一个锁上的线程。这三个方法都是 Object 类的方法(因为锁可以是任意对象 ),必须在 synchronized 里用,否则抛 IllegalMonitorStateException 异常。
interrupt()“中断”线程,但不是强制终止(别理解成杀死线程 )。会设置线程的“中断标志位”,如果线程在 sleepwaitjoin 等方法里,会抛出 InterruptedException,让线程感知到“被中断”,然后自己决定怎么处理(比如提前结束任务 )。只是打个“中断标记”,具体怎么响应中断,得看线程内部逻辑。比如:<br>java<br>if (Thread.currentThread().isInterrupted()) {<br> // 自己处理中断,比如结束循环、返回结果<br>}<br>
setPriority(int priority)设置线程的优先级(1 - 10,默认 5 ),只是给线程调度器“建议”,不保证优先级高的线程一定先执行。优先级高的线程被 CPU 选中的概率大一些,但别依赖它控制执行顺序(不同系统调度策略不同 )。
yield()让当前线程“让出”CPU 资源,回到“可运行”状态,重新和其他线程一起竞争 CPU。只是建议,调度器可能不理会。常用来“给优先级低的线程机会”,但实际效果看 JVM 心情,别当精确控制手段。

四、线程同步与锁(解决“多线程抢资源”问题,避免数据混乱 )

多线程同时访问共享资源(比如同一个变量、同一个文件 )时,容易出现“数据竞争”,导致结果混乱。线程同步就是解决这个问题的,核心思路是让多个线程“有序访问”共享资源,常用 synchronizedLock 接口两种方式。

1. synchronized 关键字(内置锁,简单直接 )

synchronized 可以修饰方法代码块,保证同一时间只有一个线程能进入“被保护的区域”,从而避免多线程冲突。

(1)修饰代码块(指定锁对象 )

class BankAccount {private int balance = 1000;// 自定义锁对象(也可以用 this、字节码对象等当锁,看需求 )private final Object lock = new Object();public void withdraw(int amount) {// 同步代码块:只有拿到 lock 锁的线程,才能进这个块synchronized (lock) {if (balance >= amount) {balance -= amount;System.out.println(Thread.currentThread().getName() + " 取款 " + amount + ",余额:" + balance);}}}
}
  • 原理:线程想进入 synchronized (lock) 代码块,必须先拿到 lock 对象的“锁”。如果锁被其他线程占用,当前线程就会进入阻塞(Blocked) 状态,直到锁释放。这样就保证了,同一时间只有一个线程能修改 balance,避免多线程同时减余额导致数据错误。

(2)修饰方法(隐式锁对象 )

  • 修饰实例方法

    public synchronized void withdraw(int amount) {// 锁对象是 this(当前实例)if (balance >= amount) {balance -= amount;}
    }

    相当于 synchronized (this) { ... },锁是当前对象实例。

  • 修饰静态方法

    public static synchronized void withdraw(int amount) {// 锁对象是 类的字节码对象(比如 BankAccount.class )
    }

    相当于 synchronized (BankAccount.class) { ... },锁是类对象。

  • 优缺点:

    优点:简单易用,JVM 自动管理锁的获取和释放(进入同步块自动拿锁,退出自动放锁,包括抛异常的情况 )。

    缺点:不够灵活(比如想尝试获取锁、设置超时时间,synchronized 做不到 );如果同步范围太大,会影响性能(多个线程排队太久 )。

2. Lock 接口(显式锁,更灵活 )

java.util.concurrent.locks.Lock 是更灵活的锁机制,常用实现类是 ReentrantLock(可重入锁,和 synchronized 类似,同一线程可以多次拿锁 )。

基本用法:

class BankAccount {private int balance = 1000;// 创建 Lock 对象,常用 ReentrantLockprivate Lock lock = new ReentrantLock();public void withdraw(int amount) {// 手动加锁lock.lock();try {// 临界区:操作共享资源的代码if (balance >= amount) {balance -= amount;System.out.println(Thread.currentThread().getName() + " 取款 " + amount + ",余额:" + balance);}} finally {// 手动释放锁!必须在 finally 里放,否则一旦代码抛异常,锁永远不会释放,导致死锁lock.unlock();}}
}
  • 原理:和 synchronized 类似,lock() 会尝试获取锁,拿到锁的线程才能执行临界区代码;执行完后,必须在 finally 里调用 unlock() 释放锁,保证锁一定能释放(即使代码抛异常 )。

优点(对比 synchronized ):

  • 更灵活

    • 可以尝试获取锁(tryLock() ),拿不到锁可以不等待,去做别的事;
    • 可以设置超时时间(tryLock(long time, TimeUnit unit) ),避免线程无限阻塞;
    • 可以实现更复杂的锁逻辑(比如读写锁 ReentrantReadWriteLock,读锁共享、写锁独占,适合读多写少的场景 )。
  • 可中断

    lockInterruptibly() 方法允许在获取锁的过程中响应中断(比如等待锁时,其他线程调用了 interrupt(),可以提前放弃等待

http://www.dtcms.com/a/324992.html

相关文章:

  • lesson34:深入理解Python线程:从基础到实战优化
  • hysAnalyser --- 支持文件转播UDP/RTP实时流功能
  • CompletableFuture实现Excel 多个sheet页批量导出
  • 【数据分析】循环移位岭回归分析:光遗传学冻结行为模式研究
  • 【PyTorch】单目标检测项目部署
  • MPLS的基本工作原理
  • AI玩具新浪潮:百亿资本涌入,情感计算重塑陪伴经济
  • WAIC2025逛展分享·AI鉴伪技术洞察“看不见”的伪造痕迹
  • JAVA中关于Stream流的使用
  • 虚拟主机示例
  • vuhub drippingblues靶场攻略
  • Windows环境下私有化部署Dify,并接入通义千问模型
  • UNet改进(31):基于Adaptive Attention的UNet设计与实践
  • 基于Spring SSE构建实时监控系统
  • Python 的列表 list 和元组 tuple 有啥本质区别?啥时候用谁更合适?
  • TC39x STM(System Timer)学习记录
  • 压力测试等工具源码包编译及使用方法
  • Vulnhub doubletrouble 靶场复现 详细攻略
  • Knuth‘s TwoSum Algorithm 原理详解
  • MyBatis 核心入门:从概念到实战,一篇掌握简单增删改查
  • 【东枫科技】FR3 可扩展测试平台,适用于 6G 研究与卫星通信,高达 1.6 GHz 的带宽
  • 【自动化运维神器Ansible】playbook案例解析:Tags组件实现任务选择性执行
  • 【01】华勤技术股份有限公司——华勤C++笔试,题目记录及解析
  • Java基础-使用反射做一个简易框架
  • Python 实例属性和类属性
  • 【PyTorch】单目标检测项目
  • vulnhub-Drippingblues靶机
  • Typora结合PicGo + 使用Gitee搭建个人免费图床
  • 计算机网络---IP(互联网协议)
  • 2025年6月电子学会全国青少年软件编程等级考试(Python六级)真题及答案