目录

1. 多线程交替打印 (15 分)

解法1:使用wait/notify

三个关键点

解法2:Lock/Condition

关键步骤对比(和 synchronized 版比)

 代码执行流程(以 A→B 为例)

为什么必须放 finally 解锁

Lock/Condition 的核心优势(面试必答)

2. 模拟售票系统 (15 分)

核心需求

解法1 使用 synchronized 实现

解法2使用 Lock + ReentrantLock 实现(更灵活,面试高频)

什么时候 “可重入” 特性才会发挥作用?

那售票题里为什么还推荐用 ReentrantLock?

解法3 使用AtomicInteger

核心原理拆解

1. 为什么普通 int 会出问题?

2. AtomicInteger 的 CAS 操作解决了什么?

3. 循环的作用是什么?

总结


1. 多线程交替打印 (15 分)

编写一个程序,启动三个线程,三个线程的 ID 分别是 A、B、C。每个线程将自己的 ID 在屏幕上打印 10 遍,打印顺序必须严格按照 ABCABC… 的顺序来打印。即输出结果为:

plaintext

A
B
C
A
B
C
…

(总共 30 次输出)

要求:使用 wait/notifyLock/Condition 来实现线程间的通信。

思考:

  • main 里写 new PrintA().start(); new PrintB().start(); new PrintC().start();,只是告诉裁判 “这三个人要上场跑步”,并不是让他们按顺序跑。
  • 裁判(CPU)会随机决定先喊谁起跑,而且跑一会儿还会把人拽下来,换另一个人跑(这叫线程调度)。谁先跑、跑多久,完全不由我们控制。
  • start() 只是启动线程,不是按顺序执行线程
  • 线程的执行顺序由 CPU 调度算法 决定,是随机的。(操作系统的特性)
  • 要想严格按 ABCABC... 打印,必须用锁 /wait/notify/Lock/Condition 来做线程间的协调通信,强制它们按顺序 “接力”。

解法1:使用wait/notify

// 主类:控制 ABC 打印顺序
public class PrintABC {
    // 核心:用一个变量标记「当前该谁打印」
    // 0=该A打印,1=该B打印,2=该C打印
    private static int flag = 0;
    // 锁对象:所有线程共用这一把锁
    private static final Object lock = new Object();
    // 打印轮数:比如打印10轮
    private static final int ROUND = 10;

    // ========== 1. 定义打印A的线程 ==========
    static class PrintA extends Thread {
        @Override
        public void run() {
            // 要打印10轮,循环10次
            for (int i = 0; i < ROUND; i++) {
                // 加锁:同一时间只有一个线程能进来
                synchronized (lock) {
                    // 循环等待:只要不是自己的轮次,就等着
                    while (flag != 0) {
                        try {
                            lock.wait(); // 线程A“睡觉”,等别人喊它
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    // 轮到自己了,打印A
                    System.out.print("A ");
                    // 标记:接下来该B打印了
                    flag = 1;
                    // 唤醒所有等待的线程(喊B:该你了!)
                    lock.notifyAll();
                }
            }
        }
    }

    // ========== 2. 定义打印B的线程 ==========
    static class PrintB extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < ROUND; i++) {
                synchronized (lock) {
                    // 不是B的轮次,就等
                    while (flag != 1) {
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    // 打印B
                    System.out.print("B ");
                    // 标记:接下来该C打印
                    flag = 2;
                    // 唤醒所有线程(喊C:该你了!)
                    lock.notifyAll();
                }
            }
        }
    }

