处匈跑 发表于 2025-10-29 10:05:01

Java并发编程之Lock锁机制深度解析:从使用到源码实现

1. 锁的基本概念:从现实世界到代码世界

1.1 锁的演进:synchronized → Lock

想象一下健身房储物柜的使用场景:

[*]synchronized:像固定密码锁 - 简单易用但功能有限
[*]Lock接口:像智能电子锁 - 功能丰富且灵活可控
// synchronized - 固定密码锁
public synchronized void oldMethod() {
    // 自动上锁和解锁
    // 但无法中断、无法超时、无法尝试获取
}

// Lock - 智能电子锁
public void newMethod() {
    Lock lock = new ReentrantLock();
    lock.lock();// 手动开锁
    try {
      // 临界区代码
    } finally {
      lock.unlock();// 手动关锁
    }
}1.2 Lock接口的核心优势

特性synchronizedLock中断响应❌✅超时控制❌✅尝试获取❌✅公平性❌✅条件队列单个多个2. AQS:并发世界的交通指挥中心

2.1 AQS的核心设计思想

AQS(AbstractQueuedSynchronizer)就像高速公路收费站系统:

[*]state状态:当前可通行的车道数量
[*]同步队列:等待通行的车辆排队
[*]CAS操作:智能的车辆调度系统
/**
* AQS同步状态管理示例
*/
public class AQSCoreConcept {
    // state字段的三种典型用法:
   
    // 1. 互斥锁:state = 0(未锁定) 或 1(已锁定)
    // 2. 重入锁:state = 重入次数
    // 3. 读写锁:高16位 = 读锁计数,低16位 = 写锁计数
    private volatile int state;
   
    // 三个核心的state操作方法:
    protected final int getState() { return state; }
    protected final void setState(int newState) { state = newState; }
    protected final boolean compareAndSetState(int expect, int update) {
      // CAS原子操作,保证线程安全
      return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }
}2.2 同步队列:线程的"等候区"

/**
* AQS同步队列结构演示
*/
public class SyncQueueDemo {
    /**
   * 同步队列节点结构(双向链表):
   *
   * head (虚拟节点) ↔ ↔ ↔ tail
   *
   * waitStatus状态说明:
   * - CANCELLED(1):线程已取消
   * - SIGNAL(-1):后继线程需要被唤醒
   * - CONDITION(-2):线程在Condition队列中
   * - PROPAGATE(-3):共享模式下传播唤醒
   */
   
    // 独占模式获取锁的典型流程
    public void acquireDemo() {
      Lock lock = new ReentrantLock();
      
      // 底层调用AQS的acquire方法
      lock.lock();// -> sync.acquire(1);
      
      /**
         * acquire方法执行流程:
         * 1. tryAcquire()尝试直接获取锁
         * 2. 失败 → addWaiter()加入同步队列队尾
         * 3. acquireQueued()在队列中自旋等待
         * 4. 被前驱节点唤醒后重新尝试获取锁
         */
    }
}2.3 自定义锁实战:基于AQS实现TwinsLock

/**
* TwinsLock - 同一时刻最多允许两个线程访问的共享锁
* 设计思路:将AQS的state作为许可证计数器
*/
public class TwinsLock implements Lock {
    private final Sync sync = new Sync(2);
   
    private static final class Sync extends AbstractQueuedSynchronizer {
      Sync(int count) {
            if (count <= 0) throw new IllegalArgumentException("计数必须大于0");
            setState(count);// 初始化许可证数量
      }
      
      /**
         * 共享模式获取锁
         * @return 负数:获取失败;0:获取成功但无剩余;正数:获取成功且有剩余
         */
      @Override
      public int tryAcquireShared(int reduceCount) {
            for (;;) {// 自旋避免CAS失败
                int current = getState();
                int newCount = current - reduceCount;
               
                // 如果新计数<0(无许可证)或CAS设置成功,返回结果
                if (newCount < 0 || compareAndSetState(current, newCount)) {
                  return newCount;
                }
            }
      }
      
      /**
         * 共享模式释放锁
         */
      @Override
      public boolean tryReleaseShared(int returnCount) {
            for (;;) {
                int current = getState();
                int newCount = current + returnCount;
                if (compareAndSetState(current, newCount)) {
                  return true;
                }
            }
      }
    }
   
    @Override
    public void lock() {
      sync.acquireShared(1);// 获取1个许可证
    }
   
    @Override
    public void unlock() {
      sync.releaseShared(1);// 释放1个许可证
    }
   
    // 其他Lock方法实现...
}

/**
* 测试TwinsLock
*/
public class TwinsLockTest {
    @Test
    public void testTwinsLock() {
      final Lock lock = new TwinsLock();
      
      // 启动10个线程,但同一时刻只有2个能获取锁
      for (int i = 0; i < 10; i++) {
            Thread worker = new Thread(() -> {
                lock.lock();
                try {
                  System.out.println(Thread.currentThread().getName() + " 获取锁");
                  Thread.sleep(1000);
                } catch (InterruptedException e) {
                  Thread.currentThread().interrupt();
                } finally {
                  lock.unlock();
                }
            });
            worker.start();
      }
    }
}5.2 许可机制:先发后至的灵活性

