面试题 - Java - 多线程(二)

92 阅读18分钟

12. Java常见锁

  1. synchronized: 内置锁
  2. ReentrantLock: 可重入锁
  3. ReadWriteLock: 读写锁
  4. StampedLock: 邮戳锁
  5. ReentrantReadWriteLock: 可重入读写锁

让我用生动的例子来解释这些不同类型的锁。

1. Synchronized (内置锁) - 像单人卫生间 🚽

public class BathRoom {
    public synchronized void use(String person) {
        System.out.println(person + "进入卫生间");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(person + "离开卫生间");
    }
}

特点:

  • 一次只能一个人使用
  • 自动加锁解锁
  • 不能中断等待
  • 不能尝试获取锁
sequenceDiagram
    participant A as 人员A
    participant L as 卫生间
    participant B as 人员B
    
    A->>L: 进入并锁门
    B->>L: 等待
    A->>L: 使用完毕
    A->>L: 解锁离开
    B->>L: 进入并锁门

2. ReentrantLock (可重入锁) - 像高级卫生间 🚾

public class ModernBathRoom {
    private final ReentrantLock lock = new ReentrantLock();
    
    public void use(String person) {
        // 尝试获取锁,等待5秒
        try {
            if (lock.tryLock(5, TimeUnit.SECONDS)) {
                try {
                    System.out.println(person + "进入卫生间");
                    Thread.sleep(1000);
                } finally {
                    lock.unlock();
                    System.out.println(person + "离开卫生间");
                }
            } else {
                System.out.println(person + "等太久了,去别的卫生间");
            }
        } catch (InterruptedException e) {
            System.out.println(person + "被打断了");
        }
    }
}

特点:

  • 可以尝试获取锁
  • 可以设置等待时间
  • 可以中断等待
  • 可以公平排队
  • 需要手动解锁

3. ReadWriteLock (读写锁) - 像图书馆阅览室 📚

public class Library {
    private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
    private final Lock readLock = rwLock.readLock();
    private final Lock writeLock = rwLock.writeLock();
    private String book = "原始内容";
    
    // 多人可以同时读
    public void readBook(String person) {
        readLock.lock();
        try {
            System.out.println(person + "正在读书: " + book);
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            readLock.unlock();
        }
    }
    
    // 只能一个人写
    public void writeBook(String person, String content) {
        writeLock.lock();
        try {
            System.out.println(person + "正在写书");
            book = content;
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            writeLock.unlock();
        }
    }
}
graph TD
    A[图书馆] --> B[读锁]
    A --> C[写锁]
    B --> D[多人可同时读]
    C --> E[一人写时独占]
    D --> F[读者1]
    D --> G[读者2]
    D --> H[读者3]
    E --> I[写者]

4. StampedLock (邮戳锁) - 像现代博物馆参观系统 🏛️

public class Museum {
    private final StampedLock lock = new StampedLock();
    private double treasureLocation = 0.0;
    
    // 乐观读(参观者拍照,不影响展品移动)
    public double viewTreasure(String person) {
        long stamp = lock.tryOptimisticRead();
        double currentLocation = treasureLocation;
        if (!lock.validate(stamp)) {
            // 发现展品正在移动,切换到悲观读
            stamp = lock.readLock();
            try {
                currentLocation = treasureLocation;
            } finally {
                lock.unlockRead(stamp);
            }
        }
        return currentLocation;
    }
    
    // 写锁(工作人员移动展品)
    public void moveTreasure(double newLocation) {
        long stamp = lock.writeLock();
        try {
            treasureLocation = newLocation;
        } finally {
            lock.unlockWrite(stamp);
        }
    }
}

特点:

  • 支持乐观读
  • 性能更好
  • 不可重入
  • 不支持条件变量

5. ReentrantReadWriteLock (可重入读写锁) - 像高级图书馆系统 📖

public class AdvancedLibrary {
    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    private final Map<String, String> books = new HashMap<>();
    
    // 查看书籍目录
    public String viewBook(String bookName) {
        lock.readLock().lock();
        try {
            return books.get(bookName);
        } finally {
            lock.readLock().unlock();
        }
    }
    
    // 更新书籍内容
    public void updateBook(String bookName, String content) {
        lock.writeLock().lock();
        try {
            books.put(bookName, content);
        } finally {
            lock.writeLock().unlock();
        }
    }
    
    // 整理书籍(需要同时获取读锁和写锁)
    public void organizeBooks() {
        // 可重入特性,允许同一个线程获取多个锁
        lock.writeLock().lock();
        try {
            lock.readLock().lock();
            try {
                // 整理书籍逻辑
            } finally {
                lock.readLock().unlock();
            }
        } finally {
            lock.writeLock().unlock();
        }
    }
}

6. 锁的对比图

graph TB
    A[Java锁类型] --> B[Synchronized]
    A --> C[ReentrantLock]
    A --> D[ReadWriteLock]
    A --> E[StampedLock]
    
    B --> B1[简单易用]
    B --> B2[自动解锁]
    B --> B3[不可中断]
    
    C --> C1[灵活可控]
    C --> C2[可中断]
    C --> C3[可设超时]
    
    D --> D1[读写分离]
    D --> D2[共享/独占]
    D --> D3[可重入]
    
    E --> E1[乐观读]
    E --> E2[高性能]
    E --> E3[不可重入]

7. 使用场景对比

锁类型适用场景比喻
Synchronized简单同步普通卫生间
ReentrantLock需要灵活控制高级卫生间
ReadWriteLock读多写少图书馆
StampedLock高性能场景现代博物馆
ReentrantReadWriteLock复杂读写场景高级图书馆

8. 性能对比示例

public class LockPerformanceTest {
    private static final int THREAD_COUNT = 100;
    private static final int OPERATION_COUNT = 10000;
    
    // 测试不同锁的性能
    public static void main(String[] args) throws InterruptedException {
        testLock(new SynchronizedLock());
        testLock(new ReentrantLockWrapper());
        testLock(new ReadWriteLockWrapper());
        testLock(new StampedLockWrapper());
    }
    
    private static void testLock(LockInterface lock) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
        long startTime = System.currentTimeMillis();
        
        // 创建多个线程测试
        for (int i = 0; i < THREAD_COUNT; i++) {
            new Thread(() -> {
                for (int j = 0; j < OPERATION_COUNT; j++) {
                    lock.read();
                    if (j % 100 == 0) {
                        lock.write();
                    }
                }
                latch.countDown();
            }).start();
        }
        
        latch.await();
        long endTime = System.currentTimeMillis();
        System.out.println(lock.getClass().getSimpleName() + 
            " 耗时: " + (endTime - startTime) + "ms");
    }
}

记住:

  1. Synchronized: 简单场景首选
  2. ReentrantLock: 需要灵活控制时使用
  3. ReadWriteLock: 读多写少的场景
  4. StampedLock: 追求极致性能时使用
  5. ReentrantReadWriteLock: 复杂读写场景使用

13. sleep()和wait()的区别

  • sleep():

    • 不释放锁
    • Thread类的方法
    • 到时间自动恢复
  • wait():

    • 释放锁
    • Object类的方法
    • 需要notify/notifyAll唤醒

14. 悲观锁和乐观锁

  • 悲观锁:假设会发生并发冲突,访问时都要加锁

    • 例如:synchronized、ReentrantLock
  • 乐观锁:假设不会发生并发冲突,更新时检查是否有冲突

    • 例如:CAS操作、版本号机制

15. BlockingQueue

阻塞队列特点:

  • 队列满时,入队阻塞
  • 队列空时,出队阻塞

常见实现:

  • ArrayBlockingQueue:有界队列
  • LinkedBlockingQueue:可选有界队列
  • PriorityBlockingQueue:优先级队列
  • DelayQueue:延迟队列

让我用一个餐厅点餐系统来生动地解释 BlockingQueue 的实现和使用。

1. BlockingQueue 家族图示

graph TD
    A[BlockingQueue] --> B[ArrayBlockingQueue]
    A --> C[LinkedBlockingQueue]
    A --> D[PriorityBlockingQueue]
    A --> E[DelayQueue]
    A --> F[SynchronousQueue]
    
    B --> B1[固定大小餐厅]
    C --> C1[无限制餐厅]
    D --> D1[优先级餐厅]
    E --> E1[定时取餐]
    F --> F1[即点即取]

2. ArrayBlockingQueue - 像固定座位的餐厅 🏪

public class Restaurant {
    // 创建一个容量为10的候餐区
    private final BlockingQueue<Order> orderQueue = new ArrayBlockingQueue<>(10);
    
    static class Order {
        private final String customerName;
        private final String dishName;
        
        public Order(String customerName, String dishName) {
            this.customerName = customerName;
            this.dishName = dishName;
        }
        
        @Override
        public String toString() {
            return customerName + "的" + dishName;
        }
    }
    
    // 服务员接单
    public void takeOrder(Order order) {
        try {
            // 如果候餐区满了,服务员等待
            orderQueue.put(order);
            System.out.println("接到订单: " + order);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
    
    // 厨师做菜
    public void cookOrder() {
        try {
            // 如果没有订单,厨师等待
            Order order = orderQueue.take();
            System.out.println("正在制作: " + order);
            // 模拟做菜时间
            Thread.sleep(1000);
            System.out.println("完成订单: " + order);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
    
    public static void main(String[] args) {
        Restaurant restaurant = new Restaurant();
        
        // 创建服务员线程
        new Thread(() -> {
            for (int i = 1; i <= 20; i++) {
                restaurant.takeOrder(new Order("顾客" + i, "菜品" + i));
            }
        }, "服务员").start();
        
        // 创建多个厨师线程
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                while (true) {
                    restaurant.cookOrder();
                }
            }, "厨师" + (i + 1)).start();
        }
    }
}

3. LinkedBlockingQueue - 像无限座位的自助餐厅 🍱

public class BuffetRestaurant {
    // 创建一个不限容量的排队队列
    private final BlockingQueue<Customer> customerQueue = new LinkedBlockingQueue<>();
    
    static class Customer {
        String name;
        public Customer(String name) {
            this.name = name;
        }
    }
    
    // 顾客排队
    public void enterQueue(Customer customer) {
        customerQueue.offer(customer);
        System.out.println(customer.name + "加入队列");
    }
    
