【狂神说笔记】Java 多线程知识点详解
视频原地址:【狂神说Java】多线程详解哔哩哔哩bilibili
笔记优化说明:补充完整可运行代码、核心原理解释、使用场景和避坑点,适配新手理解和实际开发场景。
一.创建多线程3 种核心方式(附完整代码 + 使用场景)
1.继承Thread类(不推荐,单继承限制)
-
步骤:继承
Thread类 → 重写run()方法(线程执行体) → 创建实例调用start()启动(不能直接调用run(),否则只是普通方法) -
缺点:Java 单继承特性,继承 Thread 后无法继承其他类,灵活性差。
-
案例是主方法线程逻辑与线程竞争
// 1. 继承Thread类
public class TestThread01 extends Thread {
// 2. 重写run方法(线程要执行的逻辑)
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("子线程执行:" + i);
}
}
// 主方法(程序入口)
public static void main(String[] args) {
// 3. 创建线程实例
TestThread01 thread = new TestThread01();
// 4. 调用start()启动线程(JVM会自动调用run方法)
thread.start();
// 5.主线程逻辑
for (int i = 0; i < 5; i++) {
System.out.println("主线程执行:" + i);
}
}
}
2.实现MyRunnable接口(推荐,无单继承限制)
-
步骤:实现
Runnable接口 → 重写run()方法 → 将实现类传入Thread构造器 → 调用start()启动;
-
优势:可继承其他类,支持多线程共享同一个实现类实例(适合资源共享场景)。
-
案例主方法线程逻辑与线程竞争
// 1. 实现Runnable接口
public class TestThread02 implements Runnable {
// 2. 重写run方法
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("子线程执行:" + i);
}
}
public static void main(String[] args) {
// 3. 创建Runnable实现类实例
TestThread02 runnable = new TestThread02();
// 4. 包装成Thread对象(Thread才是线程核心类)
Thread thread = new Thread(runnable);
// 5. 启动线程
thread.start();
// 4+5的简化写法:一步创建+启动
// new Thread(runnable).start();
// 主线程逻辑
for (int i = 0; i < 5; i++) {
System.out.println("主线程执行:" + i);
}
}
}
3.实现Callable接口(有返回值 + 可抛异常)
-
步骤:实现
Callable<T>接口 → 重写call()方法(有返回值、可抛异常) → 通过线程池ExecutorService提交执行 → 获取返回值 → 关闭线程池;
-
优势:可获取线程执行结果、可处理异常,适合需要 “线程执行后返回数据” 的场景(如多线程下载文件后校验结果)。
-
案例:多线程执行返回值任务
package src.com.xing;
import java.util.concurrent.*;
//call()无需手动调用,由线程池(ExecutorService)的工作线程自动调度执行触发开关是submit(Callable)方法,提交任务即触发后续的自动调用流程;手动调用 call () 会在当前线程同步执行,丧失多线程异步执行的特性,违背设计初衷;
// 1. 实现Callable接口,指定返回值类型(示例为Boolean)
public class TestThread03 implements Callable<Boolean> {
private String taskName;
// 构造器传参,标识任务
public TestThread03(String taskName) {
this.taskName = taskName;
}
// 2. 重写call方法(有返回值、可抛异常)
@Override
public Boolean call() throws Exception {
System.out.println(taskName + "执行中...");
Thread.sleep(1000); // 模拟任务耗时
return true; // 任务执行成功返回true
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 3. 创建Callable实例
TestThread03 task1 = new TestThread03("线程任务1");
TestThread03 task2 = new TestThread03("线程任务2");
// 4. 创建线程池(固定3个线程)
ExecutorService service = Executors.newFixedThreadPool(3);
// 5. 提交任务,获取Future(用于接收返回值)
Future<Boolean> result1 = service.submit(task1);
Future<Boolean> result2 = service.submit(task2);
// 6. 获取任务返回值(get()会阻塞,直到任务完成)
boolean rs1 = result1.get();
boolean rs2 = result2.get();
System.out.println("任务1执行结果:" + rs1);
System.out.println("任务2执行结果:" + rs2);
// 7. 关闭线程池(必须关闭,否则程序不会结束)
service.shutdown();
}
}
4.实际案例——多线程下载图片
需导入commons-io包(IDEA 可直接下载),用于简化 URL 文件下载:
-
步骤 1:下载 commons-io jar 包
-
打开 Apache 官网下载地址:https://commons.apache.org/proper/commons-io/download_io.cgi
-
下滑找到「Binaries」区域,下载
commons-io-版本-bin.zip(版本在更新,Windows 注意后缀选bin. zip 包即可);

-
解压 zip 包,找到里面的
commons-io-2.15.1.jar文件(仅仅需要第二个文件,核心文件,其余是开源文件测试文件...),复制到你项目的lib目录(若没有lib目录,手动在目根目录创建src\com\lib)。
-
右键点击「lib」文件夹!不是jar包,在弹出来的条目选择最下面的「添加为库...」完成导入

-
-
主代码:
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.net.URL;
// 1. 图片下载器工具类
class WebDownloader {
// 下载方法:参数1=图片URL,参数2=保存路径+文件名(必须带后缀,如.jpg/.png)
public void download(String url, String savePath) {
try {
// 核心API:将URL内容下载为文件
FileUtils.copyURLToFile(new URL(url), new File(savePath));
System.out.println("图片下载完成:" + savePath);
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 2. 多线程下载实现类(实现Runnable)
public class ImageDownloadThread implements Runnable {
private String imgUrl; // 图片URL
private String saveName; // 保存文件名(如:picture1.jpg)
// 构造器传参
public ImageDownloadThread(String imgUrl, String saveName) {
this.imgUrl = imgUrl;
this.saveName = saveName;
}
// 3. 重写run方法,执行下载
@Override
public void run() {
WebDownloader downloader = new WebDownloader();
// 注意:文件名必须带后缀(如.jpg),否则文件无法正常打开
downloader.download(imgUrl, saveName);
}
// 主方法测试
public static void main(String[] args) {
// 示例图片URL(可替换为自己的图片地址)
String url1 = "https://img-blog.csdnimg.cn/20240101000001.jpg";
String url2 = "https://img-blog.csdnimg.cn/20240101000002.jpg";
// 创建多线程任务
Thread t1 = new Thread(new ImageDownloadThread(url1, "pic1.jpg"));
Thread t2 = new Thread(new ImageDownloadThread(url2, "pic2.jpg"));
// 启动线程(异步下载),可以看到图片1图片2下载顺序是随机的
t1.start();
t2.start();
}
}
5.实际案例——多人抢票
-
主代码:
package src.com.xing;
//三个线程同时抢10张票
public class TT0202 implements Runnable{
private int ticketNums=10;//票数为10
//1.重run方法
@Override
public void run() {
while (true){
if(ticketNums<=0){
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNums--+"票");
}
}
2.主方法
public static void main(String[] args) {
TT0202 tt0202=new TT0202();
new Thread(tt0202,"大学生").start();
new Thread(tt0202,"黄牛").start();
new Thread(tt0202,"研究生").start();
}
}
二.静态代理模式(多线程底层核心原理)
-
本质:真实对象和代理对象实现同一接口,代理对象封装真实对象,增强其功能;
-
多线程底层:
Thread就是代理类,Runnable实现类是真实对象,start()方法就是代理增强(启动线程 + 执行run())。
//真实对象和代理对象都要实现同一个接口,代理对象要代理真实对象
//真实角色
// 1. 定义共同接口(婚姻接口)
interface Marry {
void happyMarry(); // 核心方法:结婚
}
// 2. 真实角色(你,要结婚的人)
class You implements Marry {
@Override
public void happyMarry() {
System.out.println("秦老师要结婚了,超开心!");
}
}
// 3. 代理角色(婚庆公司,增强真实角色功能)
class WeddingCompany implements Marry {
private Marry target; // 代理的真实对象
// 构造器传入真实对象
public WeddingCompany(Marry target) {
this.target = target;
}
// 核心方法:添加婚前、婚后服务
@Override
public void happyMarry() {
beforeMarry(); // 婚前准备
target.happyMarry(); // 执行真实对象的核心方法
afterMarry(); // 婚后收尾
}
// 代理的方法
private void beforeMarry() {
System.out.println("婚庆公司布置现场,准备彩礼~");
}
private void afterMarry() {
System.out.println("婚庆公司收尾款,打扫现场~");
}
}
// 测试类
public class StaticProxyDemo {
public static void main(String[] args) {
// 真实对象:你
Marry you = new You();
// 代理对象:婚庆公司,代理你结婚
Marry proxy = new WeddingCompany(you);
// 执行代理方法(增强后的结婚流程)
proxy.happyMarry();
}
}
三.推导Lamda表达式
-
1.前置概念:
在开始推导前,必须先明确一个核心前提:Lambda 表达式只能简化「函数式接口」的实现。
所谓函数式接口,就是:
-
接口中只有一个抽象方法(可以有默认方法、静态方法);
-
可以用
@FunctionalInterface注解标记(可选,但推荐,编译器会校验是否符合函数式接口规范)。
比如本文示例二中的Marry接口,就是一个典型的函数式接口:
// 1. 定义共同接口(婚姻接口)
interface Marry {
void happyMarry(); // 核心方法:结婚
}
-
2.一步步推导 Lambda 表达式(从繁到简,按序号看)
package src.com.xing;
//推导lamda表达式
():对应接口抽象方法的参数列表(本例中 lamda () 无参数,所以是空括号);
->:Lambda 操作符,意为 “将参数传递给方法体”;
{}:对应抽象方法的方法体
/**总结1.只有函数式接口才能用lamda简化
* 2.如果函数内代码多行,用{}代码块包裹
* 3.多个参数可以去掉参数类型,但要在()内
*/
public class TestLamda {
public static void main(String[] args) {
ILike like=new Like();
like.lamda();
like = new Like2();
like.lamda();
// 3..局部内部类(将实现类定义在方法(main 方法)内部,作用域仅限当前方法,临时使用更灵活,但依然需要写完整的类和重写方法。)
class Like3 implements ILike {
@Override
public void lamda() {
System.out.println("i like lamda4");
}
}
like=new Like3();
like.lamda();
//4.匿名内部类(去掉类的名称,直接通过 “接口 + 方法重写” 的方式创建对象,是简化的关键一步(Lambda 的前身))
like =new ILike() {
@Override
public void lamda() {
System.out.println("i like lamda5");
}
};
like.lamda();
//5.用lamda简化(基于匿名内部类,去掉所有冗余结构,只保留 “方法参数” 和 “方法体”,这就是 Lambda 的核心!)
like = () ->{
System.out.println("i like lamda6");
};
like.lamda();
}
//2.静态内部类(将实现类定义为外部类的静态内部类,无需创建外部类对象即可使用,比独立实现类更紧凑,但仍需定义完整的类结构。)
static class Like2 implements ILike{
@Override
public void lamda() {
System.out.println("i like lamda3");
}
}
}
//定义一个函数接口
interface ILike{
void lamda();
}
//1.实现类(这是最基础的写法:定义一个独立的类实现接口,然后创建对象调用方法。)
class Like implements ILike{
@Override
public void lamda() {
System.out.println("i like lamda2");
}
}
四.线程状态修改
线程有 6 种状态(NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED),核心操作需掌握以下 7 种:
1.线程停止(优雅停止,禁止用 stop/destroy)
禁止使用 JDK 废弃方法(stop()/destroy()),会导致线程资源未释放;
推荐方式: 次数控制自动停止; 标识位控制停止。
package src.com.xing;
public class TestStop implements Runnable{
//1.设置一个标志位
private boolean flag=true;
public void run(){
int i =0;
while (flag){
System.out.println("run Thread"+i++);
}
}
//重写stop方法,不是直接调用stop
//设置一个公开方法停止线程,转换标志位
public void stop(){
this.flag=false;
}
public static void main(String[] args) {
TestStop testStop=new TestStop();
new Thread(testStop).start();
for (int i = 0; i <=1000; i++) {
System.out.println("main"+i);
if(i==900){
testStop.stop();
System.out.println("线程停止了");
}
}
}
}
2.线程休眠(Thread.sleep ())
作用:让线程暂停指定时间,释放 CPU 资源,但不释放锁;
`Thread.sleep(200);`
3.线程礼让(Thread.yield ())
作用:让当前线程放弃 CPU 执行权,回到就绪状态,与其他线程重新竞争 CPU;
注意:礼让不一定成功,取决于 CPU 调度(可能刚礼让又抢到执行权)。
Thread.yield();
4.线程强制执行
作用:让调用join()的线程优先执行完毕,当前线程阻塞等待;
thread.join();
5.获取线程状态(Thread.getState ())
Thread.State state = thread.getState();
System.out.println("线程状态:" + state);
6.线程优先级(1~10,默认 5)
作用:优先级越高,CPU 调度的概率越大(不是绝对优先);
主线程默认5,其他范围1~10 thread.setPriority(1);
7.守护线程(setDaemon (true))
作用:守护线程依赖用户线程一直执行,当所有用户线程结束,守护线程自动结束;
场景:垃圾回收(GC)、后台日志输出、监控线程等。
package src.com.xing;
public class TestDaemon {
public static void main(String[] args) {
Young young = new Young();
God god = new God();
Thread thread01 = new Thread(god);
thread01.setDaemon(true);//守护线程需在start()前设置,否则抛出异常。
thread01.start();
new Thread(young).start();
}
}
class God implements Runnable{
@Override
public void run() {
while (true){
System.out.println("GOD一直都在");
}
}
}
class Young implements Runnable{
@Override
public void run() {
for (int i = 0; i < 365; i++) {
System.out.println("每一天开心活着");
}
System.out.println("你死了");
}
}
五.线程同步机制(解决多线程资源竞争问题)
多线程共享资源时会出现 “线程不安全”(如卖票超卖、数据错乱),需通过同步机制保证原子性。
1.synchronized同步方法(隐式锁)
法一、配合 `private` 封装数据,本质是为当前对象(this)加锁,保证同一时间只有一个线程能执行同步代码
public synchronized void method(int args) {}
法二、Obj 称为同步代码块,可以是任何对象,更灵活,推荐用共享资源作为监视器
synchronized (Obj) {
// 需要同步的代码
}
package src.com.synchronization;
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTickkkket buyTickkkket=new BuyTickkkket();
new Thread(buyTickkkket,"大学生").start();
new Thread(buyTickkkket,"黄牛").start();
new Thread(buyTickkkket,"黄牛3").start();
}
}
class BuyTickkkket implements Runnable{
private int nums=100;
boolean flag =true;
@Override
public void run() {
//买票
while (flag){
try {
buy();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
//添加同步后,不会出现卖到了第-1票的情况
private synchronized void buy() throws InterruptedException {
if(nums<=0){
return;
}
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+"买了"+nums--);
}
}
2.JUC安全集合
该集合自动实现了同步,保证共享资源的安全
package src.com.synchronization;
import java.util.concurrent.CopyOnWriteArrayList;
//测试JUC自带安全类型的集合CopyOnWriteArrayList
public class TestJUC {
public static void main(String[] args) throws InterruptedException {
CopyOnWriteArrayList<String> list=new CopyOnWriteArrayList<String>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
Thread.sleep(1000);
System.out.println(list.size());
}
}
3.死锁
-
核心原因
-
线程 1 持有锁 A,等待锁 B;
-
线程 2 持有锁 B,等待锁 A;
-
双方互相等待,无法释放锁,程序卡死。
-
-
代码示例(死锁场景)
package src.com.synchronization;
//死锁
public class DeadLock {
public static void main(String[] args) {
Makeup makeup01=new Makeup(0,"girl01");
Makeup makeup02=new Makeup(1,"girl02");
makeup02.start();
makeup01.start();
}
}
class Lipstick{ }
class Mirror{}
class Makeup extends Thread{
//用static保证资源只有一份
static Mirror mirror=new Mirror();
static Lipstick lipstick=new Lipstick();
int choice;
String name;//人
public Makeup(int choice, String name) {
this.choice = choice;
this.name = name;
}
@Override
public void run() {
try {
makeup();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
//化妆,互相持有对方的锁
private void makeup() throws InterruptedException {
if(choice==0){
synchronized (mirror){//获得镜子的锁
System.out.println(this.name+"镜子");
Thread.sleep(1000);
synchronized (lipstick){
System.out.println(this.name+"口红");
}
}
}else {
synchronized (lipstick){//获得镜子的锁
System.out.println(this.name+"口红");
Thread.sleep(1000);
synchronized (mirror){
System.out.println("镜子");
}
}
}
}
}
4.Lock锁(显式锁,比 synchronized 更灵活)
-
java.util.concurrent.locks.Lock是接口,常用实现类ReentrantLock;
-
优势:可手动加锁 / 解锁、支持超时锁、可中断锁、公平锁(按线程等待顺序获取锁);
-
规范:必须在
try块中加锁,finally块中解锁(确保锁一定释放)。
try {
lock.lock();//加锁
{代码块}
}finally {
lock.unlock();//解锁
5.管程法
-
核心 API
-
wait():线程等待,释放锁,进入等待队列; -
wait(long timeout):超时等待,超时后自动唤醒; -
notify():随机唤醒一个等待的线程; -
notifyAll():唤醒所有等待的线程
package src.com.synchronization;
//测试生产者消费者模型--->利用缓冲区管程法
//生产者,消费者,产品,核心思路如下
//生产者:生产产品→通知消费者→若仓库满则等待;
//消费者:消费产品→通知生产者→若仓库空则等待。
public class TestPC {
public static void main(String[] args) {
SynContainer container=new SynContainer();
new Consumer(container).start();
new Productor(container).start();
}
}
class Productor extends Thread{
SynContainer container;
public Productor(SynContainer container) {
this.container = container;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
container.push(new Chicken(i));
System.out.println("生产了第"+i+"只鸡");
}
}
}
class Consumer extends Thread{
SynContainer container;
public Consumer(SynContainer container) {
this.container = container;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消费了第"+container.pop().id+"只鸡");
}
}
}
class Chicken{
int id;
public Chicken(int id) {
this.id = id;
}
}
//缓冲区
class SynContainer{
//需要一个容器大小
Chicken[] chickens=new Chicken[10];
int count=0;
//生产者放入产品
public synchronized void push(Chicken chicken){
//
if(count==chickens.length){
try {
this.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
chickens[count++]=chicken;
this.notify();
}
//消费者消费产品
public synchronized Chicken pop(){
if(count==0){
try {
this.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
Chicken chicken=chickens[--count];
this.notify();
return chicken;
}
}
6.信号灯法(标志位法,简化版生产者消费者)
//生产和消费必须严格交替执行的场景(如流水线作业、消息队列的顺序消费)
package src.com.synchronization;
//测试生产者消费者模型--->利用信号灯法
//演员,观众,节目
public class TestPc2 {
public static void main(String[] args) {
TV tv=new TV();
new Player(tv).start();
new Watcher(tv).start();
}
}
class Player extends Thread{
TV tv;
public Player(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if(i%2==0){
this.tv.play("节目1");
}else {
this.tv.play("节目2");
}
}
}
}
class Watcher extends Thread{
TV tv;
public Watcher(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
this.tv.watch();
}
}
}
class TV{
//演员表演,观众等待 T
String voive;
boolean flag=true;
//观众观看,演员等待 F
public synchronized void play(String voive){
if(!flag){
try {
this.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("演员表演了"+voive);
this.notifyAll();
this.voive=voive;
this.flag=!this.flag;
}
public synchronized void watch(){
if(flag){
try {
this.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("观众观看了"+voive);
//通知演员表演
this.notify();
this.flag=!this.flag;
}
}
7.线程池(复用线程,减少创建销毁开销)
频繁创建 / 销毁线程会消耗大量资源,线程池可复用线程,降低资源消耗、提高响应速度、便于管理线程
package src.com.synchronization;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestPool {
public static void main(String[] args) {
//1.创建线程池,参数为线程池大小
ExecutorService service= Executors.newFixedThreadPool(10);
service.execute(new MyTheard());
service.execute(new MyTheard());
service.execute(new MyTheard());
service.execute(new MyTheard());
//关闭连接
service.shutdown();
}
}
class MyTheard implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
更多推荐




所有评论(0)