JAVA:多线程和锁+锁升级机制
多线程能做什么
当一个任务内容比较多的时候,我们尝试着将任务进行拆解,将整个任务拆分成为多个子任务,交给多个线程各自去完成,这时候就引入了多线程的概念。
既然引入了多线程,那么就会带来相应的线程管理的问题。比如不分事情需要有绝对的先后顺序,而线程执行的时机是不确定的,因此我们必须要想办法控制A在B之前执行。这时候就需要有锁的概念。在次之前,而此处还必须从线程的基本概念开始。
JAVA中怎么创建线程
⭐️ 继承Thread
public class ThreadExtendDemo extends Thread {
@Override
public void run() {
System.out.println("线程执行方法...");
}
}
public class Main {
public static void main(String[] args) {
new ThreadExtendDemo().start();
System.out.println("主线程执行方法....");
}
}
此处需要注意,如果在main方法中直接调用线程的run方法,这便不是启动线程了,而是显式的的方法调用。
⭐️实现Runnable接口
public class ThreadImplementDemo implements Runnable {
@Override
public void run() {
System.out.println("实现Runnable开启线程....");
}
}
public class Main {
public static void main(String[] args) {
// new ThreadExtendDemo().start();
// System.out.println("主线程执行方法....");
new Thread(new ThreadImplementDemo()).start();
System.out.println("主线程执行方法....");
}
}
▲两种方法在写法上有区别,但是原理都是一样的,因为Thread类本身就是实现了Runnable接口的
另外,在某些场合,我们需要线程执行后,将结果返回,方便后续对各个结果进行后置处理。在Java中,Callable和Future可以实现实时获取到线程的执行结果。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class CallableDemo implements Callable<String> { // 建议指定泛型类型,这里是String
@Override
public String call() throws Exception { // 返回值类型与泛型一致
System.out.println("Callable 线程执行...");
// 模拟耗时操作
Thread.sleep(2000);
return "callable finished";
}
public static void main(String[] args) {
// 1. 创建Callable实现类的实例
CallableDemo callable = new CallableDemo();
// 2. 创建线程池(这里使用单线程池,也可以用其他类型)
ExecutorService executor = Executors.newSingleThreadExecutor();
// 3. 提交Callable任务到线程池,返回Future对象(用于获取结果)
Future<String> future = executor.submit(callable);
try {
// 4. 通过Future获取线程执行结果(会阻塞等待结果返回)
String result = future.get();
System.out.println("线程返回结果:" + result);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 5. 关闭线程池
executor.shutdown();
}
}
}
Demo中是使用了线程池来完成的,如果不使用线程池,则可以通过FutureTask来完成:
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class CallableDemo implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("Callable 线程执行...");
Thread.sleep(2000); // 模拟耗时操作
return "callable finished";
}
public static void main(String[] args) {
// 1. 创建Callable实例
CallableDemo callable = new CallableDemo();
// 2. 用FutureTask包装Callable(FutureTask实现了Runnable接口)
FutureTask<String> futureTask = new FutureTask<>(callable);
// 3. 用Thread包装FutureTask并启动
Thread thread = new Thread(futureTask);
thread.start();
try {
// 4. 通过FutureTask获取返回结果
String result = futureTask.get();
System.out.println("线程返回结果:" + result);
} catch (Exception e) {
e.printStackTrace();
}
}
}
题外话:本人也写过golang,这里的Future获取线程执行结果的过程,在golang中也有相似的做法,如channel和groutine
package main
import (
"fmt"
"time"
)
// 定义一个函数,模拟需要返回结果的任务
// 函数参数中包含一个用于传递结果的channel
func callableTask(resultChan chan<- string) {
fmt.Println("goroutine 执行中...")
// 模拟耗时操作
time.Sleep(2 * time.Second)
// 将结果发送到channel中
resultChan <- "callable finished"
}
func main() {
// 1. 创建一个用于接收结果的channel(字符串类型)
resultChan := make(chan string)
// 2. 启动goroutine(相当于Java的线程),并传入结果channel
go callableTask(resultChan)
// 3. 从channel中获取结果(会阻塞等待,直到有数据到来)
result := <-resultChan
fmt.Println("goroutine返回结果:", result)
// 4. 关闭channel(可选,这里简单场景可以省略)
close(resultChan)
}
在 Go 语言中,可以使用通道(channel) 来实现类似 Java 中Callable获取线程(Go 中称为 goroutine)返回结果的功能。通道是 Go 语言中用于 goroutine 之间通信的核心机制,天然来传递数据和同步操作。
线程的生命周期
主线程执行方法....
实现Runnable开启线程....
Process finished with exit code 0
从线程的输出结果来看,此处是开启了两个线程,一个main线程和一个子线程。
如果我们开启多个线程,就会出现处于多种状态下的线程。
线程的状态
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}
从以上源码中可以看到,线程一共有六种状态,分别是NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED,而从注释中可以看到:
线程的状态可以通过以下方法查看:
jpsjstack PID
NEW
线程创建了,但是未执行start方法。
* Thread state for a thread which has not yet started.
*/
public class NewStateDemo {
public static void main(String[] args) throws InterruptedException {
// 创建线程对象,但不调用start()方法
Thread newThread = new Thread(() -> {
System.out.println("线程执行中..."); // 这行代码永远不会执行,因为线程未启动
}, "NEW-Thread");
// 打印线程状态(此时应为NEW)
System.out.println("线程状态: " + newThread.getState());
// 主线程休眠足够长时间,方便用jstack查看
Thread.sleep(1000 * 60 * 5); // 休眠5分钟
}
}
输出
线程状态: NEW
NEW状态比较特殊,处于NEW状态的线程尚未被 JVM 调度初始化,因此不会出现在jstack的线程列表中,因此只能通过代码中获取状态。
RUNNABLE
运行中的状态。
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
public class RunnableStateDemo {
public static void main(String[] args) throws InterruptedException {
// 创建并启动线程(启动后进入Runnable状态)
Thread runnableThread = new Thread(new RunnableTask(), "Runnable-Thread");
runnableThread.start();
// 主线程休眠,保持程序运行
Thread.sleep(1000 * 60 * 5); // 休眠5分钟
}
// 执行持续运行的任务,让线程保持在Runnable状态
static class RunnableTask implements Runnable {
@Override
public void run() {
// 无限循环执行轻量任务(不阻塞,不等待)
long count = 0;
while (true) {
// 简单的计数操作,避免线程进入阻塞状态
count++;
// 每计数1亿次打印一次,证明线程在运行
if (count % 100_000_000 == 0) {
System.out.println("线程运行中,计数: " + count);
}
}
}
}
}
D:\Data\JAVA\code\demo1>jps
22672
7808 Jps
29268 Main
16328 RunnableStateDemo
27868 KotlinCompileDaemon
29068 Launcher
D:\Data\JAVA\code\demo1>jstack 16328
2025-09-27 13:45:37
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.221-b11 mixed mode):
"Runnable-Thread" #20 prio=5 os_prio=0 tid=0x0000000028bb9800 nid=0x5d98 runnable [0x000000002ba1e000]
java.lang.Thread.State: RUNNABLE
at RunnableStateDemo$RunnableTask.run(RunnableStateDemo.java:21)
at java.lang.Thread.run(Thread.java:748)
从jstack的结果中可以看到,线程Runnable-Thread正处于RUNNABLE状态,而从代码中可以看到,线程并没有阻塞,而是在不断地执行计算。
猜测此处命名为RUNNABLE而不是RUNNING,原因在于线程的调度,CPU也不知道线程什么时候能获取到资源,从一个就绪状态转为运行状态。
BLOCKED
线程处于阻塞状态,等待被调用notify|notifyAll方法唤醒。
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
public class BlockStateDemo {
// 定义一个共享锁对象
private static final Object LOCK = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(new LockTask(),"持有锁的线程");
Thread t2 = new Thread(new LockTask(), "持有锁的线程");
t1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
static class LockTask implements Runnable{
@Override
public void run() {
synchronized (LOCK) {
while (true) {
}
}
}
}
}
D:\Data\JAVA面试班\code\demo1>jps
22672
30016 Jps
29268 Main
31220 BlockStateDemo
27868 KotlinCompileDaemon
30796 Launcher
D:\Data\JAVA面试班\code\demo1>jstack 31220
2025-09-27 14:12:51
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.221-b11 mixed mode):
"DestroyJavaVM" #22 prio=5 os_prio=0 tid=0x0000000002d02800 nid=0x5860 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"持有??的线程" #21 prio=5 os_prio=0 tid=0x000000002835d000 nid=0x786c waiting for monitor entry [0x000000002b2cf000]
java.lang.Thread.State: BLOCKED (on object monitor)
at BlockStateDemo$LockTask.run(BlockStateDemo.java:22)
- waiting to lock <0x00000007164c48a8> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
"持有锁的线程" #20 prio=5 os_prio=0 tid=0x00000000282ab800 nid=0x70e0 runnable [0x000000002b1cf000]
java.lang.Thread.State: RUNNABLE
at BlockStateDemo$LockTask.run(BlockStateDemo.java:22)
- locked <0x00000007164c48a8> (a java.lang.Object)
从结果上看,“持有锁的线程”获取到锁之后,处于无限循环(演示用,勿用于生产)中,因此另一个线程则永远无法获取到锁,因此会处于BLOCKED阻塞状态。
此处有设计到JVM的相关优化:
锁的升级,后边会有升级机制的介绍。
WAITING&TIMED_WAITING
从注释中可以看出,通过调用wait(),join(),park()方法,可以让线程进入到WAITING状态,此处需要注意的是,调用join方法指的是不带超时时间的,如果是带了超时时间,则会进入到TIMED_WAITING状态
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
*
*- {@link Object#wait() Object.wait} with no timeout
*- {@link #join() Thread.join} with no timeout
*- {@link LockSupport#park() LockSupport.park}
*
*
*A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called Object.wait()
* on an object is waiting for another thread to call
* Object.notify() or Object.notifyAll() on
* that object. A thread that has called Thread.join()
* is waiting for a specified thread to terminate.
*/
通过wait进入
public class WaitingStateDemo {
// 共享的锁对象
private static final Object LOCK = new Object();
public static void main(String[] args) throws InterruptedException {
// 创建并启动线程
Thread waitingThread = new Thread(new WaitingTask(), "WAITING-Thread");
waitingThread.start();
// 主线程休眠,保持程序运行
Thread.sleep(1000 * 60 * 5); // 休眠5分钟
}
static class WaitingTask implements Runnable {
@Override
public void run() {
synchronized (LOCK) {
try {
System.out.println(Thread.currentThread().getName() + " 获得锁,准备进入WAITING状态");
// 调用wait()进入无限等待(无超时时间)
LOCK.wait(); // 没有其他线程唤醒,会一直处于WAITING状态
// 以下代码永远不会执行(除非被唤醒)
System.out.println(Thread.currentThread().getName() + " 被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
通过park进入
import java.util.concurrent.locks.LockSupport;
public class ParkWaitingDemo {
public static void main(String[] args) throws InterruptedException {
// 创建并启动线程
Thread parkThread = new Thread(new ParkTask(), "Park-Waiting-Thread");
parkThread.start();
// 主线程休眠,保持程序运行
Thread.sleep(1000 * 60 * 5); // 休眠5分钟
}
static class ParkTask implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 准备调用park()进入等待状态");
// 调用park()使线程进入WAITING状态
LockSupport.park(); // 没有其他线程调用unpark(),会一直等待
// 以下代码只有被unpark()唤醒后才会执行
System.out.println(Thread.currentThread().getName() + " 被unpark()唤醒");
}
}
}
通过join进入
public class JoinWaitingDemo {
public static void main(String[] args) throws InterruptedException {
// 创建子线程
Thread childThread = new Thread(new LongRunningTask(), "子线程");
childThread.start();
System.out.println("主线程调用join(),开始等待子线程完成");
// 主线程调用子线程的join(),会进入WAITING状态直到子线程结束
childThread.join(); // 无超时参数,会无限等待
// 以下代码只有子线程执行完毕后才会执行
System.out.println("子线程执行完毕,主线程继续执行");
}
// 长时间运行的任务,让主线程长时间处于等待状态
static class LongRunningTask implements Runnable {
@Override
public void run() {
try {
System.out.println("子线程开始执行,将运行5分钟");
// 子线程休眠5分钟,模拟长时间任务
Thread.sleep(1000 * 60 * 5);
System.out.println("子线程执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
调用join方法后,主线程会进入WAITNG状态,而子线程因为内部有sleep方法,且带了超时时间,会进入TIMED_WAITNG状态。
TERMINATED
线程完整执行完任务会进入改状态,同时,执行interupt方法也可以进入改状态。
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
public class InterruptTerminatedDemo {
public static void main(String[] args) throws InterruptedException {
// 创建线程,执行可中断的任务
Thread taskThread = new Thread(new InterruptibleTask(), "可中断线程");
taskThread.start();
// 主线程休眠1秒,确保子线程进入运行状态
Thread.sleep(1000);
// 调用interrupt()中断子线程
System.out.println("主线程调用interrupt()中断子线程");
taskThread.interrupt();
// 等待子线程终止
Thread.sleep(1000);
// 打印子线程最终状态(应为TERMINATED)
System.out.println("子线程最终状态: " + taskThread.getState());
}
static class InterruptibleTask implements Runnable {
@Override
public void run() {
try {
System.out.println("子线程开始执行,进入循环任务");
// 模拟长时间运行的任务(可响应中断)
while (!Thread.currentThread().isInterrupted()) {
// 执行任务逻辑(此处用休眠模拟)
Thread.sleep(500);
System.out.println("子线程执行中...");
}
// 检测到中断信号后退出循环
System.out.println("子线程检测到中断信号,准备退出");
} catch (InterruptedException e) {
// 捕获sleep()被中断的异常
System.out.println("子线程在休眠中被中断,准备退出");
// 可根据需要重新设置中断状态(视业务场景而定)
// Thread.currentThread().interrupt();
}
// run()方法执行完毕,线程进入TERMINATED状态
}
}
}
进入TERMINATED状态有两种方式,一种是正常结束,另一种是被显示调用了interupte方法。
特别需要注意的是:
interrupt()方法并不会立即终止线程,它的核心作用是给线程发送一个 “中断信号”(设置线程的中断标志位为true)。线程是否终止、何时终止,完全由线程自身的逻辑决定 —— 需要线程主动检测这个信号并做出响应。
当线程处于sleep()等阻塞状态时,它能够接收中断信号,此时interrupt()会立即唤醒线程并抛出InterruptedException(这是阻塞状态下对中断的特殊响应),但线程并不会因此 “被强行终止”,是否后续是否继续运行还是终止,取决于异常处理逻辑(例如在catch块中主动退出run()方法才会终止)。
因此,想要优雅地结束线程,应当在代码中结合Thread.currentThread().isInterrupted()(实例方法,仅返回中断状态,不清除标志位)进行判断,在检测到中断信号时主动退出任务。需要注意的是,Thread.interrupted()是静态方法,它会返回当前线程的中断状态并清除中断标志位(复位为false),使用时需区分两者的差异。
wait和park的区别
上方有提到wait和park方法可以让线程进入到BLOCKED状态,但两者之间是否有区别呢?wait()(来自Object类)和park()(来自LockSupport类)都是Java中用于线程等待/阻塞的方法,但在使用场景、实现机制和细节上有显著区别,主要差异如下:
1. 所属类与调用条件
-
wait()- 属于
Object类,是所有对象都具备的方法。 - 必须在
synchronized同步块/方法中调用(必须先获取对象的监视器锁),否则会抛出IllegalMonitorStateException。 - 示例:
synchronized (lock) { lock.wait(); // 必须在同步块内调用 }
- 属于
-
park()- 属于
LockSupport工具类,是静态方法。 - 无需获取任何锁,可在任意位置调用。
- 示例:
LockSupport.park(); // 直接调用,无需锁
- 属于
2. 唤醒方式
-
wait()- 必须由其他线程调用相同对象的
notify()或notifyAll()唤醒。 - 唤醒后,
线程需要重新竞争获取锁才能继续执行(可能再次阻塞)。
- 必须由其他线程调用相同对象的
-
park()- 需由其他线程调用
LockSupport.unpark(Thread)唤醒(指定具体线程)。 - 唤醒后直接继续执行,
无需重新竞争锁(无锁阻塞)。
- 需由其他线程调用
3. 中断响应
-
wait()- 被中断时会抛出
InterruptedException,且会清除中断标志位。 - 示例:
try { lock.wait(); } catch (InterruptedException e) { // 中断后进入此处,中断标志位已被清除 System.out.println("被中断: " + Thread.currentThread().isInterrupted()); // false }
- 被中断时会抛出
-
park()- 被中断时不会抛出异常,但会清除中断标志位(需手动检测)。
- 示例:
LockSupport.park(); // 被中断后继续执行,需手动检查 if (Thread.interrupted()) { System.out.println("被中断"); // 此处会清除标志位 }
4. 超时机制
-
wait()- 支持超时版本:
wait(long timeout)(毫秒),超时后自动唤醒。 - 示例:
lock.wait(1000); // 最多等待1秒
- 支持超时版本:
-
park()- 支持超时版本:
parkNanos(long nanos)(纳秒)和parkUntil(long deadline)(绝对时间)。 - 示例:
LockSupport.parkNanos(1_000_000_000); // 最多等待1秒
- 支持超时版本:
5. 等待队列归属
-
wait()- 线程会进入锁对象的等待队列,与锁绑定。
- 唤醒后只能竞争当前锁对象。
-
park()- 线程会进入自身的等待状态,与具体锁无关。
- 唤醒后可继续执行任意逻辑,不受锁约束。
6. 典型使用场景
-
wait()- 传统同步机制(
synchronized)中实现线程间协作(如生产者-消费者模型)。
- 传统同步机制(
-
park()- 并发工具类底层实现(如
ReentrantLock、CountDownLatch),更灵活的线程阻塞控制。
- 并发工具类底层实现(如
总结对比表
| 特性 | wait() |
park() |
|---|---|---|
| 所属类 | Object |
LockSupport |
| 是否需要锁 | 必须(synchronized块内) |
不需要 |
| 唤醒方式 | notify()/notifyAll() |
unpark(Thread) |
| 中断响应 | 抛InterruptedException |
不抛异常,需手动检测 |
| 锁竞争 | 唤醒后需重新竞争锁 | 唤醒后直接执行,无锁竞争 |
| 典型应用 | 传统同步机制协作 | 并发工具类底层实现 |
锁升级
在 Java 中,锁升级(Lock Escalation)是 JVM 为了优化同步性能而采用的一种自适应锁机制,主要针对synchronized关键字实现的同步锁。
其核心思想是:根据锁的竞争程度,将锁从低开销的形式逐步升级为高开销但更适合并发的形式,以此平衡性能和线程安全。
锁的四种状态(升级路径)
Java 中的synchronized锁存在四种状态,按照升级顺序依次为:
-
无锁状态 (Unlocked)
锁对象刚创建时,未被任何线程持有,处于无锁状态。 -
偏向锁(Biased Locking)
适用场景:当锁仅被一个线程多次获取,且无竞争时。
原理:锁对象头会记录当前持有锁的线程 ID,后续该线程再次获取锁时,无需通过 CAS 竞争,只需判断线程 ID 是否匹配,几乎无开销。
升级触发:当有第二个线程尝试获取锁时,偏向锁会升级为轻量级锁。 -
轻量级锁(Lightweight Locking)
适用场景:多个个线程交替获取锁(竞争不激烈)。
原理:线程获取锁时,会通过CAS(CompareAndSet:这是一个原子操作)操作在锁对象头中记录 “锁记录” 指针(指向线程栈中的锁记录),避免了重量级锁的内核态切换。
升级触发:当多个线程同时竞争锁(出现锁冲突),且自旋(忙等)次数超过阈值时,轻量级锁会升级为重量级锁。 -
重量级锁(Heavyweight Locking)
适用场景:多线程激烈竞争锁(如高并发场景)。
原理:依赖操作系统的互斥量(Mutex)实现,线程竞争失败时会进入阻塞状态(内核态),等待操作系统调度唤醒,开销较大。
锁升级的核心逻辑
1、自适应:
JVM 会根据历史竞争情况动态调整锁状态(例如,若偏向锁经常被多个线程竞争,可能直接取消偏向模式)。
2、不可逆:锁升级是单向的(无锁→偏向锁→轻量级锁→重量级锁),一旦升级为重量级锁,不会再降级,以避免频繁切换的开销。
为什么需要锁升级?
3、减少开销:
低竞争场景下,偏向锁和轻量级锁通过用户态操作(避免内核态切换)显著提升性能。
**4、兼顾并发:
**高竞争场景下,重量级锁通过操作系统机制保证线程安全,避免自旋浪费 CPU 资源。
这种设计让synchronized在 JDK 1.6 之后性能大幅提升,成为 Java 中高效的同步机制。
一个例子看懂JAVA锁升级机制
用 “公共卫生间” 的场景来类比 Java 中的锁升级机制,帮助理解不同锁状态的切换过程:
1. 无锁状态
卫生间刚打扫干净,门没锁,也没人使用。此时任何想使用的人都可以直接进入 —— 对应无锁状态,对象未被任何线程持有。
2. 偏向锁
假设小明第一个来上厕所,他进去后没有锁门(但心里默认 “这是我正在用的”),并且在门上贴了一张纸条:“当前使用者:小明”。
小明用完出来后,没撕纸条就走了。下次他再来时,看到纸条上是自己的名字,直接推门就进,无需重新 "登记"—— 这就是偏向锁的核心:同一线程重复获取锁时,几乎无开销。
此时如果只有小明一个人用这个卫生间,纸条会一直贴着,永远是 “偏向” 小明的状态。
3. 轻量级锁(自旋锁)
某天小红也来用卫生间,发现门上贴着 “小明” 的纸条(偏向锁)。这时小红会撕掉纸条,和小明 “协商”:“我先用一下,你稍等”。
小明如果刚好用完出来了,小红就进去,并在门上贴自己的纸条;
如果小明还在里面,小红不会立刻走,而是在门口等几秒(自旋),看看小明是不是很快就出来 —— 这就是轻量级锁:线程竞争不激烈时,通过短时间等待(自旋)避免 "重量级" 的排队流程。
4. 重量级锁
如果卫生间门口突然来了很多人(高并发),小红等了几秒(自旋次数阈值)发现小明还没出来,后面又排了一堆人。这时大家意识到 “协商” 没用了,需要一个明确的排队机制。
于是有人搬来一个叫号机,所有人必须取号排队,轮到自己才能进去,没轮到的人可以去做别的事(不会一直等)—— 这就是重量级锁:通过操作系统级别的排队机制(类似互斥量)管理竞争,虽然开销大,但适合高并发场景。
Studying will never be ending.
▲如有纰漏,烦请指正~~
更多推荐
所有评论(0)