Java参数传递均为值传递:基本类型传数值副本,引用类型传地址副本;多个线程通过地址副本共享堆中同一对象,修改其状态会导致竞态条件,需同步保障线程安全。

Java 的值传递机制本身不直接引发多线程问题,但它深刻影响你对“共享数据如何被修改”的判断——尤其在多个线程操作同一对象时,容易误以为“改了形参就等于改了实参”,从而忽略真正的线程安全边界。
值传递机制决定了谁在真正共享
Java 所有参数都是值传递:基本类型传的是数值副本,引用类型传的是对象地址的副本。这意味着:
- 多个线程拿到的是同一个对象地址(比如一个
ArrayList实例),它们操作的是堆中同一个对象——这就构成了实际的共享状态; - 但每个线程栈里的引用变量(如
list)是独立的,哪怕你在方法里把形参list = new ArrayList(),也只改变当前线程栈里的那个地址副本,不影响其他线程持有的原地址; - 所以,线程安全与否,取决于你是否在操作堆中那个共用的对象内容,而不是栈里的引用变量本身。
String 和数组是典型认知陷阱
String 不可变,看似“安全”,但它的值传递特性常被误读:
- 方法内执行
s = s + "x",只是让形参指向新字符串,原引用不受影响; - 但若多个线程同时调用某个方法,该方法内部又去修改一个共享的
StringBuilder,那问题就来了——因为 StringBuilder 是可变的,且地址被多个线程持有。
数组同理:传入一个 int[] arr,方法里 arr[0] = 1 会真实修改堆中数组元素,所有线程可见;但 arr = new int[5] 只改本地副本,不影响外部。
立即学习“Java免费学习笔记(深入)”;
它放大了可见性与原子性问题
值传递机制让你天然“以为”各线程互不干扰,但实际上:
- 多个线程通过各自栈中的引用副本,访问堆中同一对象的字段,就可能产生竞态条件(如
counter++); - 即使你没显式共享变量,只要对象被多个线程通过参数传入并修改,JMM 的可见性规则就开始起作用——没有同步手段,一个线程改了字段,另一个线程可能永远看不到;
- 值传递不提供任何同步保障,它只是数据传递的起点;后续是否需要
synchronized、volatile或并发集合,全由你对这个“共享地址副本”的使用方式决定。
线程池场景下的隐式共享更需警惕
在线程池中,任务对象(如 Runnable 实现类)常携带成员变量,这些变量在被不同线程反复执行时,就变成跨线程共享的载体:
- 如果 Runnable 中持有
List<String> results,且多个线程都往里面 add,即使你是通过值传递把 Runnable 交给线程池,结果列表仍是共享的; - 这时候不能靠“参数是值传递”来免责,而必须用
ConcurrentHashMap、CopyOnWriteArrayList或加锁; - 常见错误是:把一个非线程安全的集合作为方法参数传给多个任务,误以为“每个任务拿的是自己的副本”,其实它们拿到的是同一个堆对象的地址副本。


