    // 取餐
    public void serveCustomer() {
        try {
            Customer customer = customerQueue.take();
            System.out.println("正在服务: " + customer.name);
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

4. PriorityBlockingQueue - 像VIP餐厅 🎩

public class VipRestaurant {
    // 创建一个优先级队列,VIP优先服务
    private final PriorityBlockingQueue<Order> orderQueue = 
        new PriorityBlockingQueue<>(10, 
            Comparator.comparingInt(o -> o.vipLevel));
    
    static class Order implements Comparable<Order> {
        String customerName;
        int vipLevel; // VIP等级,数字越小优先级越高
        
        public Order(String customerName, int vipLevel) {
            this.customerName = customerName;
            this.vipLevel = vipLevel;
        }
        
        @Override
        public int compareTo(Order other) {
            return Integer.compare(this.vipLevel, other.vipLevel);
        }
    }
}

5. DelayQueue - 像预约取餐系统 ⏰

public class TakeoutRestaurant {
    // 创建一个延迟队列,用于定时取餐
    private final DelayQueue<DelayedOrder> orderQueue = new DelayQueue<>();
    
    static class DelayedOrder implements Delayed {
        private final String orderInfo;
        private final long readyTime;
        
        public DelayedOrder(String orderInfo, long delayInMillis) {
            this.orderInfo = orderInfo;
            this.readyTime = System.currentTimeMillis() + delayInMillis;
        }
        
        @Override
        public long getDelay(TimeUnit unit) {
            return unit.convert(readyTime - System.currentTimeMillis(), 
                TimeUnit.MILLISECONDS);
        }
        
        @Override
        public int compareTo(Delayed other) {
            return Long.compare(getDelay(TimeUnit.MILLISECONDS), 
                other.getDelay(TimeUnit.MILLISECONDS));
        }
    }
    
    // 下预约订单
    public void placeOrder(String orderInfo, long delayInMillis) {
        orderQueue.put(new DelayedOrder(orderInfo, delayInMillis));
    }
    
    // 取餐服务
    public void startDeliveryService() {
        new Thread(() -> {
            while (true) {
                try {
                    DelayedOrder order = orderQueue.take();
                    System.out.println("订单已准备好: " + order.orderInfo);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        }, "取餐服务").start();
    }
}

6. SynchronousQueue - 像快餐窗口 🍔

public class FastFoodCounter {
    // 创建一个同步队列,不存储订单,直接交付
    private final SynchronousQueue<String> counter = new SynchronousQueue<>();
    
    // 服务员下单
    public void placeOrder(String order) {
        try {
            System.out.println("等待厨师接单: " + order);
            counter.put(order);
            System.out.println("厨师已接单: " + order);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
    
    // 厨师接单
    public void processOrder() {
        try {
            String order = counter.take();
            System.out.println("厨师开始制作: " + order);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

7. 生产者-消费者模式示例

public class RestaurantSystem {
    private final BlockingQueue<Order> orderQueue;
    private final int cookCount;
    private volatile boolean isOpen = true;
    
    public RestaurantSystem(int queueSize, int cookCount) {
        this.orderQueue = new ArrayBlockingQueue<>(queueSize);
        this.cookCount = cookCount;
    }
    
    // 启动餐厅
    public void start() {
        // 启动服务员线程
        new Thread(this::waiterWork, "服务员").start();
        
        // 启动多个厨师线程
        for (int i = 0; i < cookCount; i++) {
            new Thread(this::cookWork, "厨师" + (i + 1)).start();
        }
    }
    
    // 服务员工作
    private void waiterWork() {
        try {
            int orderNum = 1;
            while (isOpen) {
                Order order = new Order("订单" + orderNum++);
                orderQueue.put(order);
                System.out.println("服务员接单: " + order);
                Thread.sleep(500); // 模拟接单时间
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
    
    // 厨师工作
    private void cookWork() {
        try {
            while (isOpen || !orderQueue.isEmpty()) {
                Order order = orderQueue.take();
                System.out.println(Thread.currentThread().getName() + 
                    " 正在制作: " + order);
                Thread.sleep(1000); // 模拟制作时间
                System.out.println(Thread.currentThread().getName() + 
                    " 完成订单: " + order);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
    
    // 关闭餐厅
    public void close() {
        isOpen = false;
    }
}

8. 不同队列的特点对比

graph TB
    A[BlockingQueue类型] --> B[ArrayBlockingQueue]
    A --> C[LinkedBlockingQueue]
    A --> D[PriorityBlockingQueue]
    A --> E[DelayQueue]
    A --> F[SynchronousQueue]
    
    B --> B1[固定容量]
    B --> B2[循环数组]
    
    C --> C1[无限容量]
    C --> C2[链表结构]
    
    D --> D1[优先级排序]
    D --> D2[堆实现]
    
    E --> E1[延迟获取]
    E --> E2[优先级队列]
    
    F --> F1[零容量]
    F --> F2[直接交付]

记住:

  1. ArrayBlockingQueue: 适合固定大小的场景
  2. LinkedBlockingQueue: 适合动态增长的场景
  3. PriorityBlockingQueue: 需要优先级处理的场景
  4. DelayQueue: 需要延时处理的场景
  5. SynchronousQueue: 需要即时交付的场景

选择合适的队列类型对提高系统性能很重要!

16. 线程安全的集合

  1. Vector: 线程安全的ArrayList
  2. Hashtable: 线程安全的HashMap
  3. ConcurrentHashMap: 分段锁实现的Map
  4. CopyOnWriteArrayList: 写时复制的List
  5. ConcurrentLinkedQueue: 非阻塞队列

让我用生动的例子来解释这些线程安全集合的实现原理。

1. Vector - 像带锁的储物柜 🔒

public class SafeLocker {
    private Vector<String> items = new Vector<>();
    
    public void addItem(String item) {
        items.add(item);  // synchronized
    }
    
    public String getItem(int index) {
        return items.get(index);  // synchronized
    }
}

原理解释:

graph TD
    A[Vector操作] --> B{synchronized锁}
    B --> C[一次只允许一个线程访问]
    B --> D[其他线程等待]
    C --> E[完成操作]
    E --> F[释放锁]
    D --> B

就像一个带锁的储物柜:

  • 每次只能一个人操作
  • 操作时会锁门
  • 其他人必须等待
  • 性能较差(都要排队)

2. Hashtable - 像老式银行柜台 🏦

public class OldBank {
    private Hashtable<String, Double> accounts = new Hashtable<>();
    
    public void deposit(String account, Double amount) {
        accounts.put(account, accounts.getOrDefault(account, 0.0) + amount);
    }
    
    public Double getBalance(String account) {
        return accounts.get(account);
    }
}

原理解释:

graph LR
    A[客户请求] --> B{全局锁}
    B --> C[账户操作]
    C --> D[释放锁]
    D --> E[下一个客户]

就像老式银行:

  • 所有操作都要排队
  • 一次只能服务一个客户
  • 其他客户必须等待
  • 效率低下

3. ConcurrentHashMap - 像现代银行 🏢

public class ModernBank {
    private ConcurrentHashMap<String, Double> accounts = new ConcurrentHashMap<>();
    
    public void deposit(String account, Double amount) {
        accounts.compute(account, (k, v) -> (v == null ? 0 : v) + amount);
    }
    
    public Double getBalance(String account) {
        return accounts.get(account);
    }
}

原理解释:

graph TD
    A[ConcurrentHashMap] --> B[分段锁Segment]
    B --> C[Segment 1]
    B --> D[Segment 2]
    B --> E[Segment 3]
    C --> F[多个Entry]
    D --> G[多个Entry]
    E --> H[多个Entry]

就像现代银行:

  • 多个柜台同时服务
  • 每个柜台独立工作
  • 不同柜台互不影响
  • 效率更高

4. CopyOnWriteArrayList - 像共享文档系统 📄

public class DocumentSystem {
    private CopyOnWriteArrayList<String> documents = new CopyOnWriteArrayList<>();
    
    public void addDocument(String doc) {
        documents.add(doc);  // 创建新副本
    }
    
    public void readDocuments() {
        // 读取不需要锁
        for (String doc : documents) {
            System.out.println(doc);
        }
    }
}

原理解释:

sequenceDiagram
    participant Writer as 写线程
    participant Original as 原始数组
    participant Copy as 数组副本
    participant Reader as 读线程
    
    Writer->>Original: 请求写入
    Writer->>Copy: 创建副本
    Writer->>Copy: 修改副本
    Writer->>Original: 替换原始数组
    Reader->>Original: 读取(不受影响)

就像共享文档系统:

  • 修改时创建新副本
  • 读取时使用原件
  • 修改完成后替换
  • 适合读多写少的场景

5. ConcurrentLinkedQueue - 像自助餐厅 🍽️

public class ModernBuffet {
    private ConcurrentLinkedQueue<String> dishes = new ConcurrentLinkedQueue<>();
    
    public void addDish(String dish) {
        dishes.offer(dish);  // 无锁添加
    }
    
    public String takeDish() {
        return dishes.poll();  // 无锁获取
    }
}

原理解释:

graph LR
    A[头节点] --> B[节点1]
    B --> C[节点2]
    C --> D[节点3]
    D --> E[尾节点]
    
    F[线程1] -.-> B
    G[线程2] -.-> D
    H[线程3] -.-> C

就像自助餐厅:

  • 多人可同时取餐
  • 使用CAS操作保证安全
  • 无需加锁,效率高
  • 适合高并发场景

6. 综合比较示例

public class CollectionPerformanceTest {
    private static final int THREAD_COUNT = 100;
    private static final int OPERATION_COUNT = 10000;
    
    public static void main(String[] args) throws InterruptedException {
        // 测试不同集合的性能
        testCollection(new Vector<>(), "Vector");
        testCollection(new CopyOnWriteArrayList<>(), "CopyOnWriteArrayList");
        testMap(new Hashtable<>(), "Hashtable");
        testMap(new ConcurrentHashMap<>(), "ConcurrentHashMap");
        testQueue(new ConcurrentLinkedQueue<>(), "ConcurrentLinkedQueue");
    }
    
    private static void testCollection(List<Integer> list, String name) {
        long start = System.currentTimeMillis();
        CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
        
        for (int i = 0; i < THREAD_COUNT; i++) {
            new Thread(() -> {
                for (int j = 0; j < OPERATION_COUNT; j++) {
                    list.add(j);
                    list.get(j % (j + 1));
                }
                latch.countDown();
            }).start();
        }
        
        try {
            latch.await();
            System.out.println(name + " 耗时: " + 
                (System.currentTimeMillis() - start) + "ms");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

7. 各集合类型的特点对比

graph TB
    A[线程安全集合] --> B[Vector]
    A --> C[Hashtable]
    A --> D[ConcurrentHashMap]
    A --> E[CopyOnWriteArrayList]
    A --> F[ConcurrentLinkedQueue]
    
    B --> B1[synchronized]
    B --> B2[全局锁]
    
    C --> C1[synchronized]
    C --> C2[全局锁]
    
    D --> D1[分段锁]
    D --> D2[CAS操作]
    
    E --> E1[写时复制]
    E --> E2[读写分离]
    
    F --> F1[无锁]
    F --> F2[CAS操作]

8. 使用场景建议

集合类型适用场景优点缺点
Vector简单同步需求实现简单性能差
Hashtable简单同步需求实现简单性能差
ConcurrentHashMap高并发读写性能好内存占用较大
CopyOnWriteArrayList读多写少读性能好写性能差
ConcurrentLinkedQueue高并发队列性能好不保证即时一致性

9. 最佳实践示例

public class ThreadSafeCollectionExample {
    // 用户信息缓存
    private final ConcurrentHashMap<String, UserInfo> userCache = 
        new ConcurrentHashMap<>();
    
    // 操作日志
    private final CopyOnWriteArrayList<String> operationLogs = 
        new CopyOnWriteArrayList<>();
    
    // 任务队列
    private final ConcurrentLinkedQueue<Task> taskQueue = 
        new ConcurrentLinkedQueue<>();
    
    public void processUser(String userId, UserInfo info) {
        // 更新用户信息
        userCache.put(userId, info);
        
        // 记录操作日志
        operationLogs.add("Updated user: " + userId);
        
        // 添加后续任务
        taskQueue.offer(new Task("process-" + userId));
    }
    
    static class UserInfo {
        String name;
        int age;
    }
    
    static class Task {
        String taskId;
        
        Task(String taskId) {
            this.taskId = taskId;
        }
    }
}

记住:

  1. Vector/Hashtable: 简单场景,不追求性能时使用
  2. ConcurrentHashMap: 需要高并发读写时使用
  3. CopyOnWriteArrayList: 读多写少的场景使用
  4. ConcurrentLinkedQueue: 高并发队列操作时使用

选择合适的线程安全集合可以大大提高系统性能!

17. 线程安全的集合底层实现

让我详细解析每个线程安全集合的底层实现原理。

1. Vector 底层实现 - 数组 + synchronized 🔒

public class Vector<E> {
    // 底层使用数组存储
    protected Object[] elementData;
    
    // 实际元素个数
    protected int elementCount;
    
    // 所有方法都使用synchronized修饰
    public synchronized E get(int index) {
        if (index >= elementCount)
            throw new ArrayIndexOutOfBoundsException(index);
        return elementData(index);
    }
    
    public synchronized E set(int index, E element) {
        if (index >= elementCount)
            throw new ArrayIndexOutOfBoundsException(index);
        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }
}
graph TD
    A[Vector] --> B[Object数组]
    B --> C[synchronized方法]
    C --> D[全局锁]
    D --> E[性能较差]

2. Hashtable 底层实现 - 数组 + 链表 + synchronized 🏦

public class Hashtable<K,V> {
    // 底层使用Entry数组
    private Entry<?,?>[] table;
    
    // Entry节点,形成链表
    private static class Entry<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Entry<K,V> next;
    }
    
    // 所有方法都使用synchronized修饰
    public synchronized V put(K key, V value) {
        if (value == null) {
            throw new NullPointerException();
        }
        Entry<?,?> tab[] = table;
        // ... 哈希计算和冲突处理
    }
}
graph TD
    A[Hashtable] --> B[Entry数组]
    B --> C[链表结构]
    C --> D[synchronized方法]
    D --> E[全局锁]

3. ConcurrentHashMap 底层实现 - Node数组 + CAS + synchronized 🏢

public class ConcurrentHashMap<K,V> {
    // Node数组(表)
    transient volatile Node<K,V>[] table;
    
    // Node节点
    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        volatile V val;
        volatile Node<K,V> next;
    }
    
    // put操作
    final V putVal(K key, V value, boolean onlyIfAbsent) {
        if (key == null || value == null) throw new NullPointerException();
        int hash = spread(key.hashCode());
        int binCount = 0;
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            // CAS操作确保线程安全
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))
                    break;
            }
            // 其他情况使用synchronized锁定节点
            else {
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        // ... 处理冲突
                    }
                }
            }
        }
    }
}
graph TD
    A[ConcurrentHashMap] --> B[Node数组]
    B --> C[CAS操作]
    B --> D[synchronized锁]
    C --> E[无锁操作]
    D --> F[细粒度锁]
    E --> G[高并发性能]
    F --> G

4. CopyOnWriteArrayList 底层实现 - 数组副本 + ReentrantLock 📄

public class CopyOnWriteArrayList<E> {
    // 存储数组
    private transient volatile Object[] array;
    
    // 独占锁
    final transient ReentrantLock lock = new ReentrantLock();
    
    // 添加元素
    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            // 创建新数组
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            // 替换原数组
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }
    
    // 获取元素(无锁)
    public E get(int index) {
        return get(getArray(), index);
    }
}
sequenceDiagram
    participant Thread1 as 写线程
    participant Array as 原数组
    participant Lock as ReentrantLock
    participant NewArray as 新数组
    participant Thread2 as 读线程
    
    Thread1->>Lock: 获取锁
    Thread1->>Array: 复制数组
    Thread1->>NewArray: 修改新数组
    Thread1->>Array: 替换原数组
    Thread1->>Lock: 释放锁
    Thread2->>Array: 直接读取(无锁)

5. ConcurrentLinkedQueue 底层实现 - CAS + 链表 🔄

public class ConcurrentLinkedQueue<E> {
    // 节点类
    private static class Node<E> {
        volatile E item;
        volatile Node<E> next;
    }
    
    // 头尾节点
    private transient volatile Node<E> head;
    private transient volatile Node<E> tail;
    
    // 入队操作
    public boolean offer(E e) {
        if (e == null) throw new NullPointerException();
        final Node<E> newNode = new Node<E>(e);
        
        for (Node<E> t = tail, p = t;;) {
            Node<E> q = p.next;
            if (q == null) {
                // CAS设置next引用
                if (p.casNext(null, newNode)) {
                    // 更新tail
                    if (p != t)
                        casTail(t, newNode);
                    return true;
                }
            }
            else if (p == q)
                p = (t != (t = tail)) ? t : head;
            else
                p = (p != t && t != (t = tail)) ? t : q;
        }
    }
}
graph LR
    A[头节点] -->|CAS| B[节点1]
    B -->|CAS| C[节点2]
    C -->|CAS| D[节点3]
    D -->|CAS| E[尾节点]

6. 性能对比

public class CollectionPerformanceTest {
    private static final int THREAD_COUNT = 10;
    private static final int OPERATION_COUNT = 100000;
    
    public static void main(String[] args) throws Exception {
        // 测试各种集合的性能
        testCollection(new Vector<>(), "Vector");
        testCollection(new CopyOnWriteArrayList<>(), "CopyOnWriteArrayList");
        testMap(new Hashtable<>(), "Hashtable");
        testMap(new ConcurrentHashMap<>(), "ConcurrentHashMap");
        testQueue(new ConcurrentLinkedQueue<>(), "ConcurrentLinkedQueue");
    }
    
    private static void testCollection(List<Integer> list, String name) {
        ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);
        long startTime = System.currentTimeMillis();
        
        for (int i = 0; i < THREAD_COUNT; i++) {
            executor.submit(() -> {
                for (int j = 0; j < OPERATION_COUNT; j++) {
                    list.add(j);
                }
            });
        }
        
        executor.shutdown();
        try {
            executor.awaitTermination(10, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        
        System.out.println(name + " 耗时: " + 
            (System.currentTimeMillis() - startTime) + "ms");
    }
}

7. 各集合的特点总结

graph TB
    A[线程安全集合] --> B[实现方式]
    B --> C[锁类型]
    B --> D[数据结构]
    B --> E[性能特点]
    
    C --> C1[synchronized全局锁]
    C --> C2[分段锁]
    C --> C3[CAS无锁]
    
    D --> D1[数组]
    D --> D2[链表]
    D --> D3[数组+链表]
    
    E --> E1[读写性能]
    E --> E2[内存消耗]
    E --> E3[并发度]

8. 选择建议

集合类型底层实现线程安全实现适用场景
Vector数组synchronized简单同步需求
Hashtable数组+链表synchronized简单同步需求
ConcurrentHashMap数组+链表CAS+synchronized高并发读写
CopyOnWriteArrayList数组副本ReentrantLock读多写少
ConcurrentLinkedQueue链表CAS高并发队列

记住:

  1. Vector/Hashtable:使用synchronized,性能较差
  2. ConcurrentHashMap:使用分段锁+CAS,性能好
  3. CopyOnWriteArrayList:写时复制,读性能好
  4. ConcurrentLinkedQueue:CAS无锁,高并发性能好

选择时要根据具体场景权衡性能和内存消耗!

18. Atomic类

原子类特点:

  • 基于CAS操作
  • 保证原子性
  • 性能比synchronized好

缺点:

  • ABA问题
  • 循环时间长开销大
  • 只能保证一个共享变量的原子操作

让我用一个生动的例子来解释原子类:

/* 
想象一个银行柜台场景:

普通变量 = 手工记账
原子类 = 电子记账系统

多个柜员同时操作:
- 手工记账可能出错(数据不一致)
- 电子系统永远准确(原子操作)
*/

class BankExample {
    // 手工记账(普通变量)
    private var manualBalance = 1000
    
    // 电子系统(原子类)
    private val atomicBalance = AtomicInteger(1000)
    
    fun demonstrate() {
        // 模拟多个柜员同时操作
        repeat(3) { tellerId ->
            thread {
                // 手工记账
                manualBalance += 100
                
                // 电子系统
                atomicBalance.addAndGet(100)
            }
        }
        
        Thread.sleep(1000)
        println("手工记账余额: $manualBalance")        // 可能不是1300
        println("电子系统余额: ${atomicBalance.get()}") // 一定是1300
    }
}

具体场景演示:

/* 
想象售票窗口场景:
- 多个窗口同时卖票
- 需要保证不超卖
*/

class TicketSystem {
    // 普通计数(不安全)
    private var normalTickets = 100
    
    // 原子计数(安全)
    private val atomicTickets = AtomicInteger(100)
    
    // 模拟售票
    fun sellTickets() {
        // 开启5个售票窗口
        repeat(5) { windowId ->
            thread {
                repeat(10) {
                    // 普通售票(可能出问题)
                    if (normalTickets > 0) {
                        normalTickets-- // 可能超卖
                    }
                    
                    // 原子售票(绝对安全)
                    while (true) {
                        val current = atomicTickets.get()
                        if (current <= 0) break
                        
                        // compareAndSet 就像"看票-卖票"是一气呵成的
                        if (atomicTickets.compareAndSet(current, current - 1)) {
                            println("窗口$windowId 卖出一张票,剩余${atomicTickets.get()}张")
                            break
                        }
                    }
                }
            }
        }
    }
}

CAS操作形象解释:

/* 
想象超市结账场景:

普通操作:
1. 收银员看到价格100元
2. 找零时发现价格已被改成200元
3. 按照100元找零就出错了

CAS操作:
1. 收银员看到价格100元
2. 在收款前再次确认价格还是100元
3. 如果价格变了,重新开始整个过程
*/

class CashierExample {
    private val price = AtomicInteger(100)
    
    fun checkout() {
        while (true) {
            val currentPrice = price.get()  // 看价格
            val newPrice = currentPrice - 50  // 计算折扣
            
            // 确认价格没变并更新
            if (price.compareAndSet(currentPrice, newPrice)) {
                println("成功修改价格为: $newPrice")
                break
            } else {
                println("价格已被其他收银员修改,重试")
            }
        }
    }
}

常见原子操作:

class AtomicOperations {
    private val counter = AtomicInteger(0)
    
    fun examples() {
        // 1. 增加并获取 (像电子计数器)
        counter.incrementAndGet()  // ++i
        counter.getAndIncrement()  // i++
        
        // 2. 更新值 (像电子价签)
        counter.set(100)
        
        // 3. 获取当前值 (像查看显示屏)
        val current = counter.get()
        
        // 4. 比较并设置 (像确认后更新)
        counter.compareAndSet(100, 200)
        
        // 5. 增加指定值 (像批量加减)
        counter.addAndGet(50)
    }
}

实际应用场景:

class RealWorldExamples {
    // 1. 计数器(像点击量统计)
    private val pageViews = AtomicLong(0)
    
    // 2. 状态标记(像开关状态)
    private val initialized = AtomicBoolean(false)
    
    // 3. 库存管理(像商品库存)
    private val stock = AtomicInteger(100)
    
    fun operate() {
        // 记录访问量
        pageViews.incrementAndGet()
        
        // 初始化一次
        if (initialized.compareAndSet(false, true)) {
            println("首次初始化")
        }
        
        // 减少库存
        if (stock.decrementAndGet() >= 0) {
            println("成功扣减库存")
        } else {
            println("库存不足")
        }
    }
}

总结:

  1. 原子类就像电子系统,保证操作的准确性
  2. CAS像是"看-改"的一气呵成操作
  3. 适合简单的计数、状态管理场景
  4. 不需要加锁就能保证线程安全
  5. 性能通常比锁要好

使用建议:

  1. 简单计数用AtomicInteger
  2. 状态标记用AtomicBoolean
  3. 对象引用用AtomicReference
  4. 需要多个操作原子性时用锁
  5. 高并发场景优先考虑原子类

19. ThreadLocal使用场景

使用场景:

  1. 数据库连接
  2. Session管理
  3. 用户身份信息传递
  4. 事务上下文

与Synchronized比较:

  • ThreadLocal: 线程隔离,各线程独立
  • Synchronized: 线程同步,保证互斥

这些是Java多线程编程中最常见的面试题及其详细解答。每个主题都很重要,建议深入理解原理和使用场景。

20.请详细说明下CAS原理和应用

让我用生动的例子来解释CAS(Compare And Swap)操作:

1. 生活中的CAS例子

场景一:排队买奶茶

sequenceDiagram
    participant C as 顾客
    participant S as 奶茶店
    
    C->>S: 看到剩余10杯奶茶
    C->>S: 想买1杯
    C->>S: 确认还是10杯才买
    Note over S: 如果还是10杯:成功购买
    Note over S: 如果不是10杯:重新查看

就像你去买奶茶:

  1. 看到还剩10杯(获取当前值)
  2. 你想买1杯(准备更新)
  3. 付款前再确认还是10杯(比较并交换)
    • 如果还是10杯:成功买到
    • 如果变成9杯:重新确认数量

2. 代码中的CAS

简单的CAS操作:

public class CASExample {
    private AtomicInteger tickets = new AtomicInteger(100);
    
    public boolean buyTicket() {
        while (true) {
            // 获取当前票数
            int current = tickets.get();
            
            // 如果没票了,返回false
            if (current <= 0) {
                return false;
            }
            
            // 尝试更新票数,只有当前值等于current时才会成功
            if (tickets.compareAndSet(current, current - 1)) {
                return true;    // 购票成功
            }
            // 如果失败,说明有其他人先买了,继续循环尝试
        }
    }
}

3. 形象的图解

CAS操作流程:

graph TD
    A[开始] --> B[读取当前值]
    B --> C{值是否符合预期?}
    C -->|是| D[更新成功]
    C -->|否| E[更新失败]
    E --> B

4. 真实场景示例

场景:电影院售票系统

public class Cinema {
    private AtomicInteger[] seats;  // 每个座位的状态
    
    public Cinema(int seatCount) {
        seats = new AtomicInteger[seatCount];
        for (int i = 0; i < seatCount; i++) {
            seats[i] = new AtomicInteger(0); // 0表示未售出
        }
    }
    
    public boolean bookSeat(int seatNumber) {
        // 获取座位
        AtomicInteger seat = seats[seatNumber];
        
        // 尝试预订座位
        while (true) {
            int current = seat.get();
            
            // 座位已被预订
            if (current == 1) {
                System.out.println("座位" + seatNumber + "已被预订");
                return false;
            }
            
            // CAS操作:尝试将座位状态从0改为1
            if (seat.compareAndSet(0, 1)) {
                System.out.println("成功预订座位" + seatNumber);
                return true;
            }
            
            // 如果CAS失败,说明其他人刚刚预订了这个座位
            System.out.println("座位" + seatNumber + "预订失败,重试中...");
        }
    }
}

// 使用示例
public class BookingDemo {
    public static void main(String[] args) {
        Cinema cinema = new Cinema(10);
        
        // 多个线程同时订票
        new Thread(() -> cinema.bookSeat(5)).start();
        new Thread(() -> cinema.bookSeat(5)).start();
        new Thread(() -> cinema.bookSeat(5)).start();
    }
}

5. CAS的优缺点

优点图解:

graph LR
    A[CAS优点] --> B[无锁]
    A --> C[高性能]
    A --> D[不会死锁]
    
    B --> B1[不需要线程阻塞]
    C --> C1[适合高并发]
    D --> D1[更安全]

缺点示例:

// ABA问题示例
public class ABAExample {
    private AtomicReference<Integer> value = 
        new AtomicReference<>(100);
    
    public void update() {
        Integer oldValue = value.get();  // 获取值100
        // 其他线程执行了 100 -> 101 -> 100 的操作
        // 当前线程无法感知这个变化
        value.compareAndSet(oldValue, 200);  // 仍然成功
    }
}

// 解决ABA问题
public class ABAFixed {
    private AtomicStampedReference<Integer> value = 
        new AtomicStampedReference<>(100, 0);
    
    public void update() {
        int stamp = value.getStamp();
        Integer oldValue = value.getReference();
        // 检查值和版本号
        value.compareAndSet(oldValue, 200, stamp, stamp + 1);
    }
}

6. 实际应用场景

1. 计数器:

public class ConcurrentCounter {
    private AtomicLong count = new AtomicLong(0);
    
    public void increment() {
        while (true) {
            long current = count.get();
            if (count.compareAndSet(current, current + 1)) {
                break;
            }
            // 如果失败,说明其他线程修改了值,重试
        }
    }
}

2. 缓存更新:

public class CacheUpdater {
    private AtomicReference<Map<String, String>> cache = 
        new AtomicReference<>(new HashMap<>());
    
    public void updateCache(String key, String value) {
        while (true) {
            Map<String, String> current = cache.get();
            Map<String, String> newMap = new HashMap<>(current);
            newMap.put(key, value);
            
            if (cache.compareAndSet(current, newMap)) {
                break;
            }
        }
    }
}

7. 总结

CAS就像是一个"乐观"的操作:

  1. 先看看现在的值是什么
  2. 计算新的值
  3. 在更新时确认原来的值没变
    • 没变:更新成功
    • 变了:重试

优点:

  • 不需要加锁,性能好
  • 适合并发场景
  • 不会死锁

缺点:

  • 可能会自旋(一直重试)
  • 存在ABA问题
  • 只能保证单个变量的原子性

使用建议:

  1. 适合简单的原子操作
  2. 需要考虑ABA问题
  3. 高并发场景下要考虑自旋开销

通过这些例子,我们可以看到CAS是一种非常巧妙的无锁并发机制,在正确的场景下使用可以带来很好的性能提升。

让我用一个生动的例子来解释原子类:

/* 
想象一个银行柜台场景:

普通变量 = 手工记账
原子类 = 电子记账系统

多个柜员同时操作:
- 手工记账可能出错(数据不一致)
- 电子系统永远准确(原子操作)
*/

class BankExample {
    // 手工记账(普通变量)
    private var manualBalance = 1000
    
    // 电子系统(原子类)
    private val atomicBalance = AtomicInteger(1000)
    
    fun demonstrate() {
        // 模拟多个柜员同时操作
        repeat(3) { tellerId ->
            thread {
                // 手工记账
                manualBalance += 100
                
                // 电子系统
                atomicBalance.addAndGet(100)
            }
        }
        
        Thread.sleep(1000)
        println("手工记账余额: $manualBalance")        // 可能不是1300
        println("电子系统余额: ${atomicBalance.get()}") // 一定是1300
    }
}

具体场景演示:

/* 
想象售票窗口场景:
- 多个窗口同时卖票
- 需要保证不超卖
*/

class TicketSystem {
    // 普通计数(不安全)
    private var normalTickets = 100
    
    // 原子计数(安全)
    private val atomicTickets = AtomicInteger(100)
    
    // 模拟售票
    fun sellTickets() {
        // 开启5个售票窗口
        repeat(5) { windowId ->
            thread {
                repeat(10) {
                    // 普通售票(可能出问题)
                    if (normalTickets > 0) {
                        normalTickets-- // 可能超卖
                    }
                    
                    // 原子售票(绝对安全)
                    while (true) {
                        val current = atomicTickets.get()
                        if (current <= 0) break
                        
                        // compareAndSet 就像"看票-卖票"是一气呵成的
                        if (atomicTickets.compareAndSet(current, current - 1)) {
                            println("窗口$windowId 卖出一张票,剩余${atomicTickets.get()}张")
                            break
                        }
                    }
                }
            }
        }
    }
}
CAS操作形象解释:
/* 
想象超市结账场景:

普通操作:
1. 收银员看到价格100元
2. 找零时发现价格已被改成200元
3. 按照100元找零就出错了

CAS操作:
1. 收银员看到价格100元
2. 在收款前再次确认价格还是100元
3. 如果价格变了,重新开始整个过程
*/

class CashierExample {
    private val price = AtomicInteger(100)
    
    fun checkout() {
        while (true) {
            val currentPrice = price.get()  // 看价格
            val newPrice = currentPrice - 50  // 计算折扣
            
            // 确认价格没变并更新
            if (price.compareAndSet(currentPrice, newPrice)) {
                println("成功修改价格为: $newPrice")
                break
            } else {
                println("价格已被其他收银员修改,重试")
            }
        }
    }
}
常见原子操作:
class AtomicOperations {
    private val counter = AtomicInteger(0)
    
    fun examples() {
        // 1. 增加并获取 (像电子计数器)
        counter.incrementAndGet()  // ++i
        counter.getAndIncrement()  // i++
        
        // 2. 更新值 (像电子价签)
        counter.set(100)
        
        // 3. 获取当前值 (像查看显示屏)
        val current = counter.get()
        
        // 4. 比较并设置 (像确认后更新)
        counter.compareAndSet(100, 200)
        
        // 5. 增加指定值 (像批量加减)
        counter.addAndGet(50)
    }
}
实际应用场景:
class RealWorldExamples {
    // 1. 计数器(像点击量统计)
    private val pageViews = AtomicLong(0)
    
    // 2. 状态标记(像开关状态)
    private val initialized = AtomicBoolean(false)
    
    // 3. 库存管理(像商品库存)
    private val stock = AtomicInteger(100)
    
    fun operate() {
        // 记录访问量
        pageViews.incrementAndGet()
        
        // 初始化一次
        if (initialized.compareAndSet(false, true)) {
            println("首次初始化")
        }
        
        // 减少库存
        if (stock.decrementAndGet() >= 0) {
            println("成功扣减库存")
        } else {
            println("库存不足")
        }
    }
}

总结:

  1. 原子类就像电子系统,保证操作的准确性
  2. CAS像是"看-改"的一气呵成操作
  3. 适合简单的计数、状态管理场景
  4. 不需要加锁就能保证线程安全
  5. 性能通常比锁要好

使用建议:

  1. 简单计数用AtomicInteger
  2. 状态标记用AtomicBoolean
  3. 对象引用用AtomicReference
  4. 需要多个操作原子性时用锁
  5. 高并发场景优先考虑原子类

21.CAS和synchronized的区别

让我用通俗易懂的方式来解释CAS和synchronized的区别:

1. CAS(Compare And Swap)

通俗理解: 想象你在修改一个数字,就像在玩"抢座位"游戏:

  1. 看到座位是空的(获取当前值)
  2. 准备坐下(准备修改)
  3. 坐下前再次确认座位还是空的(比较并交换)
// CAS操作示例
public class CASExample {
    private AtomicInteger value = new AtomicInteger(0);
    
    public void increment() {
        int oldValue;
        int newValue;
        do {
            oldValue = value.get();           // 获取当前值
            newValue = oldValue + 1;          // 计算新值
        } while (!value.compareAndSet(oldValue, newValue)); // CAS操作
    }
}

图解CAS过程:

sequenceDiagram
    participant T1 as 线程1
    participant M as 内存
    participant T2 as 线程2
    
    T1->>M: 读取值A
    T2->>M: 读取值A
    T1->>M: CAS(A→B) 成功
    T2->>M: CAS(A→B) 失败
    T2->>M: 重新读取值B
    T2->>M: CAS(B→C) 成功

2. synchronized

通俗理解: 像是给房间上锁:

  1. 进入房间前先上锁(其他人进不来)
  2. 做完事情后开锁(其他人才能进)
public class SynchronizedExample {
    private int value = 0;
    
    public synchronized void increment() {
        value++;    // 直接修改,因为已经上锁了
    }
}

图解synchronized过程:

sequenceDiagram
    participant T1 as 线程1
    participant L as 锁
    participant T2 as 线程2
    
    T1->>L: 获取锁
    T2->>L: 尝试获取锁(等待)
    T1->>L: 执行操作
    T1->>L: 释放锁
    T2->>L: 获取锁
    T2->>L: 执行操作
    T2->>L: 释放锁

3. 主要区别

graph TD
    A[CAS vs Synchronized] --> B[并发策略]
    A --> C[性能影响]
    A --> D[使用场景]
    A --> E[实现机制]
    
    B --> B1[CAS: 乐观锁]
    B --> B2[Synchronized: 悲观锁]
    
    C --> C1[CAS: 无阻塞]
    C --> C2[Synchronized: 可能阻塞]
    
    D --> D1[CAS: 简单原子操作]
    D --> D2[Synchronized: 复杂同步操作]
    
    E --> E1[CAS: 硬件级别]
    E --> E2[Synchronized: 操作系统级别]

4. 实际应用示例

1. CAS适用场景:

// 计数器场景
public class Counter {
    private AtomicInteger count = new AtomicInteger(0);
    
    public void increment() {
        count.incrementAndGet(); // 使用CAS
    }
    
    public int getCount() {
        return count.get();
    }
}

2. synchronized适用场景:

// 复杂业务逻辑场景
public class BankAccount {
    private double balance;
    
    public synchronized void transfer(BankAccount target, double amount) {
        if (this.balance >= amount) {
            this.balance -= amount;
            target.balance += amount;
        }
    }
}

5. 在ConcurrentHashMap中的应用

public class ConcurrentHashMap<K,V> {
    // CAS操作:插入空桶
    final V putVal(K key, V value, boolean onlyIfAbsent) {
        if ((p = tabAt(tab, i = (n - 1) & hash)) == null) {
            // 如果桶是空的,用CAS操作插入
            if (casTabAt(tab, i, null, 
                new Node<K,V>(hash, key, value, null)))
                return null;
        }
        // 如果发生冲突,使用synchronized
        else {
            synchronized (p) {
                // 处理冲突
            }
        }
    }
}

6. 性能比较

graph LR
    A[性能比较] --> B[CAS]
    A --> C[Synchronized]
    
    B --> B1[优点]
    B --> B2[缺点]
    
    C --> C1[优点]
    C --> C2[缺点]
    
    B1 --> B11[无锁开销]
    B1 --> B12[适合简单操作]
    
    B2 --> B21[可能自旋]
    B2 --> B22[ABA问题]
    
    C1 --> C11[实现简单]
    C1 --> C12[适合复杂操作]
    
    C2 --> C21[有锁开销]
    C2 --> C22[可能阻塞]

7. 使用建议

  1. 选择CAS的场景:
// 简单的原子操作
AtomicInteger counter = new AtomicInteger(0);
counter.incrementAndGet();

// 状态更新
AtomicReference<State> state = new AtomicReference<>(State.INIT);
state.compareAndSet(State.INIT, State.RUNNING);
  1. 选择synchronized的场景:
// 复杂的业务逻辑
synchronized void complexOperation() {
    // 多个步骤的操作
    step1();
    step2();
    step3();
}

// 需要保护多个资源
synchronized void transferMoney(Account from, Account to, double amount) {
    // 涉及多个对象的操作
}

总结:

  1. CAS适合简单的原子操作,性能更好,但可能出现ABA问题
  2. synchronized适合复杂的同步操作,实现简单,但性能较差
  3. ConcurrentHashMap巧妙地结合了两者的优点:
    • 用CAS处理无冲突情况
    • 用synchronized处理冲突情况

这种组合使用的方式充分发挥了两种机制的优势,是一种非常优秀的设计。

22.volatile的原理和作用

让我详细解释volatile的原理和作用:

3. volatile的基本概念

通俗理解: 想象成"告示牌"机制:

  • 所有人看到的都是最新的公告
  • 一个人更新了公告,其他人立即能看到
  • 但不能保证多人同时更新公告的正确性

2. volatile的三大特性

graph TD
    A[volatile特性] --> B[可见性]
    A --> C[有序性]
    A --> D[单次写入原子性]
    
    B --> B1[所有线程立即看到最新值]
    C --> C1[禁止指令重排]
    D --> D1[单次读/写操作原子性]

3. 实际应用示例

1. 状态标志:

public class TaskRunner {
    private volatile boolean running = false;
    
    public void start() {
        running = true;  // 其他线程立即可见
        while(running) {
            // 执行任务
        }
    }
    
    public void stop() {
        running = false; // 其他线程立即可见
    }
}

2. 双重检查锁定:

public class Singleton {
    private static volatile Singleton instance;
    
    public static Singleton getInstance() {
        if (instance == null) {  // 第一次检查
            synchronized (Singleton.class) {
                if (instance == null) {  // 第二次检查
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

4. volatile vs synchronized vs CAS

graph TB
    A[并发控制机制] --> B[volatile]
    A --> C[synchronized]
    A --> D[CAS]
    
    B --> B1[保证可见性]
    B --> B2[不保证原子性]
    B --> B3[性能最好]
    
    C --> C1[保证可见性]
    C --> C2[保证原子性]
    C --> C3[性能最差]
    
    D --> D1[保证可见性]
    D --> D2[保证原子性]
    D --> D3[性能居中]

5. 在ConcurrentHashMap中的应用

public class ConcurrentHashMap<K,V> {
    // volatile数组,确保所有线程看到最新的table
    private transient volatile Node<K,V>[] table;
    
    // volatile变量,确保size操作的可见性
    private transient volatile int sizeCtl;
    
    static class Node<K,V> {
        final int hash;
        final K key;
        // volatile值,确保修改对所有线程立即可见
        volatile V val;
        volatile Node<K,V> next;
    }
}

6. volatile读取流程

sequenceDiagram
    participant T1 as 线程1
    participant M as 主内存
    participant T2 as 线程2
    
    T1->>M: 写入volatile变量
    Note over M: 强制刷新到主内存
    M->>T2: 通知其他线程
    T2->>M: 从主内存读取最新值

7. 使用场景对比

1. 适合使用volatile的场景:

public class ConfigManager {
    private volatile String config;
    
    // 一个线程写,多个线程读的场景
    public void updateConfig(String newConfig) {
        config = newConfig;
    }
    
    public String getConfig() {
        return config;
    }
}

2. 不适合使用volatile的场景:

public class Counter {
    private volatile int count;  // 不适合
    
    // 多线程计数场景
    public void increment() {
        count++;  // 非原子操作
    }
}

8. 性能考虑

// 1. 读取性能
public class VolatileRead {
    private volatile int value;
    
    public int getValue() {
        return value;  // 直接读取,性能好
    }
}

// 2. 写入性能
public class VolatileWrite {
    private volatile int value;
    
    public void setValue(int value) {
        this.value = value;  // 需要内存屏障,性能略差
    }
}

9. 最佳实践

  1. 正确使用场景:
// 状态标志
public class ServiceManager {
    private volatile boolean isRunning;
    
    public void start() {
        isRunning = true;
    }
    
    public void stop() {
        isRunning = false;
    }
    
    public boolean isRunning() {
        return isRunning;
    }
}
  1. 组合使用:
public class CombinedExample {
    private volatile long count = 0;
    
    // 需要原子性时配合synchronized使用
    public synchronized void increment() {
        count++;
    }
    
    // 只读操作直接使用volatile即可
    public long getCount() {
        return count;
    }
}

10. 总结

volatile特别适合以下场景:

  1. 一写多读的场景
  2. 状态标志
  3. 双重检查锁定

不适合的场景:

  1. 需要原子性的操作
  2. 复合操作
  3. 多线程写入的场景

在ConcurrentHashMap中:

  • volatile确保了数据的可见性
  • 配合CAS和synchronized实现完整的并发控制
  • 是实现高性能并发的重要组成部分

理解volatile的特性和适用场景,对于编写高性能的并发程序非常重要。在ConcurrentHashMap中,它与CAS和synchronized一起,形成了完整的并发控制机制。