当前位置: 首页 > article >正文

(二十三)Java-synchronized

  synchronized关键字作为Java实现线程同步的基础工具。自JDK 1.0引入以来,经过多次优化(特别是Java 6的锁升级机制),使它已成为高效且易用的同步解决方案。本文将深入剖析synchronized的实现原理、使用方式及最佳实践。

一、基本用法

1.同步实例方法

锁对象:当前实例对象(this)

作用范围:整个方法体

public synchronized void increment() {

    // 临界区代码

}

2.同步静态方法

锁对象:类的Class对象(ClassName.class)

作用范围:整个静态方法

public static synchronized void staticIncrement() {

    // 临界区代码

}

3.同步代码块

显式指定锁对象

细粒度控制同步范围

public void doSomething() {

    // 非同步代码

    synchronized(lockObject) {

        // 临界区代码

    }

    // 非同步代码

}

二、底层实现原理

1.对象头结构

Mark Word存储内容随状态变化:

|----------------------------------------------------    |

| 锁状态    | 存储内容                                  |

|----------------------------------------------------    |

| 无锁        | 对象哈希码、分代年龄等         |

| 偏向锁    | 线程ID、Epoch、分代年龄等  |

| 轻量级锁 | 指向栈中锁记录的指针           |

| 重量级锁 | 指向Monitor的指针                 |

|----------------------------------------------------   |

2.Monitor机制

每个Java对象关联一个Monitor,包含:

_owner:持有锁的线程;

_EntryList:阻塞中的线程队列;

_WaitSet:调用wait()的线程队列;

_recursions:重入次数计数器。

3.字节码层面

通过monitorenter和monitorexit指令实现:

void test() {

    synchronized(obj) {

        // code

    }

}

// 对应字节码:

0: aload_1

1: dup

2: astore_2

3: monitorenter   // 进入同步

4: aload_2

5: monitorexit    // 正常退出

6: goto          14

9: astore_3

10: aload_2

11: monitorexit    // 异常退出

12: aload_3

13: athrow

14: return

三、锁升级过程(Java 6+优化)

1.偏向锁(Biased Locking)

适用场景:单线程访问。

优化原理:CAS设置线程ID。

撤销条件:出现第二个线程访问。

2.轻量级锁(Lightweight Locking)

适用场景:低竞争、短临界区。

实现方式:栈帧中创建Lock Record。

膨胀条件:自旋失败(默认10次)。

3.重量级锁(Heavyweight Locking)

实现方式:操作系统mutex。

特点:线程进入内核态阻塞。

优化:自适应自旋(JDK 6+)。

锁升级路径:无锁 → 偏向锁 → 轻量级锁 → 重量级锁。

四、内存语义

可见性保证:遵循happens-before原则

内存屏障:

  monitorenter:LoadLoad + LoadStore

  monitorexit:StoreStore + LoadStore

五、高级特性

1.可重入性

实现机制:_recursions计数器

最大递归深度:Integer.MAX_VALUE

public synchronized void a() {

    b();

}

public synchronized void b() {

    // 可重入获取锁

}

2.锁消除

JIT编译器通过逃逸分析消除不必要的锁:

public String concat(String s1, String s2) {

    StringBuffer sb = new StringBuffer();

    sb.append(s1);

    sb.append(s2);

    return sb.toString();  // 锁被消除

}

3.锁粗化

JVM优化连续同步操作:

// 优化前

for(int i=0; i<100; i++) {

    synchronized(lock) {

        // 操作

    }

}

// 优化后

synchronized(lock) {

    for(int i=0; i<100; i++) {

        // 操作

    }

}

六、最佳实践

1.选择正确的锁对象

避免使用String常量等公共对象。

推荐使用私有final对象:

private final Object lock = new Object();

2.控制锁粒度

分段锁(如ConcurrentHashMap);

分离读写锁。

3.避免死锁

遵循顺序加锁原则:

// 线程1

synchronized(A) {

    synchronized(B) { ... }

}

// 线程2

synchronized(A) {      // 保持相同顺序

    synchronized(B) { ... }

}

4.性能优化

缩短同步块执行时间;

使用ThreadLocal减少竞争;

考虑ReadWriteLock替代方案。

七、常见问题

Q1: synchronized vs ReentrantLock?

特性对比:

公平性:synchronized非公平,ReentrantLock可选

条件变量:synchronized通过Object的wait/notify,ReentrantLock支持多个Condition

可中断:ReentrantLock支持lockInterruptibly()

Q2: 是否保证可见性?

完全保证,通过内存屏障实现

Q3: 静态同步与非静态同步是否互斥?

不互斥,因为锁对象不同(Class对象 vs 实例对象)

八、结论

  synchronized作为Java内置锁,经过多年优化已具备优异的性能表现。开发者应当:理解不同锁状态的转换条件;根据场景选择合适的同步粒度;结合JVM优化特性编写高效代码;在复杂场景中结合java.util.concurrent工具类使用。

  随着Java版本的更新(如JDK 15引入的偏向锁延迟启用),需要持续关注底层实现的演进。正确使用synchronized能在保证线程安全的同时,获得良好的性能表现。


http://www.kler.cn/a/575957.html

相关文章:

  • 基于Docker去创建MySQL的主从架构
  • DeepSeek × 豆包深度整合指南:工作流全解析
  • vue管理布局左侧菜单栏NavMenu
  • 蓝桥备赛(11)- 数据结构、算法与STL
  • 在 MySQL 的默认事务隔离级别(可重复读,REPEAT READ)下,事务 A 和事务 B 对同一行数据的操作时会产生什么呢?
  • “此电脑”中删除WPS云盘方法(百度网盘通用)
  • C++的基础(类)练习
  • Modbus TCP转Profibus DP协议转换网关赋能玻璃生产企业设备协同运作
  • nginx简单命令启动,关闭等
  • 嵌入式 ARM Linux 系统构成(3):根文件系统(Root File System)
  • 【Java代码审计 | 第十篇】命令执行漏洞成因及防范
  • HTML页面中divborder-bottom不占用整个底边,只占用部分宽度
  • HTML5的新特性有哪些?
  • 【第22节】C++设计模式(行为模式)-Iterator(迭代器)模式
  • 深入解析MySQL MVCC实现机制:从源码到实战演示隔离级别原理
  • RSA的理解运用与Pycharm组装Cryptodome库
  • Java8——Lambda表达式,常见的内置函数式接口
  • python学习笔记-day4(解决实际问题)
  • 如何查看Elastic-Job在Zookeeper中的注册信息
  • 深入解析MySQL备份技术:从逻辑到物理的全面指南