Java 线程同步-04:lock 机制

前言

Java的Lock机制是Java并发编程(JDK 1.5+)中用于控制多个线程访问共享资源的核心工具。它位于java.util.concurrent.locks包下,提供了比传统的synchronized关键字更灵活、更强大的锁定操作。

本文主要介绍以 ReentrantLock 和 ReentrantReadWriteLock 为代表的lock机制,文章内容包含一下几个:Java Lock 类继承关系、Lock 使用代码示例、Lock 原理。

类结构

Java的lock机制源头可以从Lock接口说起,这是lock机制的抽象类,规定了锁的相关协议,它定义了锁的获取和释放方法。相比于synchronized(隐式获取/释放锁),Lock需要显式地获取和释放锁。

Lock接口核心方法:

public interface Lock { // 1. 基本锁操作 void lock(); void unlock(); // 2. 可中断锁 void lockInterruptibly() throws InterruptedException; // 3. 尝试获取锁 boolean tryLock(); boolean tryLock(long time, TimeUnit unit) throws InterruptedException; // 4. 条件变量 Condition newCondition(); }

Java同步机制里面涉及的接口层主要有下面三个核心接口:

  1. Lock 接口 - 所有锁的顶级接口,定义了 lock()、unlock() 等基本方法
  2. ReadWriteLock 接口 - 读写锁接口,定义了读锁和写锁的获取方法
  3. Condition 接口 - 条件变量接口,用于线程间的等待/通知机制

对上面几个接口类常用的主要有几个实现类:

  1. ReentrantLock:实现了 Lock 接口内部包含 Sync 同步器
  2. ReentrantReadWriteLock:实现了 ReadWriteLock 接口 内部包含 Sync、ReadLock、WriteLock几个内部类
  3. ConditionObject:实现了 Condition 接口,是 AQS 的内部类

简化版继承结构类图UML:

Lock接口

ReentrantLock

ReadLock

WriteLock

ReadWriteLock接口

ReentrantReadWriteLock

Condition接口

ConditionObject

AQS

Sync

ReadWriteSync

FairSync

NonfairSync

详细版继承结构类图UML:

使用方式

ReentrantLock 使用示例

package concurrent; import java.util.concurrent.*; import java.util.concurrent.locks.*; public class ReentrantLockDemo { private final ReentrantLock lock = new ReentrantLock(); private int counter = 0; // 1. 基本 lock() 和 unlock() public void basicLock() { lock.lock(); try { counter++; System.out.println(Thread.currentThread().getName() + " [basicLock] 计数器: " + counter); Thread.sleep(100); // 模拟工作 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { lock.unlock(); } } /** 持锁一段时间,用于配合 lockInterruptibly 演示 */ public void holdLock(long millis) { lock.lock(); try { System.out.println(Thread.currentThread().getName() + " 持有锁 " + millis + "ms"); Thread.sleep(millis); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { lock.unlock(); System.out.println(Thread.currentThread().getName() + " 释放锁"); } } // 2. tryLock(timeout) - 超时尝试获取锁 public void tryLockWithTimeout() { try { if (lock.tryLock(500, TimeUnit.MILLISECONDS)) { try { System.out.println(Thread.currentThread().getName() + " [tryLockTimeout] 在500ms内获取成功"); Thread.sleep(200); // 模拟工作 } finally { lock.unlock(); } } else { System.out.println(Thread.currentThread().getName() + " [tryLockTimeout] 获取超时"); } } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName() + " [tryLockTimeout] 被中断"); Thread.currentThread().interrupt(); } } // 3. lockInterruptibly() - 可中断锁(在等待锁的过程中可被 interrupt 并抛出 InterruptedException) public void lockInterruptiblyExample() throws InterruptedException { System.out.println(Thread.currentThread().getName() + " 尝试 lockInterruptibly(),若锁被占用将阻塞等待..."); lock.lockInterruptibly(); // 阻塞等待期间若被 interrupt,会抛出 InterruptedException try { System.out.println(Thread.currentThread().getName() + " 获取锁成功,执行业务"); Thread.sleep(500); } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); System.out.println(Thread.currentThread().getName() + " 释放锁"); } } } // 运行 ReentrantLock 所有示例 public static void main(String[] args) throws Exception { System.out.println("\n========== ReentrantLock 示例开始 =========="); ReentrantLockDemo reentrantDemo = new ReentrantLockDemo(); // 1. 基本 lock() 示例 System.out.println("\n1. 基本 lock() 示例:"); Thread t1 = new Thread(() -> reentrantDemo.basicLock(), "Thread-1"); Thread t2 = new Thread(() -> reentrantDemo.basicLock(), "Thread-2"); t1.start(); t2.start(); t1.join(); t2.join(); // 2. tryLock(timeout) 示例 System.out.println("\n2. tryLock(timeout) 示例:"); Thread t5 = new Thread(() -> reentrantDemo.tryLockWithTimeout(), "Thread-5"); Thread t6 = new Thread(() -> reentrantDemo.tryLockWithTimeout(), "Thread-6"); t5.start(); t6.start(); t5.join(); t6.join(); // 3. lockInterruptibly() 示例:先让一个线程持锁,另一个线程在 lockInterruptibly() 上阻塞,再中断阻塞线程 System.out.println("\n3. lockInterruptibly() 示例(在等待锁时被中断):"); Thread holder = new Thread(() -> reentrantDemo.holdLock(5000), "Holder"); Thread interruptible = new Thread(() -> { try { reentrantDemo.lockInterruptiblyExample(); System.out.println("Interruptible-Thread 正常结束"); } catch (InterruptedException e) { System.out.println("Interruptible-Thread 在等待锁时被中断,抛出 InterruptedException"); Thread.currentThread().interrupt(); } }, "Interruptible-Thread"); holder.start(); Thread.sleep(100); // 确保 Holder 先拿到锁 interruptible.start(); Thread.sleep(800); // 此时 Interruptible 正在 lockInterruptibly() 上阻塞 interruptible.interrupt(); // 中断正在等待锁的线程 interruptible.join(); holder.join(); System.out.println("\n========== ReentrantLock 示例结束 =========="); } }

代码运行结果:

========== ReentrantLock 示例开始 ========== 1. 基本 lock() 示例: Thread-1 [basicLock] 计数器: 1 Thread-2 [basicLock] 计数器: 2 2. tryLock(timeout) 示例: Thread-5 [tryLockTimeout] 在500ms内获取成功 Thread-6 [tryLockTimeout] 在500ms内获取成功 3. lockInterruptibly() 示例(在等待锁时被中断): Holder 持有锁 5000ms Interruptible-Thread 尝试 lockInterruptibly(),若锁被占用将阻塞等待... Interruptible-Thread 在等待锁时被中断,抛出 InterruptedException Holder 释放锁 ========== ReentrantLock 示例结束 ==========

ReentrantLock 使用示例

package concurrent; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * ReentrantReadWriteLock 使用示例: * - 读锁(readLock):共享,多线程可同时持有,与写锁互斥 * - 写锁(writeLock):独占,同一时刻仅一个线程可持有,与读锁、写锁均互斥 */ public class ReentrantReadWriteLockDemo { private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); private final ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock(); private final ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock(); private String data = "initial"; // 1. 读锁:多个线程可同时读 public void readWithReadLock() { System.out.println(Thread.currentThread().getName() + " 尝试获取读锁"); readLock.lock(); // 若写锁被占用,会在此阻塞 try { System.out.println(Thread.currentThread().getName() + " 获取到读锁(写锁已释放后才会执行到这里)"); System.out.println(Thread.currentThread().getName() + " [读锁] 读取: " + data); Thread.sleep(200); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { readLock.unlock(); } } // 2. 写锁:独占,同一时刻只有一个线程可写 public void writeWithWriteLock(String newData) { writeLock.lock(); try { System.out.println(Thread.currentThread().getName() + " [写锁] 写入: " + newData); data = newData; Thread.sleep(300); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { writeLock.unlock(); } } // 3. 持写锁一段时间,用于演示读/写互斥 public void holdWriteLock(long millis) { writeLock.lock(); try { System.out.println(Thread.currentThread().getName() + " 持有写锁 " + millis + "ms(此时读锁会阻塞)"); Thread.sleep(millis); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { // 先打印再 unlock,保证控制台顺序能体现:先释放写锁,后读者才拿到读锁 System.out.println(Thread.currentThread().getName() + " 释放写锁"); writeLock.unlock(); } } public static void main(String[] args) throws InterruptedException { ReentrantReadWriteLockDemo demo = new ReentrantReadWriteLockDemo(); System.out.println("========== 1. 读锁共享:多个线程同时读 =========="); Thread r1 = new Thread(() -> demo.readWithReadLock(), "Reader-1"); Thread r2 = new Thread(() -> demo.readWithReadLock(), "Reader-2"); Thread r3 = new Thread(() -> demo.readWithReadLock(), "Reader-3"); r1.start(); r2.start(); r3.start(); r1.join(); r2.join(); r3.join(); System.out.println("\n========== 2. 写锁独占:同一时刻只有一个写 =========="); Thread w1 = new Thread(() -> demo.writeWithWriteLock("A"), "Writer-1"); Thread w2 = new Thread(() -> demo.writeWithWriteLock("B"), "Writer-2"); w1.start(); w2.start(); w1.join(); w2.join(); System.out.println("\n========== 3. 读与写互斥:写锁持有时,读锁阻塞 =========="); Thread holder = new Thread(() -> demo.holdWriteLock(2000), "Writer-Holder"); Thread reader = new Thread(() -> demo.readWithReadLock(), "Reader-Blocked"); holder.start(); Thread.sleep(100); reader.start(); holder.join(); reader.join(); System.out.println("\n========== ReentrantReadWriteLock 示例结束 =========="); } }

代码运行结果:

========== 1. 读锁共享:多个线程同时读 ========== Reader-1 尝试获取读锁 Reader-2 尝试获取读锁 Reader-3 尝试获取读锁 Reader-1 获取到读锁(写锁已释放后才会执行到这里) Reader-2 获取到读锁(写锁已释放后才会执行到这里) Reader-3 获取到读锁(写锁已释放后才会执行到这里) Reader-1 [读锁] 读取: initial Reader-3 [读锁] 读取: initial Reader-2 [读锁] 读取: initial ========== 2. 写锁独占:同一时刻只有一个写 ========== Writer-1 [写锁] 写入: A Writer-2 [写锁] 写入: B ========== 3. 读与写互斥:写锁持有时,读锁阻塞 ========== Writer-Holder 持有写锁 2000ms(此时读锁会阻塞) Reader-Blocked 尝试获取读锁 Writer-Holder 释放写锁 Reader-Blocked 获取到读锁(写锁已释放后才会执行到这里) Reader-Blocked [读锁] 读取: B ========== ReentrantReadWriteLock 示例结束 ==========

Lock 原理

Java lock机制使用AQS来管理锁的状态,进而管理同步锁的获取和释放。

使用AQS管理锁状态

AQS (AbstractQueuedSynchronizer) 是一个抽象接口,核心代码如下:

// AQS 简化实现原理 public abstract class AbstractQueuedSynchronizer { // 核心字段1: 同步状态(32位int) private volatile int state; // 核心字段2: CLH队列(双向链表) private transient volatile Node head; // 队列头 private transient volatile Node tail; // 队列尾 // 队列节点 static final class Node { volatile Node prev; // 前驱节点 volatile Node next; // 后继节点 volatile Thread thread; // 等待的线程 volatile int waitStatus; // 等待状态 Node nextWaiter; // 条件队列使用 } // 核心方法:CAS操作 protected final boolean compareAndSetState(int expect, int update) { return unsafe.compareAndSwapInt(this, stateOffset, expect, update); } }

ReentrantLock 使用 AQS 的 state 字段表示锁状态,类内部存在一个Sync内部类:

// ReentrantLock 使用 AQS 的 state 字段表示锁状态 public class ReentrantLock { abstract static class Sync extends AbstractQueuedSynchronizer { // state 含义: // 0: 无锁状态 // 1: 有线程持有锁(非重入) // N: 同一个线程重入了 N-1 次 } }

基于lock获取同步锁时,会经历以下流程:

成功

失败

线程尝试获取锁

是否持有锁?

重入计数+1

state是否为0?

公平锁?

有前驱节点?

CAS获取锁

入队等待

直接CAS尝试

获取成功

获取成功

进入CLH队列

自旋或挂起

被唤醒后尝试

这里内部类Sync实现主要有公平锁和非公平锁两种实现,本文不展开深入,可看下篇文章。另外,这里涉及到的CLH 队列是一种自旋锁的等待队列实现,全称为 Craig, Landin, and Hagersten queue,是 AQS(AbstractQueuedSynchronizer)中实现线程排队等待的核心数据结构。

读写锁获取的实现

读写锁这里用到了state字段做了些状态设计:

static class Sync extends AbstractQueuedSynchronizer { // 使用 state 的高16位表示读锁数量,低16位表示写锁重入次数 // state = (读锁数量 << 16) | 写锁重入次数 static final int SHARED_SHIFT = 16; static final int SHARED_UNIT = (1 << SHARED_SHIFT); // 读锁单位: 65536 static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1; // 最大计数: 65535 static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; // 写锁掩码: 0xFFFF // 获取读锁数量 static int sharedCount(int c) { return c >>> SHARED_SHIFT; } // 获取写锁重入次数 static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; } }

写锁获取的核心方法如下:

// WriteLock 的 tryAcquire 方法 protected final boolean tryAcquire(int acquires) { Thread current = Thread.currentThread(); int c = getState(); int w = exclusiveCount(c); // 获取写锁数量 if (c != 0) { // 有锁被持有 // 情况1: 有读锁 (w == 0 但 c != 0) // 情况2: 有写锁但不是当前线程持有 (w != 0 && 持有者 != current) if (w == 0 || current != getExclusiveOwnerThread()) return false; // 获取失败 // 情况3: 当前线程持有写锁(重入) if (w + exclusiveCount(acquires) > MAX_COUNT) throw new Error("Maximum lock count exceeded"); setState(c + acquires); return true; } // c == 0 无锁状态 if (writerShouldBlock() || // 公平性检查 !compareAndSetState(c, c + acquires)) return false; setExclusiveOwnerThread(current); return true; }

读锁获取核心方法如下:

// ReadLock 的 tryAcquireShared 方法 protected final int tryAcquireShared(int unused) { Thread current = Thread.currentThread(); int c = getState(); // 如果有写锁,并且不是当前线程持有的 if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) return -1; // 获取失败 int r = sharedCount(c); // 当前读锁数量 // 检查是否应该阻塞(公平性检查) if (!readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) { // 第一个读锁 if (r == 0) { firstReader = current; firstReaderHoldCount = 1; } // 当前线程是第一个读锁持有者 else if (firstReader == current) { firstReaderHoldCount++; } // 其他线程持有读锁 else { HoldCounter rh = cachedHoldCounter; if (rh == null || rh.tid != current.getId()) cachedHoldCounter = rh = readHolds.get(); else if (rh.count == 0) readHolds.set(rh); rh.count++; } return 1; // 获取成功 } // CAS失败或应该阻塞,进入完整获取流程 return fullTryAcquireShared(current); }

Read more

Mac 扩展坞(Dock)总是跑到副屏?一个非常有效的解决方法记录

Mac 扩展坞(Dock)总是跑到副屏?一个非常有效的解决方法记录

在使用 Mac + 外接显示器(扩展坞 / 副屏) 的过程中,很多人都会遇到一个让人非常抓狂的问题:Dock(程序坞)莫名其妙跑到副屏上了,而且怎么挪都不太对。 尤其是当你把 主屏 用来工作、而副屏只是辅助显示时,这种行为会极大影响效率。 最近我也遇到了这个问题,为了找到原因和解决方法,特地去检索了一下资料,发现有用户提出了一种看似很简单、但实际非常有效的方法。 亲测之后,确实可行,因此记录下来,供以后快速参考。 一、问题现象 * 典型表现包括: * Dock 出现在 副屏底部,而不是主屏 * 系统设置里已经选好了“主显示器”,但 Dock 依旧不听话 * 拖动鼠标、调整排列,效果不可控 * 本质原因在于:👉 macOS 会根据“鼠标最近触发 Dock 的屏幕”来动态决定 Dock 显示在哪一块屏幕上。也就是说,

Flutter 组件 assertable_json 的适配 鸿蒙Harmony 实战 - 驾驭结构化 JSON 断言、实现鸿蒙端 API 回包自动化审计与零容错数据校验方案

Flutter 组件 assertable_json 的适配 鸿蒙Harmony 实战 - 驾驭结构化 JSON 断言、实现鸿蒙端 API 回包自动化审计与零容错数据校验方案

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 assertable_json 的适配 鸿蒙Harmony 实战 - 驾驭结构化 JSON 断言、实现鸿蒙端 API 回包自动化审计与零容错数据校验方案 前言 在鸿蒙(OpenHarmony)生态的金融级应用、大型电商后台以及涉及到敏感信息交换的政务系统中,“数据一致性”是高可用架构的最后一道防线。面对后端返回的动辄数千行、深度嵌套十余层的 JSON 数据流。如果仅仅依靠 data['user']['info']['id'] != null 这种脆弱的手动判空。那么不仅会导致代码中充斥着大量的噪声片段。更会因为某个微小的字段缺失(如:金额字段 amount 变为了 null)

Flutter 三方库 jaguar 的鸿蒙化适配指南 - 在鸿蒙系统上构建极致、透明、全能的工业级嵌入式 HTTP 服务端框架与 REST API 交互引擎

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 jaguar 的鸿蒙化适配指南 - 在鸿蒙系统上构建极致、透明、全能的工业级嵌入式 HTTP 服务端框架与 REST API 交互引擎 在鸿蒙(OpenHarmony)系统的端侧服务器化、分布式设备互联监控、或者是需要将鸿蒙应用转变为一个能够提供 API 服务的微型网关(如鸿蒙版物联网中枢)场景中,如何通过一套 Dart 代码构建出极致稳健、带路由拦截、支持 Session 且完全透明的 HTTP 服务?jaguar 为开发者提供了一套工业级的、基于生产环境优化的服务端处理方案。本文将深入实战其在鸿蒙端侧服务化中的应用。 前言 什么是 Jaguar?它不是一个普通的 HTTP 监听器,而是一个专为“速度”与“扩展性”

鸿蒙金融理财全栈项目——性能优化与安全加固

鸿蒙金融理财全栈项目——性能优化与安全加固

《鸿蒙APP开发从入门到精通》第28篇:鸿蒙金融理财全栈项目——性能优化与安全加固 🔧🛡️📈 内容承接与核心价值 这是《鸿蒙APP开发从入门到精通》的第28篇——性能优化与安全加固篇,100%承接第27篇的生态合作与用户运营优化架构,并基于金融场景的性能优化与安全加固要求,设计并实现鸿蒙金融理财全栈项目的性能优化与安全加固功能。 学习目标: * 掌握鸿蒙金融理财项目的性能优化设计与实现; * 实现应用启动优化、页面加载优化、内存管理优化; * 理解安全加固在金融场景的核心设计与实现; * 实现应用混淆、代码加密、防调试; * 掌握性能优化与安全加固的协同优化策略; * 优化金融理财项目的用户体验与安全防护能力。 学习重点: * 鸿蒙金融理财项目的性能优化设计原则; * 安全加固在金融场景的应用; * 性能优化与安全加固的协同优化策略。 一、 性能优化基础 🎯 1.1 性能优化定义 性能优化是指对金融理财项目的性能进行优化,提升应用的运行效率,主要包括以下方面: * 应用启动优化:优化应用启动的速度; * 页面加载优化:优化页面加载的