    // ========== 3. 定义打印C的线程 ==========
    static class PrintC extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < ROUND; i++) {
                synchronized (lock) {
                    // 不是C的轮次,就等
                    while (flag != 2) {
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    // 打印C
                    System.out.print("C ");
                    // 标记:接下来该A打印(完成一轮,重置)
                    flag = 0;
                    // 唤醒所有线程(喊A:该你了!)
                    lock.notifyAll();
                }
            }
        }
    }

    // ========== 4. 启动线程 ==========
    public static void main(String[] args) {
        new PrintA().start();
        new PrintB().start();
        new PrintC().start();
    }
}
  • 按顺序打印 ABC 的核心是线程间等待 / 唤醒,通过flag标记轮次、lock加锁、wait()/notifyAll()通信;
  • synchronized保证同一时间只有一个线程执行,while防止虚假唤醒;
  • 所有线程共用同一个lockflag,才能实现顺序控制。
  • 只要用 await ()/wait (),判断条件就必须用 while——线程可能被误唤醒,while会再检查一次flag,确保真的轮到自己了。虚假唤醒不是代码写错导致的,而是 JVM / 操作系统层面的 “偶发情况”(比如 Linux 内核的 pthread_cond_wait 机制就可能触发)。虽然概率低,但只要出现一次,程序逻辑就会彻底乱掉,所以必须用 while 防御。
三个关键点
  • 锁的是 “对象”,不是代码

    • 修饰静态方法时:锁的是 “类对象”(整个类只有一把锁);
    • 修饰普通方法 / 代码块时:锁的是你指定的对象(比如代码里的lock);
    • 核心:多个线程必须抢同一把锁,synchronized 才生效

    比如打印 ABC 的代码里,A、B、C 三个线程都抢同一个lock对象的锁,才能按顺序执行;如果各抢各的锁,就会乱序。

  • 自动加锁、自动释放锁

    • 线程进入synchronized修饰的代码 → 自动 “拿到锁”;
    • 线程执行完代码 / 抛出异常 → 自动 “释放锁”;
    • 不用手动操作,新手不容易出错(这也是它比Lock简单的原因)。
  • 互斥性

    • 同一把锁,同一时间只能被一个线程持有;
    • 其他线程想拿这把锁,只能等(阻塞),直到锁被释放。

解法2:Lock/Condition

先搞懂核心概念(大白话版)

组件 类比(生活例子) 作用
Lock 厕所的大门锁(可手动开关) 替代 synchronized,实现更灵活的加锁 / 解锁
Condition 厕所门口的 “排队叫号器”(A/B/C 各一个) 替代 wait/notify,实现精准的线程等待 / 唤醒

核心优势:wait/notify 是 “广播式唤醒”(喊所有线程来抢),而 Condition 是 “精准唤醒”(只喊指定线程),效率更高、逻辑更清晰。

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

// 用Lock/Condition实现ABC顺序打印
public class PrintABCWithLock {
    // 1. 创建一把可重入锁(替代synchronized的锁对象)
    private static final Lock lock = new ReentrantLock();
    
    // 2. 为每个线程创建专属的Condition(叫号器)
    private static final Condition conditionA = lock.newCondition(); // A的叫号器
    private static final Condition conditionB = lock.newCondition(); // B的叫号器
    private static final Condition conditionC = lock.newCondition(); // C的叫号器
    
    // 3. 标记当前该谁打印(0=A,1=B,2=C)
    private static int flag = 0;
    // 打印轮数
    private static final int ROUND = 10;

