使用synchronized小坑

本文探讨了在Java中错误地使用`synchronized`关键字锁住封装的常量时可能出现的问题。当锁定的Integer数值在-128到127之间时,由于缓存机制,会锁住这部分范围,而超出该范围则无法实现同步。同样的问题也适用于Long类型。为了解决这个问题,建议使用静态常量或采取分布式锁方案,避免对小数进行锁定,因为它们无法被正确同步。

一般使用synchronized的时候,主要是用在方法上,代码块上,当使用在代码块的时候一般锁的都是类或者对象,但是有的人会去锁封装的常量。这个时候去锁这个封装的常量就会有问题了,我们来写个简单的demo来看看什么问题。

错误使用案例:

public static void print(Integer num) {    synchronized (num) {        try {            Thread.sleep(1000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println(Thread.currentThread().getName() + ":" + System.currentTimeMillis() / 1000);    }}

当num为2的时候输出

没有问题对吧。

把num换成128输出:

这时候就有问题了都是单独运行的统一时间执行的,测试完后就会发现-128~127

范围的数字是锁住了,但是这个范围之外的没有锁住,是什么原因呢?下面就为你揭晓答案哦:

debug  Integer num = 129;在valueOf上打断点后看到

初始化就会调用valueOf,如果在-128~127就会存缓存里面取,不在这个范围就会new 一个对象放进去,new 对象地址都不一样肯定是锁不住了呀。Long类型也是一样的

那如果你要锁一个常量怎么办呢?

可以转换为String锁字符串,前面加一个单独的标识拼接一下。错误的使用:

public static void print(Integer num) {    String key = "NUM_KEY_" + num;    synchronized (key) {        try {            Thread.sleep(1000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println(Thread.currentThread().getName() + ":" + System.currentTimeMillis() / 1000);    }}

这样的你觉得可以锁住不?答案肯定锁不住的因为String拼接时候就是

new String的操作这样拼接后地址不一样了,导致锁不住。

一定要用常量的话,可以写成静态的,或者使用分布式锁等,

尽量避免锁这样的数字吧。还有就是Double,Float小数是一个都锁不住的哦。

 

                                         文章同时会更新到公众号,觉得对你有帮助或者有用的可以关注一下哦 

### Java 编程语言中常见的陷阱及解决方案 #### 并发编程中的常见陷阱 在并发编程中,Java 提供了许多工具来实现高效的任务调度和资源利用。然而,如果不小心处理,可能会引发一系列严重的问题。 1. **死锁 (Deadlock)** 当多个线程互相等待对方释放资源时会发生死锁。这种情况下,程序可能陷入无限期的阻塞状态[^1]。为了避免死锁,可以采用以下策略: - 尽量减少锁定范围,仅在必要时才加锁。 - 使用 `tryLock()` 方法代替传统的 `synchronized` 锁定机制,以便在线程无法获得锁时可以选择其他操作。 2. **竞态条件 (Race Condition)** 多个线程访问共享变量且至少有一个线程修改该变量时可能发生竞态条件。这可能导致不可预测的行为[^3]。解决方法包括: - 使用同步块 (`synchronized`) 或者更高层次的并发控制类(如 `ReentrantLock` 和 `AtomicInteger`)。 - 避免直接暴露可变对象的状态。 3. **内存可见性问题** 如果不正确地使用 volatile 关键字或者 synchronized 块,某些线程可能看不到另一个线程对共享数据所做的更改[^3]。可以通过以下方式改善: - 对于需要立即更新的数据字段,声明为 `volatile`。 - 利用原子类(如 `AtomicBoolean`, `AtomicLong`),它们内置了必要的屏障以确保一致性。 #### 异常处理中的常见陷阱 异常处理是构建稳定应用程序的重要组成部分,但在实践中容易犯一些典型错误。 1. **捕获所有异常** 不区分具体类型的异常而统一捕获所有的 Throwable 可能掩盖潜在的真实问题[^2]。推荐的做法如下: - 明确指定要捕捉的具体异常类别而非泛化的 Exception 类型。 - 记录详细的日志信息并重新抛出未预期到的异常类型。 2. **忽略异常** 某些时候程序员倾向于简单地吞掉异常而不做任何反应,这样会使调试变得极其困难[^5]。应该始终妥善处置每一个被捕获的异常实例: - 至少打印堆栈跟踪消息至标准输出流或专用的日志文件里去。 - 考虑向调用方传递经过封装后的自定义业务逻辑层面上的新异常形式。 #### 容器化环境下的特殊注意事项 当运行基于 JVM 的服务处于 Docker/Kubernetes 这样的容器环境中时,还需要特别留意以下几个方面的隐患: 1. **资源分配不足** CPU、内存等硬件资源配置不合理会影响性能表现甚至造成 OOM(OutOfMemoryError)[^4]。因此需合理规划初始值与最大限制参数(-Xms,-Xmx),同时密切监视实际消耗情况调整设定值大小。 2. **网络延迟/分区容忍度差强人意** 很多分布式架构依赖远程过程调用来完成协作工作,在高负载场景下如果缺乏足够的超时设置则很容易因为单点失败拖垮整个系统链路[^4]。为此应实施断路器模式(Circuit Breaker Pattern)保护下游组件不受上游不稳定影响;另外也要定期演练故障恢复能力测试预案效果如何。 --- ```java // 示例代码展示如何安全地处理竞争条件 public class Counter { private int count; public void increment() { synchronized(this){ this.count++; } } public int getCount(){ synchronized(this){ return this.count; } } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值