由一个多线程共享Integer类变量问题引起的。。。

探讨了在并发环境中,对Integer类型直接使用synchronized加锁进行计数操作时,并不能保证线程安全的原因。通过示例代码说明Integer在一定条件下会创建新对象,导致锁失效。

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

假设并发环境下,业务代码中存在一些统计操作,为了保证线程安全,开发人员往往会对计数值进行加锁(synchronized),值得注意的是,直接对Integer类型进行加锁,似乎并会达到预期效果,比如下面这段代码:

Integer num = new Integer(0);

public void test() throws InterruptedException {

final int THREAD_SIZE = 10;

final int TASK_SIZE = 100000;

final CountDownLatch latch = new CountDownLatch(THREAD_SIZE);

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

new Thread() {

public void run() {

for (int j = 0; j < TASK_SIZE / THREAD_SIZEj++) {

synchronized (num) {

num++;

}

}

latch.countDown();

}

}.start();

}

latch.await();

System.out.println("num-->" + num);

}

 

上述代码示例中,总共有10个线程运行,每个线程执行次数为10000(taskSize/threadSize),但是实际程序输出结果却并非是10W次,或许有些同学会觉得诧异,在对计数值numInteger)进行递增操作前,已经执行了加锁操作,为啥还是非线程安全。我们首先来看一下上述程序的线程堆栈信息:

 

 

上图中,每一个线程锁住的资源其实都并非是同一个,这就可以解释为什么对Integer类型进行加锁仍然是非线程安全的

 

或许有同学会说,JavaAPI提供有线程安全的AtomicInteger为啥不用,尽管AtomicInteger是线程安全的,但是接下来我们还是要聊一聊为啥锁不住Integer等原始数据类型的封装类型。

 

JAVA5为原始数据类型提供了自动装/拆箱功能,假设对Integer进行递增/递减操作后,其实HashCode已经发生了变化,synchronized自然也不是同一个对象实例,Integer的源码,如下所示:

 

 

从源码中可以看出,当为Integer赋值数值在-128~127区间时,会从Integer中的一个Integer[]中获取一个缓存的Integer对象,而超出区间值得时候,每次都会new一个新的Integer对象,假设下述这段代码:

Integer num = new Integer(300);

System.out.println("identityHashCode-->" + System.identityHashCode(num));

System.out.println("hashCode-->" + num.hashCode());

num = 300;

System.out.println("identityHashCode-->" + System.identityHashCode(num));

System.out.println("hashCode-->" + num.hashCode());

 

实际程序输出为:

identityHashCode-->627248822

hashCode-->300

identityHashCode-->523481450

hashCode-->300

 内存改变了!即Integer类型超出[-128~127]区间时值的变动都会带来内存的变动

 

Synchronized锁的是对象,也就是identityHashCode所指向的内存地址中的对象实例(根据对象内存地址生成散列值),而hashcode输出的是值得散列值。所以为啥上述程序示例中,identityHashCode每次不同,而hashCode输出的值却相同

 

最后总结下,synchronized(Integer)时,当值发生改变时,基本上每次锁住的都是不同的对象实例,想要保证线程安全,推荐使用AtomicInteger之类会更靠谱。

### Java多线程共享内存的工具及其实现 在Java多线程环境中,为了确保多个线程能够安全地访问和修改共享内存中的数据,通常会借助一些专门设计的工具。这些工具不仅简化了开发过程,还提供了更高的可靠性和性能保障。 #### 1. `volatile`关键字 `volatile`是一种轻量级的同步机制,用于确保变量的可见性。当一个变量被声明为`volatile`时,它会在每次读取时从主存中获取最新值,而不是缓存在本地线程的工作内存中[^2]。这种方式适用于不需要原子性的场景,比如标志位的状态更新。 ```java public class VolatileExample { private volatile boolean flag = true; public void stop() { this.flag = false; } public void runTask() { while (flag) { System.out.println("Running..."); } } } ``` --- #### 2. 原子 (`AtomicXXX`) Java 提供了一组位于 `java.util.concurrent.atomic` 包下的原子操作,例如 `AtomicInteger`, `AtomicLong`, 和 `AtomicReference` 等。它们利用硬件级别的 CAS(Compare And Swap)指令实现了无锁的高效并发控制[^2]。 ```java import java.util.concurrent.atomic.AtomicInteger; public class AtomicExample { private AtomicInteger count = new AtomicInteger(0); public int incrementAndGet() { return count.incrementAndGet(); } } ``` --- #### 3. 阻塞队列 (`BlockingQueue`) 阻塞队列是一系列支持生产者-消费者模式的数据结构,在多线程环境下非常有用。常用的实现有 `ArrayBlockingQueue`, `LinkedBlockingQueue`, 和 `SynchronousQueue` 等。这些队列内部已经处理好了线程间的协调工作,开发者只需关注逻辑即可[^2]。 ```java import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; public class BlockingQueueExample { private final BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(); public void produce(int value) throws InterruptedException { queue.put(value); } public Integer consume() throws InterruptedException { return queue.take(); } } ``` --- #### 4. 同步容器 虽然统的集合框架(如 `Vector` 或 `Hashtable`)也提供了一些基本的线程安全性,但在高并发情况下效率较低。因此推荐使用 `ConcurrentHashMap`, `CopyOnWriteArrayList` 这样的现代替代品[^2]。 ```java import java.util.concurrent.ConcurrentHashMap; public class ConcurrentMapExample { private ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>(); public void put(String key, String value) { map.put(key, value); } public String get(String key) { return map.get(key); } } ``` --- #### 5. 计数器与屏障工具 除了直接管理对象外,还有一些辅助型工具可以帮助我们更好地组织复杂的并发流程: - **`CountDownLatch`**: 如前所述,允许一组线程等待其他线程完成特定的操作后再继续运行[^3]。 - **`CyclicBarrier`**: 能够让参与协作的一组线程彼此相互等待到达某个公共屏障点之后再一起前进。 ```java import java.util.concurrent.CountDownLatch; public class LatchExample { private CountDownLatch latch = new CountDownLatch(3); // 初始化计数值为3 public void doWork() { try { Thread.sleep(1000); // 模拟耗时任务 latch.countDown(); // 减少计数 } catch (InterruptedException e) { e.printStackTrace(); } } public void awaitCompletion() throws InterruptedException { latch.await(); // 主线程在此处挂起直到计数归零 } } ``` --- #### 性能优化建议 尽管上述工具有助于构建健壮的应用程序,但仍需注意合理配置参数以及避免不必要的竞争条件。例如可以通过设置合适的初始容量减少扩容频率;或者采用不可变对象降低加锁需求等等[^2]。 --- ###
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值