    // ========== 打印A的线程 ==========
    static class PrintA extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < ROUND; i++) {
                // ① 手动加锁(替代synchronized代码块)
                lock.lock();
                try {
                    // 循环等待:不是A的轮次,就等A的叫号器
                    while (flag != 0) {
                        // 替代lock.wait():A线程等待,直到被唤醒
                        conditionA.await();
                    }
                    // 打印A
                    System.out.print("A ");
                    // 标记:该B打印了
                    flag = 1;
                    // 替代lock.notify():精准唤醒B线程(只喊B,不喊C)
                    conditionB.signal();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    // ② 手动解锁(必须放finally,防止死锁)
                    lock.unlock();
                }
            }
        }
    }

    // ========== 打印B的线程 ==========
    static class PrintB extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < ROUND; i++) {
                lock.lock();
                try {
                    while (flag != 1) {
                        // B线程等待,直到被A唤醒
                        conditionB.await();
                    }
                    System.out.print("B ");
                    flag = 2;
                    // 精准唤醒C线程
                    conditionC.signal();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }
    }

    // ========== 打印C的线程 ==========
    static class PrintC extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < ROUND; i++) {
                lock.lock();
                try {
                    while (flag != 2) {
                        // C线程等待,直到被B唤醒
                        conditionC.await();
                    }
                    System.out.print("C ");
                    flag = 0;
                    // 精准唤醒A线程,开始下一轮
                    conditionA.signal();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }
    }

    // ========== 启动线程 ==========
    public static void main(String[] args) {
        new PrintA().start();
        new PrintB().start();
        new PrintC().start();
    }
}
关键步骤对比(和 synchronized 版比)
操作 synchronized + wait/notify Lock + Condition
加锁 自动加锁(进入代码块) 手动调用 lock.lock()
解锁 自动解锁(退出代码块) 手动调用 lock.unlock()(必须放 finally)
线程等待 lock.wait() conditionX.await()
线程唤醒 lock.notifyAll()(广播) conditionX.signal()(精准唤醒)
 代码执行流程(以 A→B 为例)
  1. 线程 A 加锁 → 检查flag=0 → 打印 A → 改flag=1 → 调用conditionB.signal()(只唤醒 B)→ 解锁;
  2. 线程 B 之前在conditionB.await()等待 → 被唤醒后加锁 → 检查flag=1 → 打印 B → 改flag=2 → 调用conditionC.signal()(只唤醒 C)→ 解锁;
  3. 线程 C 同理,最后唤醒 A,完成一轮循环。
为什么必须放 finally 解锁

如果线程执行中抛出异常,lock.unlock() 放在 finally 里能保证锁一定被释放,避免死锁(这是Locksynchronized最大的区别:synchronized 自动解锁,Lock 必须手动解锁)。

Lock/Condition 的核心优势(面试必答)

  1. 精准唤醒:不用唤醒所有线程,只唤醒需要的线程(比如 A 只唤醒 B,不唤醒 C),减少无效竞争,效率更高;
  2. 灵活的锁控制:支持超时加锁(lock.tryLock(1, TimeUnit.SECONDS))、可中断加锁,解决了synchronized锁无法中断的问题;
  3. 多个等待队列:一个 Lock 可以创建多个 Condition,对应多个等待队列(比如 A/B/C 各一个),而synchronized只有一个等待队列。

2. 模拟售票系统 (15 分)

某电影院正在上映一部电影,共有 100 张票。现有 3 个售票窗口(模拟 3 个线程)同时对外售票。请设计一个程序,模拟这 3 个窗口的售票过程,直到所有票售完。

要求

  • 必须保证线程安全,不能出现多卖、超卖(卖出第 101 张票)的情况。
  • 每卖出一张票,打印出当前售票窗口的名称和票号,例如:“窗口 1 售出第 1 号票,还剩 99 张”。
  • 票售完后,每个窗口应停止售票,程序正常结束。
  • 请至少给出两种不同的实现方式(例如,使用 synchronizedLockAtomicInteger 等)

核心需求

  1. 3 个线程模拟 3 个售票窗口;
  2. 100 张票,线程安全(不超卖、不多卖);
  3. 打印格式:窗口X 售出第N号票,还剩M张
  4. 票售完后所有窗口停止,程序正常结束。

解法1 使用 synchronized 实现

public class TicketSellerWithSync {
    // 总票数(static保证3个窗口共享这100张票)
    private static int totalTickets = 100;

    // 同步售票方法:同一时间只能一个线程调用
    private static synchronized boolean sellTicket(String windowName) {
        // 1. 检查是否有票
        if (totalTickets <= 0) {
            return false; // 无票可售,返回false
        }
        // 2. 卖票(当前票号 = 剩余票数)
        int ticketNo = totalTickets;
        totalTickets--; // 票数减1
        // 3. 按要求打印
        System.out.printf("%s 售出第 %d 号票,还剩 %d 张%n", windowName, ticketNo, totalTickets);
        return true; // 售票成功
    }

    // 售票窗口线程类
    static class TicketWindow extends Thread {
        // 窗口名称(比如“窗口1”)
        private String windowName;

        public TicketWindow(String windowName) {
            this.windowName = windowName;
        }

