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

Java多线程JUC

1. 什么是多线程?

多线程是一种程序执行方式,它允许一个程序在同一时间运行多个线程,每个线程可以看作是程序中的一个执行单元。多线程的目的是提高程序的并发性、利用多核CPU的性能、提升执行效率。

2. 并发和并行

  • 并发:在同一时刻,有多个指令在单个CPU上交替执行
  • 并行:在同一时刻,有多个指令在多个CPU上同时执行

3. 多线程的实现方式

3.1. 继承Thread类的方式进行实现

直接继承 java.lang.Thread 类,重写其中的 run() 方法,把线程要执行的代码写在里面。

  • 优点:简单直观,适合小型程序
  • 缺点:Java 单继承限制,如果继承了 Thread,就不能继承其他类,不灵活
package com.fg.thread1;public class MyThread extends Thread {@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(getName() + "hello juc");}}
}
package com.fg.thread1;public class ThreadDemo1 {public static void main(String[] args) {MyThread t1 = new MyThread();MyThread t2 = new MyThread();t1.setName("线程1");t2.setName("线程2");t1.start();t2.start();}
}

在这里插入图片描述

3.2. 实现Runnable接口的方式进行实现

通过实现 Runnable 接口,把任务逻辑与线程解耦,使得任务可以被多个线程复用。

  • 更加灵活,推荐使用
  • 可与线程池配合使用
  • 便于资源共享
package com.fg.thread2;public class MyRun implements Runnable {@Overridepublic void run() {for (int i = 0; i < 100; i++) {Thread t = Thread.currentThread();System.out.println(t.getName() + "hello juc");}}
}
package com.fg.thread2;public class ThreadDemo2 {public static void main(String[] args) {MyRun mr = new MyRun();Thread t1 = new Thread(mr);Thread t2 = new Thread(mr);t1.setName("t1");t2.setName("t2");t1.start();t2.start();}
}

3.3. 利用Callable接口和Future接口方式实现

Callable 接口与 Runnable 类似,但可以有返回值和抛出异常,配合 Future 对象获取异步执行结果。

  • 有返回值,比 Runnable 更强大

  • 异步编程的基础

package com.fg.thread3;import java.util.concurrent.Callable;public class MyCallable implements Callable<Integer> {@Overridepublic Integer call() throws Exception {int sum = 0;for (int i = 0; i < 100; i++) {sum += i;}return sum;}
}
package com.fg.thread3;import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;public class ThreadDemo3 {public static void main(String[] args) throws ExecutionException, InterruptedException {// 创建MyCallable对象,表示多线程要执行的任务MyCallable mc = new MyCallable();// 创建FutureTask对象,作用管理多线程运行的结果FutureTask<Integer> ft = new FutureTask<>(mc);Thread t1 = new Thread(ft);t1.start();Integer result = ft.get();System.out.println(result);}
}

4. 常用成员方法

在这里插入图片描述

4.1. 线程的优先级

Java 中的线程优先级(Thread Priority)是指调度器(线程调度器)用来决定哪个线程应该先执行的一个提示性参数。它不能保证执行顺序,但可以影响执行倾向。

常量含义
Thread.MIN_PRIORITY1最低优先级
Thread.NORM_PRIORITY5默认优先级
Thread.MAX_PRIORITY10最高优先级
Thread t1 = new Thread(() -> {System.out.println("低优先级线程");
});
t1.setPriority(Thread.MIN_PRIORITY); // 1Thread t2 = new Thread(() -> {System.out.println("高优先级线程");
});
t2.setPriority(Thread.MAX_PRIORITY); // 10

4.2. 守护线程(Daemon Thread)

守护线程是一种服务型线程,它在后台运行,为其他线程提供服务,比如垃圾回收线程。

当所有用户线程(非守护线程)都结束时,JVM会自动 退出,并杀掉所有守护线程。

Thread t = new Thread(() -> {while (true) {System.out.println("守护线程运行中...");}
});
t.setDaemon(true);  // 设置为守护线程(必须在 start() 前)
t.start();

4.3. 礼让线程(Yield)

yield() 是一个静态方法,用于提示线程调度器:当前线程愿意“礼让”,让其他相同或更高优先级的线程先执行。

并不保证一定会让出CPU,也可能继续执行当前线程。

Thread t = new Thread(() -> {for (int i = 0; i < 5; i++) {System.out.println("线程运行:" + i);Thread.yield();  // 礼让}
});
t.start();

4.4. 插入线程(Join)

join() 方法表示:当前线程等待另一个线程执行完毕后再继续执行。

主要用于线程之间的依赖关系控制,比如:先下载再处理数据。

Thread t1 = new Thread(() -> {System.out.println("子线程执行中...");
});
t1.start();t1.join();  // 主线程等待 t1 执行完毕后再继续
System.out.println("主线程继续执行");

5. 线程的状态

在这里插入图片描述

  • 新建状态(NEW):线程被创建但尚未启动。
  • 就绪状态(RUNNABLE):线程可以在任意时刻运行。处于这个状态的线程可能正在运行,也可能正在等待CPU分配时间片。
  • 阻塞状态(BLOCKED):线程被阻止执行,因为它正在等待监视器锁定。其他线程正在占用所需的锁定,因此线程被阻塞。
  • 等待状态(WAITING):线程进入等待状态,直到其他线程显式地唤醒它。线程可以调用Object类的wait()方法、join()方法或Lock类的条件等待方法进入此状态。
  • 计时等待(TIMED_WAITING):线程进入计时等待状态,等待一段指定的时间。线程可以调用Thread.sleep()方法、Object类的wait()方法、join()方法或Lock类的计时等待方法进入此状态。
  • 结束状态(TERMINATED):线程完成了其任务,或者因为异常或其他原因而终止运行。

6. 线程的安全问题

6.1. 例子

例子:假如有100张票,有三个窗口售卖。

package com.fg.thread4;public class ThreadDemo4 {public static void main(String[] args) {MyThread t1 = new MyThread();MyThread t2 = new MyThread();MyThread t3 = new MyThread();t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");t1.start();t2.start();t3.start();}
}
package com.fg.thread4;public class MyThread extends Thread {static int ticket = 0;@Overridepublic void run() {while (true) {if (ticket < 100) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}ticket++;System.out.println(getName() + "正在卖第" + ticket + "张票");} else {break;}}}
}

以上代码会出现线程安全问题:

  • 同一张票被多个线程重复卖出(如两个线程都判断 ticket < 100 成立)
  • 票号跳过或重复(非原子性导致结果错乱)
  • 总票数超过100张

解决方法:

1. 使用 synchronized关键字加锁

  • 使用 synchronized 锁定 MyThread.class(因为 ticket 是静态变量,多个线程共享)
  • 保证if判断 + ticket++ + 输出 是一个原子操作
public class MyThread extends Thread {static int ticket = 0;@Overridepublic void run() {while (true) {// 同步代码块synchronized (MyThread.class) {if (ticket < 100) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}ticket++;System.out.println(getName() + "正在卖第" + ticket + "张票");} else {break;}}}}
}

2. 使用ReentrantLock