/**
* 重入锁的重入特性演示
*/
public class ReentrantDemo {
    private final ReentrantLock lock = new ReentrantLock();
   
    public void outer() {
      lock.lock();
      try {
            System.out.println("外层方法获取锁,重入计数: " + getHoldCount());
            inner();// 重入:同一个线程再次获取同一把锁
      } finally {
            lock.unlock();
      }
    }
   
    public void inner() {
      lock.lock();// 这里不会阻塞,因为已经是锁的持有者
      try {
            System.out.println("内层方法获取锁,重入计数: " + getHoldCount());
      } finally {
            lock.unlock();
      }
    }
   
    private int getHoldCount() {
      // 返回当前线程持有该锁的次数
      return lock.getHoldCount();
    }
}5.3 Blocker:线程诊断的"身份证"

/**
* 公平性对比测试
*/
public class FairVsUnfairTest {
    private static Lock fairLock = new ReentrantLock(true);      // 公平锁
    private static Lock unfairLock = new ReentrantLock(false);   // 非公平锁
   
    @Test
    public void comparePerformance() {
      // 测试结果通常显示:
      // - 公平锁:保证顺序,但性能较低
      // - 非公平锁:可能饥饿,但吞吐量高
      
      testLock("公平锁", fairLock);
      testLock("非公平锁", unfairLock);
    }
   
    private void testLock(String type, Lock lock) {
      long start = System.currentTimeMillis();
      
      // 多个线程竞争锁...
      
      long duration = System.currentTimeMillis() - start;
      System.out.println(type + " 耗时: " + duration + "ms");
    }
}6. Condition接口:精准的线程协调器

6.1 Condition vs Object监视器

特性Object.wait/notifyCondition.await/signal前置条件必须在synchronized内必须先获取Lock等待队列一个对象一个队列一个Lock多个Condition精确通知只能notifyAll或随机可以精确通知特定Condition超时控制有限支持丰富的时间控制方法6.2 Condition实战:有界阻塞队列

/**
* 重入锁核心实现解析
*/
public class ReentrantLockCore {
    /**
   * 非公平锁获取逻辑
   */
    final boolean nonfairTryAcquire(int acquires) {
      final Thread current = Thread.currentThread();
      int c = getState();
      
      if (c == 0) {// 锁空闲
            if (compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
      } else if (current == getExclusiveOwnerThread()) {// 重入
            int nextc = c + acquires;
            if (nextc < 0) throw new Error("超过最大锁计数");
            setState(nextc);// 增加重入计数
            return true;
      }
      return false;
    }
   
    /**
   * 释放锁逻辑
   */
    protected final boolean tryRelease(int releases) {
      int c = getState() - releases;
      if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
            
      boolean free = false;
      if (c == 0) {// 完全释放
            free = true;
            setExclusiveOwnerThread(null);
      }
      setState(c);
      return free;
    }
}8. 实战指南:如何正确使用Java并发锁

8.1 锁使用的核心原则

原则1:永远在finally块中释放锁

/**
* 基于读写锁的缓存实现
*/
public class ReadWriteCache<K, V> {
    private final Map<K, V> cache = new HashMap<>();
    private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
    private final Lock readLock = rwLock.readLock();
    private final Lock writeLock = rwLock.writeLock();
   
    /**
   * 读操作:共享锁,允许多个线程同时读
   */
    public V get(K key) {
      readLock.lock();
      try {
            String value = cache.get(key);
            // 模拟配置读取的耗时操作
            simulateProcess(1);
            return value;
      } finally {
            readLock.unlock();
      }
    }
   
    /**
   * 批量获取配置 - 读锁支持并发
   */
    public Map<String, String> getConfigs(Set<String> keys) {
      readLock.lock();
      try {
            Map<String, String> result = new HashMap<>();
            for (String key : keys) {
                result.put(key, cache.get(key));
            }
            simulateProcess(keys.size());
            return result;
      } finally {
            readLock.unlock();
      }
    }
   
    /**
   * 更新配置 - 低频操作,使用写锁
   */
    public void updateConfig(String key, String value) {
      writeLock.lock();
      try {
            // 模拟配置更新的耗时操作
            simulateProcess(10);
            cache.put(key, value);
            System.out.println("配置更新: " + key + " = " + value);
      } finally {
            writeLock.unlock();
      }
    }
   
    private void simulateProcess(int milliseconds) {
      try {
            Thread.sleep(milliseconds);
      } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
      }
    }
}原则2:避免锁嵌套,预防死锁