        @Override
        public void run() {
            // 循环售票,直到无票可售
            while (true) {
                // 调用同步售票方法
                boolean success = sellTicket(windowName);
                if (!success) {
                    // 票售完,打印结束信息并退出循环
                    System.out.println(windowName + ":票已售完,停止售票");
                    break;
                }
                // 模拟售票耗时(可选,让效果更直观)
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    // 主方法:启动3个售票窗口
    public static void main(String[] args) {
        new TicketWindow("窗口1").start();
        new TicketWindow("窗口2").start();
        new TicketWindow("窗口3").start();
    }
}

方法级 synchronized 的作用:把整个方法变成 “不可分割的原子操作”

给静态方法加 synchronized,相当于给整个类对象加锁(因为静态方法属于类,不是对象),效果是:

  • 同一时间,只有一个线程能进入这个方法执行
  • 线程 A 进入方法后,会 “锁住” 这个方法,线程 B/C 必须等 A 执行完、退出方法后,才能进来;
  • ①②③④步会被完整执行,不会被其他线程打断 → 彻底避免线程安全问题。

解法2使用 Lock + ReentrantLock 实现(更灵活,面试高频)

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TicketSellerWithLock {
    // 总票数
    private static int totalTickets = 100;
    // 创建可重入锁(替代synchronized)
    private static final Lock lock = new ReentrantLock();

    // 售票方法(手动加锁/解锁)
    private static boolean sellTicket(String windowName) {
        // 1. 手动加锁
        lock.lock();
        try {
            // 2. 检查是否有票
            if (totalTickets <= 0) {
                return false;
            }
            // 3. 卖票并打印
            int ticketNo = totalTickets;
            totalTickets--;
            System.out.printf("%s 售出第 %d 号票,还剩 %d 张%n", windowName, ticketNo, totalTickets);
            return true;
        } finally {
            // 4. 手动解锁(必须放finally,防止死锁)
            lock.unlock();
        }
    }

    // 售票窗口线程类(和方案一完全一致)
    static class TicketWindow extends Thread {
        private String windowName;

        public TicketWindow(String windowName) {
            this.windowName = windowName;
        }

        @Override
        public void run() {
            while (true) {
                boolean success = sellTicket(windowName);
                if (!success) {
                    System.out.println(windowName + ":票已售完,停止售票");
                    break;
                }
                // 模拟售票耗时
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    // 主方法:启动3个窗口
    public static void main(String[] args) {
        new TicketWindow("窗口1").start();
        new TicketWindow("窗口2").start();
        new TicketWindow("窗口3").start();
    }
}

ReentrantLock = 可重复进入的锁,简单说:

同一个线程已经拿到这把锁后,再次去抢这把锁时,不会被自己锁住(可以直接进入),还会记录 “抢锁的次数”,解锁时要对应次数才能彻底释放。

不过这道题目的整个过程中,线程只抢了 1 次锁、解了 1 次锁,没有任何 “嵌套加锁” 的场景 —— 所以哪怕用的是 “不可重入锁”(假设存在),结果也完全一样。

这也是为什么我说:在这个简单场景下,ReentrantLock 的 “可重入” 特性是 “多余的”,它的价值体现在更复杂的场景里

什么时候 “可重入” 特性才会发挥作用?

只有当代码出现「同一线程嵌套调用需要同一把锁的方法」时,“可重入” 才是必须的 —— 否则会导致线程 “自己锁自己”(死锁)。

举个例子:给售票系统加个 “退票 + 售票” 的嵌套逻辑(模拟复杂场景):

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockDemo {
    private static final ReentrantLock lock = new ReentrantLock();
    private static int totalTickets = 100;

    // 售票方法(需要加锁)
    private static void sellTicket(String windowName) {
        lock.lock(); // 第一次加锁(计数=1)
        System.out.println(windowName + ":开始售票");
        // 嵌套调用退票方法(也需要加锁)
        refundTicket(windowName, 1); 
        lock.unlock(); // 第一次解锁(计数=0)
    }

    // 退票方法(也需要加锁)
    private static void refundTicket(String windowName, int num) {
        lock.lock(); // 第二次加锁(可重入,计数=2)
        totalTickets += num;
        System.out.println(windowName + ":退了" + num + "张票,剩余:" + totalTickets);
        lock.unlock(); // 第二次解锁(计数=1)
    }

    public static void main(String[] args) {
        sellTicket("窗口1");
    }
}

运行结果(正常执行,无死锁)

窗口1:开始售票
窗口1:退了1张票,剩余:101

核心逻辑:

  1. 线程调用 sellTicket,第一次 lock() → 计数 = 1;
  2. sellTicket 里调用 refundTicket,第二次 lock() → 因为是同一个线程,ReentrantLock 允许 “重入”,计数变成 2;
  3. refundTicket 执行 unlock() → 计数 = 1;
  4. sellTicket 执行 unlock() → 计数 = 0,锁彻底释放。

如果是 “不可重入锁”(假设):

步骤 2 会卡死 —— 线程已经拿到锁了,又去抢同一把锁,结果就是 “自己等自己释放锁”,永远等不到(死锁)。

这就是 “可重入” 的核心价值:解决同一线程嵌套加锁的死锁问题

那售票题里为什么还推荐用 ReentrantLock?

虽然 “可重入” 没用到,但 ReentrantLock 还有其他关键优势,比 synchronized 更灵活:

1. 支持 “公平锁”(可选)

如果想让 3 个窗口 “排队售票”(先等先得,避免某个窗口一直抢不到锁),只需改一行代码:

// 创建公平锁(按排队顺序拿锁)
private static final Lock lock = new ReentrantLock(true);

而 synchronized 永远是 “非公平锁”(随机抢锁),无法做到公平。

2. 支持 “超时拿锁”(避免死锁)

如果担心某个窗口拿锁后卡死,可以加超时逻辑:

private static boolean sellTicket(String windowName) {
    try {
        // 尝试拿锁1秒,拿不到就放弃
        if (!lock.tryLock(1, TimeUnit.SECONDS)) {
            System.out.println(windowName + ":1秒没拿到锁,放弃本次售票");
            return false;
        }
    } catch (InterruptedException e) {
        return false;
    }

    try {
        if (totalTickets <= 0) return false;
        int ticketNo = totalTickets;
        totalTickets--;
        System.out.printf("%s 售出第 %d 号票,还剩 %d 张%n", windowName, ticketNo, totalTickets);
        return true;
    } finally {
        lock.unlock();
    }
}

synchronized 做不到这一点 —— 拿不到锁就一直等,直到拿到为止,容易死锁。

3. 支持 “可中断拿锁”

如果需要手动停止某个窗口的售票线程,可以用 lock.lockInterruptibly(),线程等待锁时能被中断,而 synchronized 的等待是无法中断的。

  • 基础版售票题里,ReentrantLock 的 “可重入” 特性确实没用 —— 因为没有嵌套加锁的场景;
  • 用 ReentrantLock,不是因为 “可重入”,而是因为它的其他高级特性(公平锁、超时锁、可中断),这些是 synchronized 没有的;
  • “可重入” 是 ReentrantLock 的基础特性(名字都带 Reentrant),虽然简单场景用不到,但复杂场景(嵌套加锁)是必须的,也是面试必问的考点。

一句话记住:ReentrantLock 的核心价值不只是 “可重入”,更是比 synchronized 更灵活的锁控制能力—— 哪怕简单场景用不到全部特性,也是学习和面试的重点。

解法3 使用AtomicInteger

AtomicInteger 是解决售票题的无锁方案,和 synchronized/ReentrantLock 的 “加锁排队” 思路完全不同 —— 它靠 CPU 指令级的原子操作保证线程安全,性能更高,而且不用写任何锁代码。

AtomicInteger = 原子整数,它的所有操作(比如自减、比较赋值)都是「不可分割的原子操作」—— 多个线程同时操作时,不会出现 “中间态”,天然保证线程安全,不用加锁。

售票题用 AtomicInteger 的核心逻辑:用 AtomicInteger 替代普通 int 存储票数 → 用 compareAndSet(CAS) 实现 “查票 + 减票” 的原子操作 → 全程无锁,线程安全。

import java.util.concurrent.atomic.AtomicInteger;

public class TicketSellerWithAtomic {
    // 核心:用AtomicInteger替代普通int,初始值100
    private static final AtomicInteger totalTickets = new AtomicInteger(100);

    // 售票方法(无锁!)
    private static boolean sellTicket(String windowName) {
        // 循环CAS操作:保证“查票+减票”原子性
        while (true) {
            // 1. 获取当前剩余票数(原子操作,拿到的是最新值)
            int current = totalTickets.get();
            
            // 2. 没票了,返回false
            if (current <= 0) {
                return false;
            }
            
            // 3. CAS核心操作:尝试把票数从current改成current-1
            // 原理:只有当当前票数还是current时,才会修改成功(避免被其他线程抢先)
            boolean success = totalTickets.compareAndSet(current, current - 1);
            
            // 4. CAS成功=售票成功,打印并返回;失败=被其他线程抢先,重新循环
            if (success) {
                System.out.printf("%s 售出第 %d 号票,还剩 %d 张%n", 
                                  windowName, current, current - 1);
                return true;
            }
            // CAS失败:不做任何操作,回到循环开头重新尝试
        }
    }

    // 窗口线程类(和锁方案完全一样)
    static class TicketWindow extends Thread {
        private String windowName;

        public TicketWindow(String windowName) {
            this.windowName = windowName;
        }

        @Override
        public void run() {
            while (true) {
                boolean success = sellTicket(windowName);
                if (!success) {
                    System.out.println(windowName + ":票已售完,停止售票");
                    break;
                }
                // 模拟售票耗时
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    // 启动3个窗口
    public static void main(String[] args) {
        new TicketWindow("窗口1").start();
        new TicketWindow("窗口2").start();
        new TicketWindow("窗口3").start();
    }
}

核心原理拆解

1. 为什么普通 int 会出问题?

普通 int totalTickets-- 其实是 3 步操作:① 读取当前值 → ② 减 1 → ③ 写回新值。多线程时,可能出现 “线程 A 读了 100,线程 B 也读了 100,都减 1 后写回 99” → 超卖。

2. AtomicInteger 的 CAS 操作解决了什么?

compareAndSet(current, current-1)CPU 级的原子指令,相当于:“我要把值从 current 改成 current-1,只有当内存里的值还是 current 时,才改成功;如果已经被其他线程改了,就不改,返回 false。”

整个过程不可分割,没有中间态 —— 彻底避免了 “多线程同时改值” 的问题。

3. 循环的作用是什么?

while (true) 是 “自旋”:如果 CAS 失败(说明被其他线程抢先卖了这张票),就重新获取最新的票数,再试一次。

总结

这道多线程售票题的核心目标是保证 100 张票被 3 个窗口安全售卖(不超卖、不多卖),售完后程序正常结束,三种实现方法从原理、写法、特点上各有侧重,核心总结如下:

实现方法 核心原理 核心代码特点 优点 缺点
1. synchronized(同步锁) 给售票方法加锁,让 “查票 - 卖票 - 减票 - 打印” 成为原子操作,同一时间仅一个线程执行 静态方法加 synchronized,自动加锁 / 解锁,无需手动控制 最简单、新手易上手,自动解锁不易死锁 功能单一(非公平锁、不可中断、无法超时)
2. ReentrantLock(可重入锁) 手动加锁 / 解锁,通过锁保证核心操作原子性,支持更灵活的锁控制 lock.lock() 加锁,finallylock.unlock() 解锁,可指定公平锁 / 超时锁 灵活(公平锁、超时拿锁、可中断),面试高频 需手动解锁(忘写 finally 会导致死锁)
3. AtomicInteger(原子整数) 基于 CPU 指令级的 CAS(比较并交换)操作,无锁实现单个变量的原子修改 自旋 +compareAndSet 实现 “查票 - 减票” 原子性,全程无锁 性能最高(非阻塞式),无死锁风险 仅适合单个变量操作,自旋重试可能耗 CPU
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