  • ReentrantLock java.util.concurrent.locks 包中的一个类,它提供了与 synchronized 类似的互斥锁功能
  • 使用 ReentrantLock 时,更适合在复杂多线程逻辑中替代 synchronized
public class MyThread extends Thread {static int ticket = 0;static ReentrantLock lock = new ReentrantLock();@Overridepublic void run() {while (true) {lock.lock(); // 加锁try {if (ticket < 100) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}ticket++;System.out.println(getName() + "正在卖第" + ticket + "张票");} else {break;}} finally {lock.unlock(); // 确保释放锁}}}
}

6.2. 同步方法

就是把 synchronized 关键字加到方法上。

修饰符 synchronized 返回值类型 方法名(方法参数) {...}
  • 同步方法是锁住方法里面所有的代码
  • 锁对象不能自己指定,静态方法:当前类的字节码文件对象,非静态方法:this
package com.fg.thread5;public class ThreadDemo5 {public static void main(String[] args) {MyRunnable mr = new MyRunnable();Thread t1 = new Thread(mr);Thread t2 = new Thread(mr);Thread t3 = new Thread(mr);t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");t1.start();t2.start();t3.start();}
}
package com.fg.thread5;import com.fg.thread4.MyThread;public class MyRunnable implements Runnable {int ticket = 0;@Overridepublic void run() {while (true) {if (method()) break;}}private synchronized boolean method() {synchronized (MyThread.class) {if (ticket < 100) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}ticket++;System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");} else {return true;}}return false;}
}

7. 死锁

死锁(Deadlock) 是指两个或多个线程在执行过程中,因为互相持有对方需要的资源而无限等待下去,导致程序无法继续执行的现象。

例子: 假设两个人(线程 A 和线程 B):

A 拿着筷子1,想要筷子2吃饭;

B 拿着筷子2,想要筷子1吃饭;

结果:谁也不肯放下自己手上的筷子,就这样一直等对方让出来,谁都吃不上饭 —— 这就是死锁!

public class DeadLockDemo {private static final Object resourceA = new Object();private static final Object resourceB = new Object();public static void main(String[] args) {Thread t1 = new Thread(() -> {synchronized (resourceA) {System.out.println("线程1 拿到了 resourceA 锁");try { Thread.sleep(100); } catch (InterruptedException ignored) {}synchronized (resourceB) {System.out.println("线程1 拿到了 resourceB 锁");}}});Thread t2 = new Thread(() -> {synchronized (resourceB) {System.out.println("线程2 拿到了 resourceB 锁");try { Thread.sleep(100); } catch (InterruptedException ignored) {}synchronized (resourceA) {System.out.println("线程2 拿到了 resourceA 锁");}}});t1.start();t2.start();}
}

两个线程:

t1 先拿 resourceA,等 resourceB

t2 先拿 resourceB,等 resourceA

就卡住了,相互等待,形成死锁。

8. 生产者和消费者

8.1. 等待唤醒机制

Java 中的“生产者-消费者模式”是多线程开发的经典案例,核心在于多个线程之间协作:

  • 生产者不断“生产资源”
  • 消费者不断“消费资源”
  • 双方通过等待-唤醒机制wait()/notify()配合使用,避免资源争抢或空转
方法说明
wait()当前线程等待并释放锁,直到被唤醒
notify()唤醒等待中的一个线程
notifyAll()唤醒所有等待线程

例子:

  • 厨师线程负责“炒菜”
  • 吃货线程负责“吃菜”
  • 一次只能炒一个菜(盘子最多只能放一个),吃了才能继续炒
package com.fg.thread6;public class Dish {private String food;  // 菜名,null表示盘子空// 厨师做菜public synchronized void cook(String dishName) throws InterruptedException {while (food != null) {System.out.println("厨师正在等待,盘子中有菜:" + food);wait();  // 等待吃货吃菜}this.food = dishName;System.out.println("厨师做好菜了:" + dishName);notifyAll(); // 通知吃货吃}// 吃货吃菜public synchronized void eat() throws InterruptedException {while (food == null) {System.out.println("吃货正在等待,盘子中没有菜");wait(); // 等待厨师做菜}System.out.println("吃货吃掉菜:" + food);food = null;notifyAll(); // 通知厨师做菜}
}
package com.fg.thread6;public class ThreadDemo6 {public static void main(String[] args) {Dish dish = new Dish();// 厨师线程Thread chef = new Thread(() -> {String[] menu = {"宫保鸡丁", "鱼香肉丝", "红烧肉"};int index = 0;while (true) {try {String dishName = menu[index % menu.length];dish.cook(dishName);Thread.sleep(800);index++;} catch (InterruptedException e) {e.printStackTrace();}}}, "厨师");// 吃货线程Thread foodie = new Thread(() -> {while (true) {try {dish.eat();Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}}, "吃货");chef.start();foodie.start();}
}

8.2. 阻塞队列方式实现

BlockingQueue是 Java 提供的线程安全的队列,具有以下特点:

  • 它内部已经帮你实现了加锁、wait() / notify() 等逻辑;
  • 如果队列满,put() 会阻塞生产者;
  • 如果队列空,take() 会阻塞消费者。
package com.fg.thread6;import java.util.concurrent.ArrayBlockingQueue;public class ThreadDemo6 {public static void main(String[] args) {// 创建一个盘子容量为3的阻塞队列ArrayBlockingQueue<String> dishQueue = new ArrayBlockingQueue<>(3);// 厨师线程Thread chef = new Thread(() -> {String[] menu = {"宫保鸡丁", "鱼香肉丝", "红烧肉"};int index = 0;try {while (true) {String dish = menu[index % menu.length];dishQueue.put(dish);   // 如果满了,会阻塞等待System.out.println("厨师做好菜了:" + dish);Thread.sleep(500);index++;}} catch (InterruptedException e) {e.printStackTrace();}});// 吃货线程Thread foodie = new Thread(() -> {try {while (true) {String dish = dishQueue.take();  // 如果空了,会阻塞等待System.out.println("吃货吃菜了:" + dish);Thread.sleep(800);}} catch (InterruptedException e) {e.printStackTrace();}});chef.start();foodie.start();}
}

9. 练习

9.1. 抢红包案例

假设有100块,分成了3个红包,现在有5个人去抢。

package com.fg.thread7;import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Random;public class MyThread extends Thread {static BigDecimal money = new BigDecimal("100.00");static int count = 3;static final BigDecimal MIN = new BigDecimal("0.01");  // 最小金额static Random random = new Random();@Overridepublic void run() {synchronized (MyThread.class) {if (count == 0) {System.out.println("来晚了,红包已被抢光");return;}BigDecimal amount;if (count == 1) {amount = money;} else {// 红包最大值 = 剩余金额 - (剩余红包 - 1) * MINBigDecimal max = money.subtract(MIN.multiply(BigDecimal.valueOf(count - 1)));// 生成 0 到 max 的随机金额,再加 MIN 保底double rand = random.nextDouble();amount = max.multiply(BigDecimal.valueOf(rand)).add(MIN);amount = amount.setScale(2, RoundingMode.DOWN);}money = money.subtract(amount);count--;System.out.println(getName() + " 抢到了红包:" + amount + " 元,剩余:" + count + " 个,余额:" + money);}}public static void main(String[] args) {// 创建 5 个线程,抢 3 个红包for (int i = 1; i <= 5; i++) {new MyThread().start();}}
}

9.2. 抽奖案例

有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池中的奖项为{10,5,20,50,100,200,500,800,2,80,300,700};
“抽奖箱2’创建两个抽奖箱(线程)设置线程名称分别为“抽奖箱1",随机从抽奖池中获取奖项元素并打印在控制台上。

package com.fg.thread7;import java.util.Random;public class MyThread extends Thread {Random random = new Random();public MyThread(String name) {super(name);}@Overridepublic void run() {while (true) {int prize;synchronized (ThreadDemo7.prizePool) {if (ThreadDemo7.prizePool.isEmpty()) {break;}int index = random.nextInt(ThreadDemo7.prizePool.size());prize = ThreadDemo7.prizePool.remove(index);}System.out.println(getName() + "抽中了" + prize + "元大奖");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
package com.fg.thread7;import java.util.ArrayList;
import java.util.List;public class ThreadDemo7 {public static final List<Integer> prizePool = new ArrayList<>();static {int[] prizes = {10, 5, 20, 50, 100, 200, 500, 800, 2, 80, 300, 700};for (int prize : prizes) {prizePool.add(prize);}}public static void main(String[] args) throws InterruptedException {MyThread t1 = new MyThread("抽奖箱1");MyThread t2 = new MyThread("抽奖箱2");t1.start();t2.start();}
}

在上一题基础上继续完成如下需求:
每次抽的过程中,不打印,抽完时一次性打印(随机)。

package com.fg.thread7;import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;public class MyThread extends Thread {private final Random random = new Random();private final List<Integer> myPrizes = new ArrayList<>();public MyThread(String name) {super(name);}@Overridepublic void run() {while (true) {int prize;synchronized (ThreadDemo7.prizePool) {if (ThreadDemo7.prizePool.isEmpty()) {break;}int index = random.nextInt(ThreadDemo7.prizePool.size());prize = ThreadDemo7.prizePool.remove(index);}myPrizes.add(prize);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}public int getMaxPrize() {return myPrizes.isEmpty() ? 0 : Collections.max(myPrizes);}public void printSummary() {int total = myPrizes.stream().mapToInt(i -> i).sum();int max = myPrizes.isEmpty() ? 0 : Collections.max(myPrizes);System.out.println(getName() + "总共抽中" + myPrizes.size() + "个奖项");System.out.println("分别为:");for (int i = 0; i < myPrizes.size(); i++) {System.out.print(myPrizes.get(i));if (i < myPrizes.size() - 1) {System.out.print(",");}}System.out.println();System.out.println("最高奖项为" + max + "元,总计额为" + total + "元");System.out.println("----------------------------------------------");}
}
package com.fg.thread7;import java.util.ArrayList;
import java.util.List;public class ThreadDemo7 {public static final List<Integer> prizePool = new ArrayList<>();static {int[] prizes = {10, 5, 20, 50, 100, 200, 500, 800, 2, 80, 300, 700};for (int prize : prizes) {prizePool.add(prize);}}public static void main(String[] args) throws InterruptedException {MyThread t1 = new MyThread("抽奖箱1");MyThread t2 = new MyThread("抽奖箱2");t1.start();t2.start();t1.join();t2.join();t1.printSummary();t2.printSummary();int max1 = t1.getMaxPrize();int max2 = t2.getMaxPrize();if (max1 > max2) {System.out.println("在此次抽奖过程中," + t1.getName() + " 中产生了最大奖项,金额为 " + max1 + " 元");} else if (max2 > max1) {System.out.println("在此次抽奖过程中," + t2.getName() + " 中产生了最大奖项,金额为 " + max2 + " 元");} else {System.out.println("在此次抽奖过程中,两个抽奖箱都抽中了相同的最大奖项,金额为 " + max1 + " 元");}}
}

10. 线程池

线程池就是提前创建好多个线程,放在一个池子(集合)中。程序需要用线程时就从池中取出,执行完任务再归还,而不是每次都重新 new 一个线程。

创建方法描述
Executors.newFixedThreadPool(int n)固定大小线程池
Executors.newCachedThreadPool()缓存线程池(线程数量无限制)
Executors.newSingleThreadExecutor()单线程线程池(顺序执行任务)
Executors.newScheduledThreadPool(int n)可执行定时任务或周期性任务的线程池

自定义线程池:

ThreadPoolExecutor threadPool = new ThreadPoolExecutor(3,  // 核心线程数,可以小于06,            // 最大线程数,不能小于0,最大线程数60,           // 空闲线程最大存活时间TimeUnit.SECONDS,  // 时间单位new ArrayBlockingQueue<>(3),  // 阻塞(任务)队列Executors.defaultThreadFactory(),  // 创建线程工厂new ThreadPoolExecutor.AbortPolicy()  // 任务拒绝策略
);

相关文章:

  • 2025.05.23 Axure 动态面板学习笔记
  • Linux 的编辑器--vim
  • Apache 高级配置实战:从连接保持到日志分析的完整指南
  • 对WireShark 中的UDP抓包数据进行解析
  • Php JIT 使用详解
  • 从智能提效到产品赋能的架构实践
  • 【HW系列】—web常规漏洞(SQL注入与XSS)
  • RocketMQ 5.0 核心概念与架构解析
  • python | vscode | 使用uv快速创建虚拟环境(实现一个项目一个虚拟环境,方便环境管理)
  • 【排序算法】典型排序算法和python 实现
  • 前端流行框架Vue3教程:28. Vue应用
  • 【排序算法】典型排序算法 Java实现
  • 基于opencv的全景图像拼接
  • CSS传统布局与定位详解与TDK三大标签SEO优化
  • Java 8 Stream操作示例
  • Redis 3.0~8.0特性与数据结构全面解析
  • Android-flutter学习总结
  • 云迹机器人底盘调用
  • 高可用 Redis 服务架构分析与搭建
  • 03. C#入门系列【变量和常量】编程世界里的“百变魔盒”与“永恒石碑”
  • .net网站制作/南宁seo手段
  • 58同城广告推广电话/长春网站seo哪家好
  • 网站链接太多怎么做网站地图/bt种子万能搜索神器
  • 破解asp网站后台密码/免费技能培训网
  • 江宁网站建设要多少钱/东莞做网站推广
  • 网站设计策划书/购买链接怎么买