第6章

🔒 高级锁机制

深入理解ReentrantLock、ReadWriteLock、StampedLock等高级锁技术,掌握公平锁与非公平锁的区别

学习目标

ReentrantLock详解

ReentrantLock是Java并发包中提供的可重入锁,它提供了比synchronized更灵活的锁机制。与synchronized相比,ReentrantLock提供了更多的功能,如可中断锁、超时锁、公平锁等。

核心特性

ReentrantLock支持可重入性,同一个线程可以多次获取同一把锁,每次获取锁时计数器加1,释放锁时计数器减1,当计数器为0时锁才真正被释放。

基本使用方法

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockExample {
    private final ReentrantLock lock = new ReentrantLock();
    private int count = 0;
    
    public void increment() {
        lock.lock();
        try {
            count++;
            System.out.println("Count: " + count);
        } finally {
            lock.unlock();
        }
    }
    
    public void decrement() {
        lock.lock();
        try {
            count--;
            System.out.println("Count: " + count);
        } finally {
            lock.unlock();
        }
    }
}

与synchronized的对比

synchronized
  • JVM内置锁机制
  • 自动获取和释放锁
  • 不可中断
  • 非公平锁
  • 无法设置超时
ReentrantLock
  • API层面的锁
  • 手动获取和释放锁
  • 可中断锁
  • 支持公平锁和非公平锁
  • 支持超时机制

高级特性示例

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

public class AdvancedReentrantLockExample {
    private final ReentrantLock lock = new ReentrantLock(true); // 公平锁
    
    // 可中断锁
    public void interruptibleLock() throws InterruptedException {
        lock.lockInterruptibly();
        try {
            // 执行业务逻辑
            Thread.sleep(1000);
        } finally {
            lock.unlock();
        }
    }
    
    // 超时锁
    public boolean tryLockWithTimeout() {
        try {
            if (lock.tryLock(5, TimeUnit.SECONDS)) {
                try {
                    // 执行业务逻辑
                    return true;
                } finally {
                    lock.unlock();
                }
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return false;
    }
}

公平锁vs非公平锁

公平锁和非公平锁是ReentrantLock的两种工作模式,它们在性能和公平性之间提供了不同的权衡。

公平锁特性

公平性保证

公平锁保证线程按照请求锁的顺序获得锁,先到先得,避免了线程饥饿问题。

公平锁
  • 按照FIFO顺序获取锁
  • 避免线程饥饿
  • 性能相对较低
  • 适合对公平性要求高的场景
非公平锁
  • 允许插队获取锁
  • 可能导致线程饥饿
  • 性能相对较高
  • 适合对性能要求高的场景

性能对比示例

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

public class FairVsUnfairLockTest {
    private static final int THREAD_COUNT = 10;
    private static final int ITERATIONS = 100000;
    
    public static void testLock(ReentrantLock lock, String lockType) {
        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 < ITERATIONS; j++) {
                    lock.lock();
                    try {
                        // 模拟业务操作
                    } finally {
                        lock.unlock();
                    }
                }
                latch.countDown();
            }).start();
        }
        
        try {
            latch.await();
            long endTime = System.currentTimeMillis();
            System.out.println(lockType + " 耗时: " + (endTime - startTime) + "ms");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
    
    public static void main(String[] args) {
        // 测试公平锁
        ReentrantLock fairLock = new ReentrantLock(true);
        testLock(fairLock, "公平锁");
        
        // 测试非公平锁
        ReentrantLock unfairLock = new ReentrantLock(false);
        testLock(unfairLock, "非公平锁");
    }
}

ReadWriteLock读写锁

ReadWriteLock是一种特殊的锁机制,它允许多个线程同时读取共享资源,但只允许一个线程写入。这种设计在读多写少的场景下能显著提升性能。

读写分离

读锁是共享锁,多个线程可以同时持有;写锁是排他锁,只能被一个线程持有,且与读锁互斥。

基本使用示例

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.HashMap;
import java.util.Map;

public class ReadWriteLockCache {
    private final Map cache = new HashMap<>();
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    
    public V get(K key) {
        lock.readLock().lock();
        try {
            return cache.get(key);
        } finally {
            lock.readLock().unlock();
        }
    }
    
    public void put(K key, V value) {
        lock.writeLock().lock();
        try {
            cache.put(key, value);
        } finally {
            lock.writeLock().unlock();
        }
    }
    
