温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

怎么用Java实现synchronized锁同步机制

发布时间:2021-11-04 13:41:44 来源:亿速云 阅读:200 作者:柒染 栏目:开发技术
# 怎么用Java实现synchronized锁同步机制

## 一、并发编程中的同步问题

### 1.1 为什么需要同步机制

在多线程编程中,当多个线程同时访问共享资源时,如果没有适当的同步机制,可能会导致数据不一致、竞态条件等问题。例如:

```java
public class Counter {
    private int count = 0;
    
    public void increment() {
        count++;  // 这不是原子操作
    }
    
    public int getCount() {
        return count;
    }
}

在这个简单的计数器例子中,count++操作实际上包含三个步骤:读取当前值、增加1、写回新值。如果两个线程同时执行这个操作,可能会导致计数结果不正确。

1.2 同步的基本概念

  • 原子性:操作要么完全执行,要么完全不执行
  • 可见性:一个线程对共享变量的修改对其他线程立即可见
  • 有序性:程序执行的顺序按照代码的先后顺序执行

二、synchronized关键字基础

2.1 synchronized的三种使用方式

2.1.1 同步实例方法

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

这种形式锁的是当前实例对象(this),同一个实例的多个同步方法会互斥。

2.1.2 同步静态方法

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

这种形式锁的是当前类的Class对象,所有该类的实例调用这个静态方法都会互斥。

2.1.3 同步代码块

public void method() {
    synchronized(obj) {  // obj是锁对象
        // 同步代码块
    }
}

这种形式可以灵活指定锁对象,可以是任意对象实例。

2.2 锁对象与监视器

每个Java对象都有一个内置锁(也称为监视器锁)。当线程进入synchronized方法或代码块时,会自动获取这个锁,退出时自动释放。

三、synchronized的实现原理

3.1 JVM层面的实现

synchronized在JVM中的实现是基于进入和退出Monitor对象来实现的:

  • monitorenter:获取对象的监视器锁
  • monitorexit:释放对象的监视器锁

3.2 对象头与Mark Word

Java对象在内存中的布局分为三部分:

  1. 对象头(Header)
  2. 实例数据(Instance Data)
  3. 对齐填充(Padding)

其中对象头包含Mark Word和类型指针。Mark Word是实现synchronized的关键:

|-------------------------------------------------------|--------------------|
|                  Mark Word (32 bits)                   |       State        |
|-------------------------------------------------------|--------------------|
| hashcode:25 | age:4 | biased_lock:1 | lock:2 (01)     |       Normal       |
|-------------------------------------------------------|--------------------|
| thread:23 | epoch:2 | age:4 | biased_lock:1 | lock:2 (01) |   Biased      |
|-------------------------------------------------------|--------------------|
| ptr_to_lock_record:30 | lock:2 (00)                    | Lightweight Locked |
|-------------------------------------------------------|--------------------|
| ptr_to_heavyweight_monitor:30 | lock:2 (10)           | Heavyweight Locked |
|-------------------------------------------------------|--------------------|
|                                               | lock:2 (11) |    Marked    |
|-------------------------------------------------------|--------------------|

3.3 锁升级过程

JDK 1.6之后,synchronized实现了锁升级机制:

  1. 无锁状态:初始状态
  2. 偏向锁:只有一个线程访问同步块时
  3. 轻量级锁:当有多个线程交替进入同步块
  4. 重量级锁:当多线程竞争激烈时

这种优化减少了锁操作的开销。

四、synchronized的高级特性

4.1 可重入性

synchronized是可重入锁,同一个线程可以多次获取同一个锁:

public class ReentrantDemo {
    public synchronized void method1() {
        method2();  // 可以调用另一个同步方法
    }
    
    public synchronized void method2() {
        // ...
    }
}

4.2 等待/通知机制

synchronized配合wait()/notify()/notifyAll()实现线程间通信:

public class WaitNotifyDemo {
    private final Object lock = new Object();
    private boolean condition = false;
    
    public void waitForCondition() throws InterruptedException {
        synchronized(lock) {
            while(!condition) {
                lock.wait();  // 释放锁并等待
            }
            // 条件满足后的处理
        }
    }
    
    public void setCondition() {
        synchronized(lock) {
            condition = true;
            lock.notifyAll();  // 唤醒所有等待线程
        }
    }
}

4.3 死锁问题

synchronized使用不当可能导致死锁:

public class DeadlockDemo {
    private final Object lock1 = new Object();
    private final Object lock2 = new Object();
    
    public void method1() {
        synchronized(lock1) {
            synchronized(lock2) {
                // ...
            }
        }
    }
    
    public void method2() {
        synchronized(lock2) {
            synchronized(lock1) {
                // ...
            }
        }
    }
}

五、synchronized的性能优化

5.1 减少锁的粒度

将大同步块拆分为小同步块:

// 不推荐
public synchronized void processBigData() {
    // 大量处理逻辑
}

// 推荐
public void processBigData() {
    // 非同步处理
    synchronized(this) {
        // 只同步必要部分
    }
    // 非同步处理
}

5.2 使用不同的锁对象

public class FineGrainedLock {
    private final Object readLock = new Object();
    private final Object writeLock = new Object();
    
    public void read() {
        synchronized(readLock) {
            // 读操作
        }
    }
    
    public void write() {
        synchronized(writeLock) {
            // 写操作
        }
    }
}

5.3 避免锁的长时间持有

public void process() {
    // 耗时的非同步操作
    List<Data> dataList = fetchDataFromDB();
    
    synchronized(this) {
        // 快速更新共享状态
        updateSharedState(dataList);
    }
}

六、synchronized与其他同步机制比较

6.1 synchronized vs ReentrantLock

特性 synchronized ReentrantLock
实现方式 JVM内置实现 JDK代码实现
锁获取方式 自动获取和释放 需要手动lock()和unlock()
可中断 不支持 支持lockInterruptibly()
公平锁 非公平 可选择公平或非公平
条件变量 只能有一个条件 可创建多个Condition
性能 JDK6后优化良好 高竞争下性能更好

6.2 synchronized vs volatile

  • synchronized:保证原子性、可见性和有序性
  • volatile:只保证可见性和有序性,不保证原子性

七、synchronized的最佳实践

7.1 同步对象的选择

// 不推荐 - 锁字符串常量可能有问题
synchronized("LOCK") {
    // ...
}

// 推荐 - 使用私有final对象
private final Object lock = new Object();

public void method() {
    synchronized(lock) {
        // ...
    }
}

7.2 避免同步方法

// 不推荐
public synchronized void process() {
    // ...
}

// 推荐
private final Object lock = new Object();

public void process() {
    synchronized(lock) {
        // ...
    }
}

7.3 双重检查锁定模式

public class Singleton {
    private volatile static Singleton instance;
    
    private Singleton() {}
    
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized(Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

注意:必须使用volatile防止指令重排序。

八、常见问题与解决方案

8.1 锁粗化与锁消除

JVM会进行优化:

  • 锁粗化:将连续的多个锁操作合并为一个
  • 锁消除:当JVM检测到不可能存在共享数据竞争时,会消除锁

8.2 锁的公平性问题

synchronized是非公平锁,可能导致线程饥饿。如果需要公平锁,可以使用ReentrantLock。

8.3 避免死锁的策略

  1. 按固定顺序获取锁
  2. 使用tryLock()设置超时
  3. 静态分析工具检测潜在死锁

九、实际案例分析

9.1 线程安全的计数器

public class SafeCounter {
    private int count = 0;
    
    public synchronized void increment() {
        count++;
    }
    
    public synchronized int getCount() {
        return count;
    }
}

9.2 生产者-消费者模型

public class ProducerConsumer {
    private final Queue<Integer> queue = new LinkedList<>();
    private final int CAPACITY = 10;
    private final Object lock = new Object();
    
    public void produce(int value) throws InterruptedException {
        synchronized(lock) {
            while(queue.size() == CAPACITY) {
                lock.wait();
            }
            queue.add(value);
            lock.notifyAll();
        }
    }
    
    public int consume() throws InterruptedException {
        synchronized(lock) {
            while(queue.isEmpty()) {
                lock.wait();
            }
            int value = queue.poll();
            lock.notifyAll();
            return value;
        }
    }
}

十、总结

synchronized是Java中最基本的同步机制,虽然看起来简单,但深入理解其实现原理和最佳实践对于编写高效、安全的并发程序至关重要。随着JDK版本的更新,synchronized的性能已经得到了显著提升,在大多数场景下都是不错的选择。

10.1 synchronized的优势

  1. 使用简单,语法清晰
  2. JVM内置支持,无需额外依赖
  3. JDK6后性能优化良好
  4. 自动释放锁,避免忘记解锁

10.2 适用场景

  1. 简单的同步需求
  2. 锁竞争不激烈的场景
  3. 需要快速实现同步的原型开发

10.3 学习建议

  1. 理解Java内存模型(JMM)
  2. 结合jstack等工具分析锁状态
  3. 阅读JVM源码了解底层实现
  4. 多实践,分析不同场景下的性能表现

通过本文的系统学习,相信读者已经掌握了synchronized的核心概念和使用技巧。在实际开发中,应根据具体场景选择合适的同步策略,平衡性能与正确性的需求。 “`

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI