深入理解 Java 多线程与线程池 —— 从原理到实战

一、为什么要使用多线程?

在现代系统中(如电商秒杀、日志分析、物联网设备数据处理等),单线程执行任务 往往无法满足性能需求。

多线程的核心目标是:提升资源利用率与系统吞吐量

🚫 单线程的不足

  • 任务串行执行,CPU 大部分时间处于空闲;

  • 无法并行计算;

  • 遇到 I/O 阻塞性能急剧下降;

  • 无法支撑高并发访问。

⚡ 多线程的优势

  • 可同时处理多个任务;

  • 充分利用多核 CPU;

  • 提升系统响应速度;

  • 提高 I/O 吞吐能力。

二、Java 实现多线程的四种方式

1️⃣ 继承 Thread

class MyThread extends Thread {

@Override

public void run() {

System.out.println("线程执行:" + Thread.currentThread().getName());

}

}

public class ThreadDemo {

public static void main(String\[\] args) {

new MyThread().start();

}

}


2️⃣ 实现 Runnable 接口

class MyRunnable implements Runnable {

@Override

public void run() {

System.out.println("Runnable线程执行:" + Thread.currentThread().getName());

}

}

public class RunnableDemo {

public static void main(String\[\] args) {

new Thread(new MyRunnable()).start();

}

}


3️⃣ 实现 Callable 接口(可返回结果)

复制代码
import java.util.concurrent.*;

public class CallableDemo {

public static void main(String\[\] args) throws Exception {

ExecutorService executor = Executors.newSingleThreadExecutor();

Future<Integer> future = executor.submit(() -> {

System.out.println("计算中...");

return 42;

});

System.out.println("计算结果:" + future.get());

executor.shutdown();

}

}

4.使用线程池创建

三、多线程

进程和线程的区别?

进程是正在运行程序的实例,一个进程有许多线程,每个线程执行一个任务

进程独占一个内存空间,进程中的线程共享该内存空间

Runnable和Callable的区别?

1.Runnable重写的是run方法,Callable重写的是call()方法

2.Runnable不能抛出异常,只能在内部消化,Callable可以向上抛出异常

3.Runnable的run方法不能返回结果,Callable的call()方法可以返回结果,配合future使用获取结果

线程包括哪些状态,状态之间是如何变化的?

在JDK中的Thread类中的枚举State里面定义了6中线程的状态分别是:新建、可运行、终结、阻塞、等待和有时限等待六种。

关于线程的状态切换情况比较多。我分别介绍一下

当一个线程对象被创建,但还未调用 start 方法时处于新建 状态,调用了 start 方法,就会由新建 进入可运行 状态。如果线程内代码已经执行完毕,由可运行 进入终结状态。当然这些是一个线程正常执行情况。

如果线程获取锁失败后,由可运行 进入 Monitor 的阻塞队列阻塞 ,只有当持锁线程释放锁时,会按照一定规则唤醒阻塞队列中的阻塞 线程,唤醒后的线程进入可运行状态

如果线程获取锁成功后,但由于条件不满足,调用了 wait() 方法,此时从可运行 状态释放锁等待 状态,当其它持锁线程调用 notify() 或 notifyAll() 方法,会恢复为可运行状态

还有一种情况是调用 sleep(long) 方法也会从可运行 状态进入有时限等待 状态,不需要主动唤醒,超时时间到自然恢复为可运行状态

在 java 中 wait 和 sleep 方法的不同?

1.wait可以被notify唤醒,sleep需要等待到时才会被唤醒

2.wait是用于线程间通信,sleep用于线程短暂休眠

3.wait是Object类的方法 只能在synchronized中使用,sleep是Thread的静态方法 可以在任何地方使用

四、线程池

线程池的核心参数?

corePoolSize 核心线程数:线程池中常驻的核心线程数

maximumPoolSize 最大线程数目:核心线程数+救急线程数

keepAliveTime 空闲存活时间:非核心线程到达空闲时间会被销毁

unit 时间单位:分钟,秒等

workQueue 任务队列:当没有空闲核心线程时,新创建的任务假如队列中排队,队列满了会创建救急线程执行任务

threadFactory 线程工厂:用于创建线程,可以自定义线程名,是否为守护线程等

handler 拒绝策略:当队列满了且线程数达到maximumPoolSize,执行拒绝策略。执行策略有四种,第一种是抛出异常(默认),第二种是由调用者执行任务,第三种是丢弃当前任务,第四种是丢弃最早排队的任务

五,java各种锁

synchronized底层原理

利用jvm中的montor判断是否获得了锁,montor位于java对象的对象头中,这也是为什么java对象可以作为锁的原因,montor内部维护了两个变量分别是EntryList等待锁的队列和Owner持有锁的线程,只有一个montor可以设置成功owner,因为一个montor只能有一个owner,在上锁的过程中,会有其他线程来抢锁,则会进入EntryList阻塞,当线程执行完会唤醒EntryList中线程竞争,竞争的时候是非公平的

synchronized锁升级

有偏向锁,轻量级锁,重量级锁

1.当只有一个线程锁一个对象时,属于偏向锁

2.此时又来一个线程与上一次线程交替锁同一对象,没有竞争,属轻量级锁

3.当多个线程锁同一对象时,属于重量级锁

ReentrantLock底层

是可重入的锁,调用lock方法获取锁,再次调用lock时不会阻塞,内部直接增加可重入次数,表示已经重复获取一把锁了,调用unlock释放锁,底层通过CAS和AQS实现,支持公平锁和非公平锁,内部通过构造方法传一个可选公平参数,设置true时为公平锁,默认是非公平锁

volatile

1.保证线程的可见性

一个线程对变量进行了修改,则该变量对其他线程是立即可见的

2.禁止指令重排序

通过插入一个内存屏障在内存屏障前后的指令禁止重排序优化

CAS

修改前,先判断数据是否与期望的一致,一致则修改,

不一致则放弃修改

AQS

1.是阻塞锁和相关工具的框架

1.内部维护了一个state表示持有锁的状态,默认为0表示没有获取锁,1表示获取到锁,通过cas机制设置state的状态

2.通过FIFO阻塞队列管理线程

synchronized和Lock有什么区别 ?

1.synchronized是由C++语言实现,代码执行完锁释放;Lock是java语言实现,调用unlock释放锁

2.两者都是悲观锁,具有互斥,同步,锁重入等功能,但是Lock具备synchronized没有的功能,比如公平锁,可超时,可等待等等

3.synchronized有锁升级,在没有竞争,使用偏向锁,轻量级锁,性能不错;有竞争时,Lock性能比较好

六,并发安全

ConcurrentHashMap底层

jdk1.7的ConcurrentHashMap

默认长度是16,一旦初始化中间不能扩容

底层是分段的数组+链表,内部维护了一个Segment数组,Segment与HashMap结构类似也是数组+链表每个Segment对应一个HashEntry数组,每个HashEntry是链表结构上的元素,segment是可重入的锁ReentrantLock 每个Segment守护一个HashEntry数组中的元素,当HashEntry数组中的元素被修改时,需要先获取对应segment锁

jdk1.8

数据结构与1.8的HashMap结构一致,通过CAS+synchronized实现并发安全,用cas控制数组节点的添加,synchronized只锁定链表或者链表的头结点,只要Hash不冲突,就不会产生并发安全

ThreadLocal

一是实现了资源隔离, 二是实现了线程内资源共享

底层是每个ThreadLocal内部维护了一个ThreadLocalMap,

.set时是把ThreadLocal作为key,要隔离的资源作为value存入map中

.get时是把ThreadLocal作为key,从map中取出value

.remove时是把ThreadLocal作为key,从map中移除value

为什么会发生内存泄漏?

因为ThreadLocalMap中key是弱引用,value是强引用,

当ThreadLocal被GC时,key会被GC,但是value是强引用在ThreadLocalMap上,所以无法被GC,只有当线程执行完才会被GC,因此要通过remove移除value,避免内存泄漏

相关推荐
爱敲键盘的猴子3 分钟前
Spring MVC 详解(一):全注解开发与请求响应处理
java·spring·mvc
云水初7 分钟前
【agent篇】RAG 知识库构建避坑指南
开发语言·python·学习·agent·rag
2019一路前行9 分钟前
Python 函数、循环语句
开发语言·python
counting money11 分钟前
SpringBoot 项目创建(IDEA不全,还需要修改)
java·spring boot·spring·maven
Dr.kangder12 分钟前
嵌入式面试总结(二十二)——指针
java·面试·职场和发展·架构·嵌入式
MacroZheng17 分钟前
几行代码给项目集成AI功能,Spring AI 2.0太香了!
java·人工智能·spring boot
Devin~Y1 小时前
从内容社区到AI智能客服:Spring Boot + Spring Cloud + Spring AI 全栈实战面试拆解
java·spring boot·redis·elasticsearch·spring cloud·kafka·mybatis
未秃头的程序猿1 小时前
从写CRUD到做AI Agent:我花了6个月转型,这是我的完整路线图
java·后端·ai编程
Java内核笔记1 小时前
万字长文剖析 Spring Boot 4.1.0 启动流程源码:从 main 到就绪
java·后端
2602_959960921 小时前
电商大厂Java面试:从Spring Boot、JPA、微服务到Redis、Kafka、Spring Security与监控,谢飞机的爆笑三轮问答
java·jvm·spring boot·redis·面试题