    public void remove(K key) {
        lock.writeLock().lock();
        try {
            cache.remove(key);
        } finally {
            lock.writeLock().unlock();
        }
    }
    
    public int size() {
        lock.readLock().lock();
        try {
            return cache.size();
        } finally {
            lock.readLock().unlock();
        }
    }
}

锁降级示例

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class LockDowngradeExample {
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private volatile boolean dataReady = false;
    private String data;
    
    public String getData() {
        lock.readLock().lock();
        try {
            if (!dataReady) {
                // 释放读锁,获取写锁
                lock.readLock().unlock();
                lock.writeLock().lock();
                try {
                    // 双重检查
                    if (!dataReady) {
                        data = loadData(); // 加载数据
                        dataReady = true;
                    }
                    // 锁降级:在释放写锁之前获取读锁
                    lock.readLock().lock();
                } finally {
                    lock.writeLock().unlock();
                }
            }
            return data;
        } finally {
            lock.readLock().unlock();
        }
    }
    
    private String loadData() {
        // 模拟数据加载
        return "Loaded Data";
    }
}

Condition接口

Condition接口提供了类似Object.wait()和notify()的功能,但更加灵活。一个Lock可以关联多个Condition,实现更精确的线程通信。

生产者消费者模式

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.LinkedList;
import java.util.Queue;

public class BlockingQueue {
    private final Queue queue = new LinkedList<>();
    private final int capacity;
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition notEmpty = lock.newCondition();
    private final Condition notFull = lock.newCondition();
    
    public BlockingQueue(int capacity) {
        this.capacity = capacity;
    }
    
    public void put(T item) throws InterruptedException {
        lock.lock();
        try {
            while (queue.size() == capacity) {
                notFull.await(); // 队列满时等待
            }
            queue.offer(item);
            notEmpty.signal(); // 通知消费者
        } finally {
            lock.unlock();
        }
    }
    
    public T take() throws InterruptedException {
        lock.lock();
        try {
            while (queue.isEmpty()) {
                notEmpty.await(); // 队列空时等待
            }
            T item = queue.poll();
            notFull.signal(); // 通知生产者
            return item;
        } finally {
            lock.unlock();
        }
    }
    
    public int size() {
        lock.lock();
        try {
            return queue.size();
        } finally {
            lock.unlock();
        }
    }
}

StampedLock高级特性

StampedLock是Java 8引入的新锁机制,它提供了乐观读锁功能,在读多写少的场景下性能更优。StampedLock不支持重入和Condition。

乐观读锁

乐观读锁不会阻塞写操作,读取时先获取一个stamp,读取完成后验证stamp是否有效,如果无效则升级为悲观读锁。

基本使用示例

import java.util.concurrent.locks.StampedLock;

public class StampedLockExample {
    private final StampedLock lock = new StampedLock();
    private double x, y;
    
    // 写操作
    public void write(double newX, double newY) {
        long stamp = lock.writeLock();
        try {
            x = newX;
            y = newY;
        } finally {
            lock.unlockWrite(stamp);
        }
    }
    
    // 乐观读
    public double distanceFromOrigin() {
        long stamp = lock.tryOptimisticRead();
        double curX = x, curY = y;
        
        if (!lock.validate(stamp)) {
            // 乐观读失败,升级为悲观读
            stamp = lock.readLock();
            try {
                curX = x;
                curY = y;
            } finally {
                lock.unlockRead(stamp);
            }
        }
        return Math.sqrt(curX * curX + curY * curY);
    }
    
    // 悲观读
    public double[] getCoordinates() {
        long stamp = lock.readLock();
        try {
            return new double[]{x, y};
        } finally {
            lock.unlockRead(stamp);
        }
    }
}

性能对比

StampedLock优势
  • 乐观读不阻塞写操作
  • 读多写少场景性能优异
  • 支持锁升级和降级
  • 内存占用更小
使用注意事项
  • 不支持重入
  • 不支持Condition
  • 使用复杂度较高
  • 适合特定场景使用

实战练习

练习1:银行转账系统

使用ReentrantLock重新实现银行转账功能,避免死锁问题。

练习2:线程安全缓存

使用ReadWriteLock实现一个高性能的线程安全缓存系统。

练习3:生产者消费者

使用Condition接口实现一个支持多生产者多消费者的阻塞队列。

练习4:性能测试

对比synchronized、ReentrantLock、ReadWriteLock和StampedLock在不同场景下的性能表现。

上一章:线程同步机制 返回目录 下一章:线程间通信