Java多线程下跨类传递变量获取null的解决方案
以下为我开发的多线程项目的高度简化版本,用于以最简形式复现问题,便于理解排查。
项目包含两个类:startSession.java 与 main.java。
我需要在多线程模式下将变量从startSession.java传递到main.java,但目前遇到问题:每次在main类中尝试获取该变量时,返回值均为null。startSession类中包含run()方法、Setter方法setSessionKey(String sess)与Getter方法getSessionKey(),我使用硬编码变量开展测试。
get方法仅在run()方法内部调用时可正常取值,在main方法中调用getSessionKey()会返回null,运行结果见下方截图。该问题仅在多线程场景下出现:若不使用多线程,直接在main方法中调用run()方法,则可正常获取非null的目标变量值。
请问在多线程场景下,如何实现变量从startSession类到main类的正确传递?
问题相关代码
startSession.java
public class startSession extends Thread { static String sessionKey; public void run() { String createdSession = "83248329"; setSessionKey(createdSession); System.out.println("Inside run method: " + getSessionKey()); } public String getSessionKey() { return sessionKey; } public void setSessionKey(String sess) { sessionKey = sess; } }
main.java
package com.Server; public class Main { static String session; public static void main(String[] args) throws InterruptedException { startSession startSession = new startSession(); startSession.start(); session = startSession.getSessionKey(); System.out.println("Inside Main: " + session); } }
运行结果
- 多线程模式运行结果:

- 非多线程模式运行结果:

问题根因
这是最基础的多线程并发时序+可见性问题,和变量传值逻辑无关:
- 调用
Thread.start()启动子线程后,子线程的run()方法和主线程后续代码是并行执行的,不存在强制先后顺序。你在主线程中立刻调用getSessionKey()时,子线程大概率还没执行到给sessionKey赋值的逻辑,自然拿到的是成员变量的初始值null。 - 直接在主线程调用
run()方法属于同步调用,代码按顺序逐行执行,赋值逻辑完成后才会走取值逻辑,所以能拿到正确结果。 - 你当前代码里把
sessionKey定义为static是错误设计:静态变量属于类所有,不属于单个线程实例,后续创建多个startSession实例时会出现值互相覆盖的问题,应该改为实例私有成员变量。 - 就算调整时序让子线程先赋值,没有正确的同步手段的话,主线程也可能因为CPU缓存看不到子线程的修改,出现可见性问题。
修复方案
最简场景下直接使用Thread.join()即可,这个方法会阻塞调用方线程(这里是主线程),直到子线程执行完毕,同时自带Happens-Before内存可见性保证,子线程对变量的所有修改都会对主线程可见,不需要额外加volatile或者锁。
修正后代码
- 修正
startSession类,去掉不合理的static修饰:
public class startSession extends Thread { // 改为实例私有变量,避免多实例值互相覆盖 private String sessionKey; public void run() { String createdSession = "83248329"; setSessionKey(createdSession); System.out.println("Inside run method: " + getSessionKey()); } public String getSessionKey() { return sessionKey; } public void setSessionKey(String sess) { sessionKey = sess; } }
- 修正
Main类,加join()等待子线程执行完成再取值:
package com.Server; public class Main { static String session; public static void main(String[] args) throws InterruptedException { startSession startSession = new startSession(); startSession.start(); // 阻塞主线程,直到子线程执行完毕 startSession.join(); session = startSession.getSessionKey(); System.out.println("Inside Main: " + session); } }
如果后续业务不需要等子线程完全执行完,只需要等sessionKey赋值完成就可以继续后续逻辑,可以使用CountDownLatch、FutureTask等并发工具实现更细粒度的等待控制,不需要死等线程结束。
补充:Java编码规范要求类名使用大驼峰命名,
startSession建议重命名为StartSession,main类类名已经是Main符合规范。
内容的提问来源于stack exchange,提问作者Crimson Space

