synchronized 加锁Integer对象(数据重复)详解

本文探讨了使用静态Integer变量及synchronized关键字实现线程安全计数时出现的问题。通过分析字节码和Integer缓存机制,揭示了线程安全问题的根本原因。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

场景描述:多线程输出1到100,对静态Integer对象加锁,synchronized代码块中操作Integer对象,发生线程安全问题(数据重复)

代码:

public class MyRunnableTest implements Runnable {
    public static Integer i = new Integer(0);

    @Override
    public void run() {
        while(true){
            synchronized (i) {
                if(i<100){
                    i++;
                    System.out.println(Thread.currentThread()+"i = " + i);
                }else {
                    break;
                }
            }
        }
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(new MyRunnableTest());
        Thread t2 = new Thread(new MyRunnableTest());
        t1.start();
        t2.start();
    }

}

运行结果:

Thread[Thread-0,5,main]i = 1
Thread[Thread-1,5,main]i = 3
Thread[Thread-1,5,main]i = 4
Thread[Thread-1,5,main]i = 5
Thread[Thread-1,5,main]i = 6
Thread[Thread-0,5,main]i = 5
Thread[Thread-1,5,main]i = 7
Thread[Thread-0,5,main]i = 8
Thread[Thread-1,5,main]i = 9
Thread[Thread-0,5,main]i = 10
Thread[Thread-1,5,main]i = 11
Thread[Thread-0,5,main]i = 12
Thread[Thread-1,5,main]i = 13

 

从运行结果中可以发现发生了线程安全问题,为什么呢?为什么synchronized无效了。

我的排查思路:

1、因为没有进行任何的额外操作,所以首先定位问题在i++处

通过javap分析字节码命令,总结:

可以看出:i++的实际操作为:Integer.valueOf(Integer.intValue(i)+1)

我们在看看Integer中vauleOf的源码:

public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

cache[]数组初始化: 
  cache = new Integer[(high - low) + 1];
  int j = low;
  for(int k = 0; k < cache.length; k++)
  cache[k] = new Integer(j++);

 

其中low为-128,hight为127。可以了解到,当i的值为-128~127时,是从IntegerCache中取的(可以理解为从缓存中取的),超过部分是new出来的对象。

2、目前我们慢慢开始解开了问题的面纱,每当i发生自增后,所对象改变了,这里我们还需要清楚一下,synchronized在锁对象发生改变时(测试发现,引用类型对象的内在属性变化不会释放锁)会立即释放锁。所以这里就会出现线程安全问题,而且在单核的运行环境下,所有的线程是并发执行而不是并行执行,当我们运行到system.out.println时,锁已经释放了,假如这边t1线程(当前运行的线程,i=1的情况)释放CPU资源,t2执行,这时i等于2(i++之前),当执行到system.out.println时释放cpu资源(此时i=3),t1执行,i此时已经为3了,所以输出3,在释放cpu资源,t2执行,输出3,这时出现了输出重复值的情况。

 

转载于:https://www.cnblogs.com/jswang/p/9099926.html

