锁原理——java与mysql

一、MySQL锁类型全景图

1. 按锁粒度划分
锁类型作用范围冲突概率性能影响
表锁整张表
行锁单行记录
页锁数据页(B+树节点)
2. 按锁模式划分
  • 共享锁(S锁)‌:SELECT ... LOCK IN SHARE MODE
  • 排他锁(X锁)‌:SELECT ... FOR UPDATE
  • 意向锁‌:快速判断表级冲突(IS/IX锁)

二、InnoDB锁实现原理

1. 行锁底层机制
  • 记录锁(Record Lock)‌:锁定索引记录

    -- 对id=1的记录加X锁 SELECT * FROM users WHERE id = 1 FOR UPDATE;

  • 间隙锁(Gap Lock)‌:锁定索引区间

    -- 锁定(5,10)区间防止插入 SELECT * FROM users WHERE id > 5 AND id < 10 FOR UPDATE;

  • 临键锁(Next-Key Lock)‌:记录锁+间隙锁组合
2. 锁升级条件
  • 单事务锁定超过innodb_lock_wait_timeout(默认50s)
  • 锁数量超过innodb_buffer_pool_size的5%

三、实战应用场景

场景1:高并发订单处理

-- 1. 开启事务 START TRANSACTION; -- 2. 检查库存并加锁(防止超卖) SELECT stock FROM products WHERE id = 1001 FOR UPDATE; -- 3. 业务逻辑判断 IF stock > 0 THEN UPDATE products SET stock = stock - 1 WHERE id = 1001; INSERT INTO orders(...) VALUES(...); END IF; -- 4. 提交释放锁 COMMIT;

场景2:批量更新防死锁

-- 按固定顺序加锁(如主键升序) SELECT * FROM accounts WHERE id IN (3,1,2) ORDER BY id ASC FOR UPDATE;

场景3:读写分离优化

-- 使用S锁允许并发读 SELECT * FROM articles WHERE id = 5 LOCK IN SHARE MODE;


四、锁问题诊断工具

1. 查看锁状态

SHOW ENGINE INNODB STATUS; -- 查看锁等待 SELECT * FROM performance_schema.events_waits_current; -- 当前等待事件

2. 死锁分析

-- 开启死锁日志 SET GLOBAL innodb_print_all_deadlocks = ON;

3. 锁等待超时设置

SET SESSION innodb_lock_wait_timeout = 30; -- 单位:秒


五、高级锁技巧

  1. 悲观锁‌:FOR UPDATE + 短事务
  2. 乐观锁‌:版本号+重试机制

    UPDATE products SET stock = stock - 1, version = version + 1 WHERE id = 1001 AND version = 2;

  3. 间隙锁规避‌:使用READ COMMITTED隔离级别

六、性能优化建议

  1. 索引设计‌:确保锁能命中索引(否则退化为表锁)
  2. 事务拆分‌:大事务拆分为小事务
  3. 隔离级别‌:根据业务选择READ COMMITTEDREPEATABLE READ

一、乐观锁核心思想

核心原则‌:假设并发冲突概率低,操作时不加锁,提交时检测数据是否被修改
类比场景‌:多人协作编辑文档,保存时提示"文件已被他人修改,请合并变更"


二、实现原理

1. 版本号机制

public class Data { private int value; private int version; // 版本号字段 // getter/setter... }

工作流程‌:

  1. 读取数据时获取当前版本号(如version=1)
  2. 修改数据前检查版本号是否变化
  3. 提交时执行原子性更新:

    UPDATE table SET value=newValue, version=version+1 WHERE id=recordId AND version=oldVersion

2. CAS(Compare-And-Swap)

AtomicInteger atomicInt = new AtomicInteger(0); boolean success = atomicInt.compareAndSet(0, 1); // 预期值0→新值1

硬件支持‌:通过CPU指令(如x86的CMPXCHG)实现原子操作


三、具体实现步骤

1. 数据库乐观锁(MyBatis示例)

// 1. 实体类添加版本字段 public class Product { private Long id; private String name; private Integer stock; private Integer version; // 乐观锁版本 } // 2. Mapper更新语句 @Update("UPDATE product SET stock=#{stock}, version=version+1 WHERE id=#{id} AND version=#{version}") int updateWithVersion(Product product); // 3. 业务逻辑 public void deductStock(Long productId) { Product product = productMapper.selectById(productId); product.setStock(product.getStock() - 1); int rows = productMapper.updateWithVersion(product); if (rows == 0) { throw new OptimisticLockException("库存更新冲突"); } }

2. Java并发工具实现

// 使用AtomicStampedReference解决ABA问题 AtomicStampedReference<Integer> atomicRef = new AtomicStampedReference<>(100, 0); // 更新操作 int[] stampHolder = new int[1]; int oldValue = atomicRef.get(stampHolder); if (!atomicRef.compareAndSet(oldValue, oldValue - 10, stampHolder[0], stampHolder[0] + 1)) { System.out.println("操作失败,值已被修改"); }


四、典型应用场景

场景实现方式优势
电商库存扣减数据库版本号避免超卖
计数器累加AtomicLong无锁高性能
状态机变更CAS+自旋避免死锁
分布式系统Redis WATCH/MULTI跨进程协调

五、与悲观锁的对比

特性乐观锁悲观锁
并发假设冲突概率低冲突概率高
加锁时机提交时检测操作前加锁
实现复杂度需处理重试逻辑直接阻塞
适用场景读多写少写多读少
典型实现CAS/版本号synchronized

六、注意事项

  1. ABA问题‌:

    • 现象:值从A→B→A,CAS无法感知中间变化
    • 解决方案:使用AtomicStampedReference带版本戳
  2. 自旋开销‌:

    while (!atomicRef.compareAndSet(...)) { // 可添加退避策略 Thread.yield(); }

  3. 适用边界‌:

    • 冲突率>20%时性能急剧下降
    • 长时间计算操作不适合(易导致频繁重试)

synchronized工作原理深度解析

1. 基础概念与使用方式

synchronized是Java中最基本的线程同步机制,用于解决多线程环境下的共享资源竞争问题。它主要有三种使用方式:

  1. 实例方法同步‌:锁住当前对象实例

    javaCopy Code

    public synchronized void method() { // 同步代码 }

  2. 静态方法同步‌:锁住当前类的Class对象

    javaCopy Code

    public static synchronized void staticMethod() { // 同步代码 }

  3. 同步代码块‌:可以指定锁对象

    javaCopy Code

    public void method() { synchronized(lockObject) { // 同步代码 } }

2. 底层实现机制

2.1 对象头与Mark Word

每个Java对象在内存中由三部分组成:

  1. 对象头 (Header)

  2. 实例数据 (Instance Data)

  3. 对齐填充 (Padding)

其中对象头包含Mark Word和Klass Pointer:

  • Mark Word‌ (32/64位):存储对象的hashCode、GC分代年龄、锁状态等信息

  • Klass Pointer‌:指向对象类型数据的指针

Mark Word在不同锁状态下的结构:

锁状态

存储内容

标志位

无锁

对象hashCode + 分代年龄

001

偏向锁

持有线程ID + Epoch + 分代年龄

101

轻量级锁

指向栈中锁记录的指针

00

重量级锁

指向监视器(Monitor)的指针

10

2.2 监视器(Monitor)机制

每个Java对象都与一个Monitor相关联,Monitor主要包含以下组件:

  • Owner‌:持有锁的线程

  • Entry Set‌:等待获取锁的线程集合

  • Wait Set‌:调用wait()后进入等待状态的线程集合

3. 锁升级过程

JVM为了优化同步性能,设计了锁升级机制:

3.1 偏向锁 (Biased Locking)

  • 适用场景‌:只有一个线程访问同步块

  • 实现原理‌:在Mark Word中记录线程ID

  • 优点‌:无竞争时几乎无开销

  • 触发条件‌:首次获取锁时

3.2 轻量级锁 (Lightweight Locking)

  • 适用场景‌:多线程交替执行,无真正竞争

  • 实现原理‌:

    1. 在当前线程栈帧中创建锁记录(Lock Record)

    2. 通过CAS将Mark Word复制到锁记录

    3. 尝试用CAS将Mark Word替换为指向锁记录的指针

  • 优点‌:避免线程阻塞切换

  • 触发条件‌:发现偏向锁的线程ID不匹配时

3.3 重量级锁 (Heavyweight Locking)

  • 适用场景‌:多线程激烈竞争

  • 实现原理‌:

    1. 向操作系统申请互斥量(mutex)

    2. 竞争失败的线程进入阻塞状态

  • 缺点‌:涉及用户态到内核态的切换,开销大

  • 触发条件‌:轻量级锁自旋失败后

4. 字节码层面实现

同步代码块编译后会生成monitorentermonitorexit指令:

public void syncMethod() { synchronized(this) { System.out.println("Hello"); } }

编译后的字节码:

0: aload_0 // 加载this引用 1: dup // 复制栈顶值 2: astore_1 // 存储到局部变量1 3: monitorenter // 进入监视器 4: getstatic #2 // 获取System.out 7: ldc #3 // 加载"Hello" 9: invokevirtual #4// 调用println 12: aload_1 // 加载监视器对象 13: monitorexit // 正常退出监视器 14: goto 22 // 跳转到方法结束 17: astore_2 // 异常处理开始 18: aload_1 // 加载监视器对象 19: monitorexit // 异常退出监视器 20: aload_2 // 加载异常对象 21: athrow // 抛出异常 22: return // 方法返回

5. 性能优化技术

5.1 自旋锁 (Spin Lock)

  • 线程不立即阻塞,而是循环尝试获取锁

  • JDK1.6引入自适应自旋:根据上次成功获取锁的自旋时间动态调整

5.2 锁消除 (Lock Elimination)

  • JIT编译器通过逃逸分析,发现同步块不可能被共享时,会消除锁

  • 示例:

    public String concat(String s1, String s2) { StringBuffer sb = new StringBuffer(); // 局部变量,不会逃逸 sb.append(s1).append(s2); return sb.toString(); }

5.3 锁粗化 (Lock Coarsening)

  • 将连续的多个同步块合并为一个更大的同步块

  • 减少频繁获取/释放锁的开销

6. 与ReentrantLock对比

特性

synchronized

ReentrantLock

实现方式

JVM内置

Java代码实现

可中断

不支持

支持

公平锁

非公平

可配置

条件变量

单一

多个

锁绑定多个条件

不支持

支持

性能

JDK6后优化好

高竞争时更好

代码复杂度

简单

需要显式释放

7. 最佳实践

  1. 锁粒度‌:尽量减小同步代码块的范围

  2. 锁对象‌:使用专门的对象而非业务对象

  3. 避免嵌套‌:防止死锁发生

  4. 性能监控‌:关注锁竞争情况(JConsole/VisualVM)

  5. 升级选择‌:高竞争场景考虑ReentrantLock

8. 常见问题

Q1: 为什么synchronized是可重入的?
A: 监视器记录锁的持有线程和进入次数,同一线程可重复获取

Q2: synchronized和volatile的区别?
A: volatile保证可见性和有序性,但不保证原子性;synchronized三者都保证

Q3: 偏向锁为什么需要撤销?
A: 当检测到有多个线程竞争时,需要撤销偏向锁升级为更高级别的锁

ReentrantLock 原理深度解析

1. 基本概念与核心特性

ReentrantLock是Java并发包(java.util.concurrent.locks)中提供的可重入互斥锁实现,相比synchronized具有更丰富的功能:

  • 可重入性‌:同一线程可以多次获取同一把锁

  • 公平性选择‌:支持公平锁和非公平锁两种模式

  • 可中断‌:支持获取锁时响应中断

  • 超时机制‌:可以尝试在指定时间内获取锁

  • 条件变量‌:支持多个条件队列

2. 核心实现原理

2.1 AQS(AbstractQueuedSynchronizer)基础

ReentrantLock的核心实现依赖于AQS框架,AQS维护了一个volatile int state变量和一个FIFO线程等待队列:

javaCopy Code

// AQS中的关键字段 private volatile int state; // 锁状态 private transient volatile Node head; // 队列头 private transient volatile Node tail; // 队列尾

2.2 锁状态管理

ReentrantLock通过AQS的state字段记录锁状态:

  • state = 0:锁未被占用

  • state > 0:锁被占用,数值表示重入次数

2.3 公平锁与非公平锁

ReentrantLock内部有两个实现类:

  • NonfairSync‌:非公平锁实现(默认)

  • FairSync‌:公平锁实现

关键区别在于获取锁时的行为:

javaCopy Code

// 非公平锁尝试获取 final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (compareAndSetState(0, acquires)) { // 直接尝试CAS setExclusiveOwnerThread(current); return true; } } // ...重入处理 } // 公平锁尝试获取 protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (!hasQueuedPredecessors() && // 检查队列中是否有等待线程 compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } // ...重入处理 }

3. 关键方法实现

3.1 lock() 方法流程

  1. 尝试通过CAS修改state获取锁

  2. 成功则设置当前线程为独占线程

  3. 失败则调用AQS.acquire()加入等待队列

3.2 unlock() 方法流程

  1. 减少state值

  2. 如果state变为0,释放锁并唤醒等待队列中的线程

4. 条件变量实现

ReentrantLock通过ConditionObject实现条件变量:

javaCopy Code

public Condition newCondition() { return sync.newCondition(); }

每个Condition维护一个独立的条件等待队列,await()和signal()操作涉及:

  • 将线程从锁队列转移到条件队列

  • 从条件队列移回锁队列

5. 与synchronized的性能对比

特性

ReentrantLock

synchronized

实现方式

Java代码实现

JVM内置

锁获取方式

显示调用lock()/unlock()

隐式获取/释放

可中断

支持

不支持

公平性

可配置

非公平

条件变量

支持多个

单一

性能

高竞争时更好

JDK6后优化良好

6. 最佳实践

  1. 必须释放锁‌:在finally块中调用unlock()

  2. 避免嵌套‌:防止死锁

  3. 合理选择公平性‌:公平锁适合保证顺序但性能较低

  4. 考虑使用tryLock‌:避免长时间阻塞

  5. 条件变量使用‌:复杂等待/通知场景

7. 典型使用示例
 

ReentrantLock lock = new ReentrantLock();

Condition condition = lock.newCondition();

lock.lock(); // 使用tryLock‌:避免长时间阻塞

try {

while (!conditionMet) {

condition.await();

}

// 执行临界区代码

} finally {

lock.unlock();

}




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

public class ProducerConsumerExample {
    private final Queue<Integer> queue = new LinkedList<>();
    private final int CAPACITY = 5;
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition notFull = lock.newCondition();
    private final Condition notEmpty = lock.newCondition();

    public static void main(String[] args) {
        ProducerConsumerExample example = new ProducerConsumerExample();
        
        Thread producer = new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    example.produce(i);
                    Thread.sleep(200);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        Thread consumer = new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    example.consume();
                    Thread.sleep(500);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        producer.start();
        consumer.start();
    }

    public void produce(int value) throws InterruptedException {
        lock.lock();
        try {
            while (queue.size() == CAPACITY) {
                System.out.println("队列已满,生产者等待...");
                notFull.await();
            }
            queue.add(value);
            System.out.println("生产: " + value + " 队列大小: " + queue.size());
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }

    public void consume() throws InterruptedException {
        lock.lock();
        try {
            while (queue.isEmpty()) {
                System.out.println("队列为空,消费者等待...");
                notEmpty.await();
            }
            int value = queue.poll();
            System.out.println("消费: " + value + " 队列大小: " + queue.size());
            notFull.signal();
        } finally {
            lock.unlock();
        }
    }
}

8. 常见问题解答

Q1: 为什么ReentrantLock是可重入的?
A: 通过记录当前持有线程和重入次数实现,每次重入state值增加1

Q2: 公平锁和非公平锁如何选择?
A: 公平锁保证FIFO顺序但吞吐量低,非公平锁可能插队但性能更高

Q3: ReentrantLock与synchronized在JVM层面有何区别?
A: synchronized依赖JVM的monitor机制,ReentrantLock完全用Java代码实现基于AQS

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值