Java多线程编程:synchronized关键字详解与实践
发布时间:2026/9/17 15:40:57 锦皓数字建站

1. synchronized关键字的本质与特性在Java多线程编程中synchronized是最基础的线程同步机制。它的核心作用可以概括为三点互斥性、内存可见性和可重入性。理解这些特性对编写正确的并发程序至关重要。1.1 互斥性原理互斥性Mutual Exclusion是synchronized最基础的功能。我们可以用现实生活中的洗手间门锁来类比当线程A进入synchronized方法时就像有人进入洗手间并锁上门线程B尝试进入同一个synchronized方法时必须等待线程A退出释放锁线程A使用完毕后线程B才能进入但需要注意几个关键细节锁的获取是非公平的等待的线程不会按照FIFO先进先出顺序获取锁。操作系统会随机选择一个等待线程唤醒这可能导致某些线程长时间等待线程饥饿锁的释放和获取不是原子操作当一个线程释放锁后其他线程不会立即获取锁。中间存在一个短暂的过渡期操作系统需要进行线程调度锁的粒度synchronized锁住的是整个对象实例对于实例方法或整个类对于静态方法这可能导致不必要的性能损耗实际开发中我们经常看到这样的代码public synchronized void transfer(Account target, int amount) { this.balance - amount; target.balance amount; }这种粗粒度的锁虽然简单但在高并发场景下性能较差。更好的做法是使用更细粒度的锁机制。1.2 内存可见性保证synchronized不仅能保证互斥还能确保内存可见性。这是通过Java内存模型JMM的happens-before规则实现的获取锁时JVM会强制从主内存重新加载所有变量释放锁时JVM会强制将所有修改刷新回主内存这个过程可以表示为synchronized(lock) { // 1. 从主内存读取共享变量 // 2. 执行代码 // 3. 将修改写回主内存 }这种机制解决了CPU缓存导致的可见性问题但也会带来性能开销。在Java 5之后对于简单的原子操作推荐使用volatile或Atomic类。1.3 可重入性详解可重入性Reentrancy是指一个线程可以重复获取已经持有的锁。这个特性非常重要否则会导致死锁class ReentrantExample { public synchronized void methodA() { methodB(); // 如果锁不可重入这里会阻塞 } public synchronized void methodB() { // ... } }Java中的synchronized是可重入的实现原理是每个锁关联一个计数器和一个持有线程当计数器为0时锁未被任何线程持有当线程第一次获取锁时计数器变为1同一线程每次重入计数器递增线程退出同步块时计数器递减当计数器归零时锁被释放这种设计避免了线程自己阻塞自己的情况使得递归调用和方法的相互调用更加安全。2. synchronized的使用方式2.1 方法级别的同步2.1.1 实例方法同步在方法声明中添加synchronized关键字public synchronized void instanceMethod() { // 同步代码 }这种方式的锁对象是当前实例this意味着同一实例的多个同步方法会互斥不同实例的方法不会互相影响2.1.2 静态方法同步public static synchronized void staticMethod() { // 同步代码 }静态同步方法的锁对象是类的Class对象如MyClass.class这意味着所有调用该静态方法的线程都会竞争同一把锁静态同步方法和实例同步方法使用不同的锁不会互斥常见错误混淆实例锁和类锁class Counter { static int count 0; public synchronized void add() { // 实例锁 count; } }这种写法在多线程环境下仍然不安全因为不同实例使用不同的锁来修改静态变量。2.2 代码块级别的同步代码块同步提供了更灵活的锁控制方式语法为synchronized(lockObject) { // 同步代码 }2.2.1 锁对象的选择锁当前实例public void method() { synchronized(this) { // 同步代码 } }等效于同步实例方法但可以只同步部分代码。锁特定对象private final Object lock new Object(); public void method() { synchronized(lock) { // 同步代码 } }使用专用锁对象可以减少锁的范围提高并发性。锁Class对象public void method() { synchronized(MyClass.class) { // 同步代码 } }等效于静态同步方法但可以只同步部分代码。2.2.2 锁对象的注意事项不要锁局部变量public void wrongMethod() { Object localLock new Object(); synchronized(localLock) { // 每个线程有自己的localLock无法同步 // ... } }不要锁可能被修改的对象private Object lock new Object(); public void wrongMethod() { synchronized(lock) { lock new Object(); // 修改锁引用会导致同步失效 } }避免锁字符串常量public void wrongMethod(String key) { synchronized(key.intern()) { // 字符串常量池是全局的可能导致意外死锁 // ... } }3. 线程安全类与同步实践3.1 Java集合框架的线程安全性3.1.1 非线程安全集合这些集合在单线程环境下性能优异但在多线程环境下需要外部同步List: ArrayList, LinkedListSet: HashSet, TreeSetMap: HashMap, TreeMap使用示例ListString list Collections.synchronizedList(new ArrayList()); // 等价于 ListString list new ArrayList(); ListString syncList Collections.synchronizedList(list);3.1.2 线程安全集合Java提供的原生线程安全集合Vector/Hashtable早期实现所有方法都使用synchronized不推荐使用性能较差迭代器不是线程安全的ConcurrentHashMap分段锁设计高并发下性能优异弱一致性的迭代器推荐替代HashtableCopyOnWriteArrayList写时复制适合读多写少场景迭代器不会抛出ConcurrentModificationExceptionStringBuffer vs StringBuilderStringBuffer是线程安全的方法使用synchronizedStringBuilder是非线程安全的但性能更好3.2 同步的最佳实践减小同步范围// 不好 public synchronized void process() { // 大量非同步代码 // 少量需要同步的代码 } // 更好 public void process() { // 非同步代码 synchronized(this) { // 需要同步的代码 } // 非同步代码 }使用专用锁对象private final Object readLock new Object(); private final Object writeLock new Object(); public void read() { synchronized(readLock) { // ... } } public void write() { synchronized(writeLock) { // ... } }避免嵌套锁// 可能导致死锁 public void methodA() { synchronized(lock1) { synchronized(lock2) { // ... } } } public void methodB() { synchronized(lock2) { synchronized(lock1) { // ... } } }考虑使用更高层次的并发工具ReentrantLock可中断、可定时、公平锁ReadWriteLock读写分离StampedLock乐观读Atomic类CAS操作4. 常见问题与性能优化4.1 死锁问题死锁的四个必要条件互斥条件占有且等待不可抢占循环等待示例死锁代码// 线程1 synchronized(lockA) { synchronized(lockB) { // ... } } // 线程2 synchronized(lockB) { synchronized(lockA) { // ... } }解决方案锁顺序所有线程按相同顺序获取锁锁超时使用tryLock()尝试获取锁死锁检测定期检查死锁并恢复4.2 性能瓶颈synchronized的主要性能问题锁竞争大量线程竞争同一把锁锁膨胀从偏向锁→轻量级锁→重量级锁的升级过程上下文切换线程阻塞和唤醒的开销优化建议减小锁粒度如ConcurrentHashMap的分段锁减少锁持有时间只在必要时同步使用读写锁读操作不互斥考虑无锁算法如CAS操作4.3 替代方案比较特性synchronizedReentrantLockvolatileAtomic类互斥性✓✓✗✗可重入✓✓--公平性✗✓--条件等待wait/notifyCondition✗✗尝试获取锁✗✓--锁中断✗✓--内存可见性✓✓✓✓适用场景简单同步复杂同步状态标志计数器在实际项目中我通常会根据具体场景选择同步方案简单同步synchronized需要高级功能ReentrantLock单一状态标志volatile计数器等AtomicInteger等4.4 调试技巧线程转储分析jstack pid thread_dump.txt可以查看线程状态和锁持有情况JVisualVM监控线程状态检测死锁日志记录public void debugMethod() { System.out.println(Thread.currentThread().getName() 尝试获取锁); synchronized(lock) { System.out.println(Thread.currentThread().getName() 获得锁); // ... } }性能分析使用JProfiler等工具分析锁竞争情况关注Monitor相关的性能指标对于刚接触多线程开发的程序员我的建议是从synchronized开始理解基本的同步概念后再逐步学习更高级的并发工具。在实际编码中要特别注意锁的范围和粒度避免过度同步导致的性能问题。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。