<think>我们正在讨论Java中的synchronized关键字。用户要求理解其底层原理、应用场景使用方法。根据之前的对话历史,我们已经详细解释了synchronized的原理(包括锁升级、内存可见性等)以及使用场景,并给出了代码示例。现在用户再次询问,我们需要在已有基础上进行更深入或更系统的总结,同时注意引用格式。 由于用户要求参考站内引用[1][2],我们将重点整合这些信息,并确保回答结构清晰。 计划结构: 1. 底层原理:从对象头、Monitor、锁升级(偏向锁、轻量级锁、重量级锁)角度详细说明。 2. 应用场景:列举典型场景并给出代码示例。 3. 使用方法:同步方法、同步代码块、静态同步方法等。 4. 注意事项:性能优化、与volatile的对比等。 5. 相关问题生成。 注意:在回答中,我们将引用[1][2](在引用到的地方标注),同时使用LaTeX公式(如必要)代码块。 开始编写回答。</think>### Java `synchronized` 底层原理、应用场景与使用方法详解 #### 一、底层原理 1. **对象头与 Monitor 机制** - 每个 Java 对象在堆内存中包含**对象头**(Mark Word + Klass Pointer) - `synchronized` 通过对象头的 **Mark Word** 实现锁状态管理: | 锁状态 | Mark Word 结构(64位) | 特点 | |----------|----------------------------------------|--------------------------| | 无锁 | 哈希码 + 分代年龄等 | 无竞争 | | 偏向锁 | 线程ID + Epoch + 偏向标志 | 单线程重入优化[^1] | | 轻量级锁 | 指向栈中锁记录的指针 | CAS 自旋($ \text{CAS}(0,1) $)[^2] | | 重量级锁 | 指向 Monitor 的指针 | 操作系统互斥量(Mutex) | - **Monitor 工作流程**: ```mermaid graph LR A[线程尝试加锁] --> B{锁状态} B -->|无锁/偏向锁| C[CAS 修改 Mark Word] B -->|轻量级锁| D[自旋尝试 CAS] D -->|成功| E[执行同步代码] D -->|失败| F[升级为重量级锁] F --> G[线程进入等待队列] ``` 2. **锁升级过程**(JDK 15+ 优化) - **偏向锁**:首次获取时写入线程 ID($ \text{ThreadID} \xrightarrow{\text{存储}} \text{Mark Word} $),后续直接访问 - **轻量级锁**:通过 **CAS 自旋**尝试修改锁标志位(失败则升级) - **重量级锁**:竞争失败时,线程进入操作系统等待队列(上下文切换开销大) > ⚠️ **JDK 15+ 变化**:默认禁用偏向锁(撤销开销过大),直接进入轻量级锁[^1] 3. **内存可见性与有序性** - 遵循 **Happens-Before 规则**: $$ \text{Unlock} \rightarrow \text{Lock} \quad \text{强制主内存同步} $$ - 解锁时:本地变量修改刷新到主内存 - 加锁时:从主内存重新加载变量[^1] 4. **可重入性** 同一线程可重复获取锁(计数器实现): ```java public synchronized void methodA() { methodB(); // 可重入(计数器+1) } ``` --- #### 二、应用场景 | **场景** | **实现方式** | **原理优势** | |--------------------------|---------------------------------------|----------------------------------| | **共享资源保护** | 同步方法/代码块 | 原子操作(如转账)[^1] | | **单例模式** | 双重校验锁 + `volatile` | 禁止指令重排序 | | **线程安全计数器** | `synchronized` + 原子操作 | 替代 `AtomicInteger`(简单场景) | | **生产者-消费者模型** | `synchronized` + `wait()`/`notify()` | 条件等待机制 | | **线程安全集合** | `Collections.synchronizedList()` | 快速包装非线程安全集合 | ##### 代码示例 ```java // 场景1:共享资源保护(账户转账) public synchronized void transfer(Account target, int amount) { if (this.balance >= amount) { this.balance -= amount; // 原子操作 target.balance += amount; } } // 场景2:单例模式(双重校验锁) public class Singleton { private static volatile Singleton instance; public static Singleton getInstance() { if (instance == null) { // 第一次检查 synchronized (Singleton.class) { // 加锁 if (instance == null) { // 第二次检查 instance = new Singleton(); } } } return instance; } } ``` --- #### 三、使用方法 1. **同步方法** ```java // 实例方法锁:锁对象为当前实例 (this) public synchronized void syncMethod() { ... } // 静态方法锁:锁对象为 Class 对象 (Singleton.class) public static synchronized void staticSyncMethod() { ... } ``` 2. **同步代码块**(更细粒度控制) ```java public void update() { // 锁指定对象(推荐锁 final 对象) final Object lock = new Object(); synchronized (lock) { // 临界区代码 } } ``` 3. **锁与条件变量** ```java // 生产者-消费者模型 public class Buffer { private final Queue<Integer> queue = new LinkedList<>(); private final int CAPACITY = 10; public synchronized void produce(int item) throws InterruptedException { while (queue.size() == CAPACITY) { wait(); // 释放锁并等待 } queue.add(item); notifyAll(); // 唤醒消费者 } public synchronized int consume() throws InterruptedException { while (queue.isEmpty()) { wait(); } int item = queue.poll(); notifyAll(); // 唤醒生产者 return item; } } ``` --- #### 四、性能优化建议 1. **减少锁粒度** - 优先使用同步代码块(仅锁定必要部分) - 避免锁整个方法: ```java // 优化前 public synchronized void update() { ... } // 优化后 public void update() { synchronized (this) { ... } // 仅锁临界区 } ``` 2. **锁分离技术** - 读写分离:使用 `ReentrantReadWriteLock` - 示例: ```java private final ReadWriteLock rwLock = new ReentrantReadWriteLock(); public void readData() { rwLock.readLock().lock(); try { /* 读操作 */ } finally { rwLock.readLock().unlock(); } } ``` 3. **避免锁升级开销** - 低竞争:依赖轻量级锁的 CAS 自旋 - 高竞争:改用 `ReentrantLock`(支持超时公平锁) --- ### 总结 | **特性** | 说明 | |-------------------|----------------------------------------------------------------------| | **底层机制** | 基于对象头 Mark Word 实现锁状态管理(无锁→轻量级锁→重量级锁)[^2] | | **内存保障** | 通过 Happens-Before 规则保证可见性($ \text{Unlock} \rightarrow \text{Lock} $) | | **适用场景** | 简单同步需求(高竞争场景考虑 `ReentrantLock`) | | **最佳实践** | 同步代码块 + 细粒度锁 + 避免嵌套锁 | > 📌 **选择建议**: > - 优先用 `synchronized`(简洁安全) > - 高并发/高级需求用 `ReentrantLock`(超时、公平锁)[^1]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值