/**
* 读写锁状态设计的精妙之处
*/
public class ReadWriteStateDesign {
    /**
   * 32位state字段的划分:
   *
   * ┌─────────────────┬─────────────────┐
   * │   高16位      │   低16位      │
   * │   读状态      │   写状态      │
   * │   (读锁计数)    │(写锁重入数)   │
   * └─────────────────┴─────────────────┘
   */
   
    // 获取写状态(低16位)
    static int exclusiveCount(int c) {
      return c & 0x0000FFFF;
    }
   
    // 获取读状态(高16位)
    static int sharedCount(int c) {
      return c >>> 16;
    }
   
    // 读锁计数+1
    int newReadState = currentState + (1 << 16);// 即 + 0x00010000
   
    // 写锁计数+1
    int newWriteState = currentState + 1;
}8.2 各组件最佳实践案例

8.2.1 ReentrantLock最佳实践:连接池管理

/**
* 锁降级示例:写锁 → 读锁
* 目的:保证数据的可见性
*/
public class LockDemotionExample {
    private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    private final Lock readLock = rwLock.readLock();
    private final Lock writeLock = rwLock.writeLock();
    private volatile boolean update = false;
    private Object data;
   
    public void processData() {
      readLock.lock();
      if (!update) {
            // 数据需要更新,必须先释放读锁
            readLock.unlock();
            
            // 获取写锁
            writeLock.lock();
            try {
                // 双重检查
                if (!update) {
                  // 准备数据...
                  data = fetchData();
                  update = true;
                }
                // 关键步骤:在释放写锁前获取读锁
                readLock.lock();// 锁降级开始
            } finally {
                writeLock.unlock();// 锁降级完成,现在持有读锁
            }
      }
      
      try {
            // 使用数据(仍在读锁保护下)
            useData(data);
      } finally {
            readLock.unlock();
      }
    }
   
    // 不支持锁升级!可能产生死锁
    public void invalidLockUpgrade() {
      readLock.lock();
      try {
            // 危险操作:尝试在持有读锁时获取写锁
            // 如果其他线程也持有读锁,会产生死锁
            writeLock.lock();// 可能永远阻塞!
            try {
                // 修改数据...
            } finally {
                writeLock.unlock();
            }
      } finally {
            readLock.unlock();
      }
    }
   
    private Object fetchData() { return null; }
    private void useData(Object data) {}
}8.2.4 LockSupport最佳实践:自定义同步器

/**
* LockSupport基础使用
*/
public class LockSupportBasic {
   
    public static void main(String[] args) throws InterruptedException {
      Thread worker = new Thread(() -> {
            System.out.println("工作线程开始执行");
            System.out.println("工作线程即将被阻塞");
            
            // 阻塞当前线程(停车)
            LockSupport.park();
            
            System.out.println("工作线程被唤醒,继续执行");
      });
      
      worker.start();
      
      Thread.sleep(2000);// 主线程等待2秒
      
      System.out.println("主线程准备唤醒工作线程");
      
      // 唤醒指定线程(开车)
      LockSupport.unpark(worker);
      
      System.out.println("主线程已发送唤醒信号");
    }
}总结:正确使用并发锁的黄金法则


[*]明确性:清楚知道每个锁保护的是什么数据
[*]最小化:锁粒度尽可能小,持有时间尽可能短
[*]一致性:按照固定顺序获取多个锁
[*]可靠性:总是在finally块中释放锁
[*]可中断性:对长时间操作使用可中断的锁获取方式
[*]监控性:对关键锁进行监控和统计
Java并发包中的锁机制通过精妙的设计实现了高效、灵活的线程同步:

[*]AQS是基石:提供了同步队列管理和状态控制的基础设施
[*]Lock接口是门面:定义了丰富的锁操作API
[*]重入锁解决重入问题:支持同一线程多次获取锁
[*]读写锁优化读多写少场景:通过锁分离提升并发性能
[*]LockSupport提供底层阻塞能力:线程控制的精准工具
[*]Condition实现精确线程协调:多条件等待队列支持复杂同步逻辑
记住这个核心关系:Lock → AQS → Condition → LockSupport,它们共同构成了Java并发锁机制的完整体系。理解这些组件的内部机制,能够帮助我们正确选择和使用合适的同步工具,诊断和解决复杂的并发问题,设计高性能的并发组件。
记住这些原则和最佳实践,你就能构建出高效、可靠的并发程序。并发编程虽然复杂,但通过正确的工具和方法,完全可以驾驭!

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

蟠鲤 发表于 2025-10-30 09:43:42

新版吗?好像是停更了吧。

胆饬 发表于 2025-11-7 01:44:36

谢谢分享,辛苦了

马璞玉 发表于 2025-12-4 02:34:55

谢谢分享,试用一下

醋辛 发表于 2025-12-11 06:03:08

用心讨论,共获提升!
页: [1]
查看完整版本: Java并发编程之Lock锁机制深度解析:从使用到源码实现