多线程分别调用类的getter和setter方法时是否需要加锁?
多线程下Getter/Setter的线程安全问题:要不要加锁?
嘿,这个问题算是多线程编程里的经典入门坑了——答案不能一概而论,得看你的字段类型、方法逻辑以及你需要保证的线程安全特性(原子性、可见性、有序性)来定。咱们一步步拆解:
核心问题:为什么会有风险?
当一个线程写(setter)、另一个线程读(getter)时,主要会遇到两类隐性问题:
- 原子性问题:如果赋值/读取不是一步完成的操作,线程切换可能导致读到“半完成”的脏数据;
- 可见性问题:线程对字段的修改可能只存在于自己的本地缓存,其他线程无法立刻读到最新值。
哪些场景必须加锁(或用同步方案)?
- 字段是复杂类型/集合:比如你的字段是
ArrayList、自定义User对象,setter里修改对象内部属性,或者getter里遍历集合——不加锁的话,很可能触发ConcurrentModificationException,或者读到逻辑不一致的对象状态。 - Setter/Getter包含复合操作:比如setter不是简单的
this.count = newCount,而是this.count = this.count + 1;或者getter里有return this.start + this.end这类多字段计算——复合操作本身不是原子的,线程切换会直接导致数据错误。 - 需要保证多步操作的原子性:比如你需要先调用getter判断值,再调用setter修改,哪怕单个方法是安全的,组合起来也需要锁来保证“判断-修改”的原子性,避免竞态条件。
哪些场景可以不用锁,但有替代方案?
- 字段是基本类型,且仅需简单赋值/读取:
- 对于32位基本类型(int、float、boolean等),赋值/读取是原子操作,但可见性依然有问题——这时候可以用
volatile修饰字段,保证一个线程的修改能立刻被其他线程看到。 - 对于64位基本类型(long、double),在32位JVM上赋值/读取不是原子操作,所以哪怕是简单赋值,也需要
volatile或者锁来避免读到“半截”数据。
- 对于32位基本类型(int、float、boolean等),赋值/读取是原子操作,但可见性依然有问题——这时候可以用
- 用原子类替代普通字段:比如用
AtomicInteger代替int,AtomicReference代替自定义对象——这些类本身就封装了线程安全的get/set操作,不用自己手动加锁。 - 字段是final且初始化后不再修改:final字段的可见性由Java内存模型(JMM)保证,一旦初始化完成,所有线程都能读到正确的初始值,这时候getter完全安全。
总结建议
- 别心存侥幸,哪怕是看起来简单的get/set,只要涉及多线程读写,一定要考虑线程安全;
- 如果只是需要保证可见性,优先用
volatile(但注意它不保证复合操作的原子性); - 如果有复合操作或需要原子性的多步操作,一定要用锁(比如
synchronized关键字,或者ReentrantLock); - 优先使用JDK提供的线程安全类(Atomic系列、Concurrent集合),比自己手写锁更可靠。
内容的提问来源于stack exchange,提问作者Thorsten Schwonbeck
相关产品推荐
相关产品推荐

