Java--多线程
什么是线程
程序执行的任务
进程就是正在运行的程序,它是系统进行资源分配和调度的基本单位,各个进程之间相互独立,系统给每个进程分配不同的地址空间和资源
Win 操作系统任务管理器查看应用程序运行的进程
进程和线程的区别
比较点 | 进程 | 线程 |
---|---|---|
地址空间 | 独立的地址空间 | 同一进程的线程共享本进程的地址空间 |
资源占用 | 进程之间的资源相互独立 | 同一进程的线程共享本进程的资源 |
健壮性 | 一个进程崩溃后不会对其他进程产生影响(多进程比多线程更加健壮) | 一个线程崩溃后则整个进程都死掉 |
执行过程 | 独立执行 | 不能独立进行,必须依赖于进程 |
并发与资源消耗(都可以同时进行) | 创建和切换消耗资源大 | 创建和切换资源小 |
线程状态图

创建线程(四种方式)
继承Thread类,重写run()方法
public class MyThread extends Thread {
public void run() {
for ( int i = 0; i < 10; i++ ) {
System.out.println(“子线程");
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
}
实现Runnable接口,并实现run()方法
public class MyThread implements Runnable {
public void run() {
for ( int i = 0; i < 10; i++ ) {
System.out.println(“子线程");
}
}
public static void main(String[] args) {
Thread myThread = new Thread(new MyThread);
myThread.start();
}
}
实现Callable接口
有返回值
使用Excutor线程池
线程优先级
设置优先级不一定优先执行
最大优先级
yieldThread.setPriority(Thread.MAX_PRIORITY);
最小优先级
noYieldThread.setPriority(Thread.MIN_PRIORITY);
线程分类
工作线程
守护线程
多线程
多线程并发问题
线程暂停执行条件
线程优先级比较低,不能获得 CPU 时间片
使用 sleep()方法使线程睡眠
通过调用 wait()方法,使线程处于等待状态
通过调用 yield()方法,线程主动出让 CPU 控制权
线程由于等待一个I/0事件处于阻塞状态
线程同步+ThreadLocal
线程同步三种方式 加锁 使用同步代码块 使用同步方法
ThreadLocal(扩展)
本地化线程概念
当工作于线程中的对象使用 ThreadLocal 维护变量时,ThreadLocal 为每个使用该变量的线程分配一个独立的变量副本。所以每一个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。从线程的角度看,这个变量就像是线程的本地变量,这也是类名中"Local“所要表达的意思。
线程局部变量并不是 Java 的新发明,很多语言在语法层面就提供线程局部变量。在 Java 中没有提供语言级支持,而以一种变通的方法,通过 ThreadLocal 的类提供支持。
所以,在Java中编写线程局部变量的代码相对来说要笨拙一些,这也是为什么线程局部变量没有在 Java 开发者中得到很好的普及的原因。
工作原理
ThreadLocal 是如何做到为每一个线程维护一份独立的变量副本,实现的思路很简单:在 ThreadLocal 类中维护一个 Map 结构,用于存储每一个线程的变量副本,Map 中元素的 key 为线程对象,而 value 对应线程的变量副本
CAS(扩展)
什么是CAS
CAS,compare and swap 的缩写,中文翻译成比较并交换。
我们都知道,在 Java 语言之前,并发就已经广泛存在并在服务器领域得到了大量的应用。所以硬件厂商老早就在芯片中加入了大量直至并发操作的原语,从而在硬件层面提升效率。在 intel 的 CPU 中,使用 cmpxchg 指令。
在 Java 发展初期,Java 语言是不能够利用硬件提供的这些便利来提升系统的性能的。而随着 Java 不断的发展,Java 本地方法 (JNI) 的出现,使得 Java 程序越过 JVM 直接调用本地方法提供了一种便捷的方式,因而 Java 在并发的手段上也多了起来。而在 Doug Lea 提供的 cucurenct 包中,CAS 理论是它实现整个 Java 包的基石。
悲观锁和乐观锁
synchronized 是悲观锁,这种线程一旦得到锁,其他需要锁的线程就挂起的情况就是悲观锁。
CAS 操作的就是乐观锁,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。
CAS基本原理
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前值。)CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。”
通常将 CAS 用于同步的方式是从地址 V 读取值 A,执行多步计算来获得新值 B,然后使用 CAS 将 V 的值从 A 改为 B。如果 V 处的值尚未同时更改,则 CAS 操作成功。
类似于 CAS 的指令允许算法执行读-修改-写操作,而无需害怕其他线程同时修改变量,因为如果其他线程修改变量,那么 CAS 会检测它(并失败),算法 可以对该操作重新计算。
CAS优势
利用 CPU 的 CAS 指令,同时借助 JNI 来完成 Java 的非阻塞算法。其它原子操作都是利用类似的特性完成的。而整个J.U.C 都是建立在 CAS 之上的,因此对于 synchronized 阻塞算法,J.U.C 在性能上有了很大的提升。
存在的问题
-
ABA问题。因为 CAS 需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用 CAS 进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。
-
从 Java1.5 开始 JDK 的 atomic 包里提供了一个类 AtomicStampedReference 来解决ABA问题。这个类的compareAndSet 方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
关于ABA问题参考文档: http://blog.hesey.net/2011/09/resolve-aba-by-atomicstampedreference.html
-
循环时间长开销大。自旋 CAS 如果长时间不成功,会给 CPU 带来非常大的执行开销。如果 JVM 能支持处理器提供的 pause 指令那么效率会有一定的提升,pause 指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU 不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起 CPU 流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。
-
只能保证一个共享变量的原子操作。当对一个共享变量执行操作时,我们可以使用循环 CAS 的方式来保证原子操作,但是对多个共享变量操作时,循环 CAS 就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作 ij。从 Java1.5 开始 JDK 提供了AtomicReference 类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。
线程死锁
线程池
工作原理
- 程序启动时向线程池中提前创建一批线程对象
- 当需要执行任务时,从线程池中获取一个空闲的线程对象
- 任务执行完毕后,不销毁线程对象,而是将其返还给线程池,并再次将状态设置为空闲
优缺点
- 减少频繁创建和销毁线程对象的时间消耗,提高了程序运行的性能(优点)
- 线程池中空闲的线程对象,会占用系统更多的内存存储空间(缺点)
线程池是一种以时间换空间的性能优化策略
Java四种内置线程池
Java 语言提供了一系列线程池的实现,以解决实际开发中各种对线程池的需求
- newCachedThreadPool
- newFixedThreadPool
- newScheduledThreadPool
- newSingleThreadExecutor
线程间通信(了解) wait-noify机制
Object类 toString() clone() equals()
Java 提供了一个精心设计的线程间通信机制,使用wait()、notify() 和 notifyAll() 方法,这些方法是作为 Object 类中的 final 方法实现的。这三个方法仅在 synchronized 方法中才能被调用
-
volatile 实现线程间相互通信
-
wait() 方法:方法告知被调用的线程退出监视器并进入等待状态,直到其他线程进入相同的监视器并调用 notify( ) 方法
-
notify( ) 方法:通知同一对象上第一个调用 wait( )线程
-
notifyAll() 方法:通知调用 wait() 的所有线程,具有最高优先级的线程将先运行
线程定时器
Timer类
Timer是一个普通的类,其中有几个重要的方法
- schedule() 方法:启动定时器一个定时任务
- cancel()方法:终止定时器所有定时任务
启动一个定时任务就创建一个线程,线程会一直执行下去,直到调用终止定时任务
TimerTask类
TimerTask 是一个抽象类,需要实现该类
- run()方法:定时任务逻辑
//创建一个定时任务
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
// 任务执行代码
}
}, 5000,1000); //延时 5s 每间隔1s 执行一次
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构