
This article deeply explores the interaction mechanism between objects and reference variables in Java multi-threaded environment. We'll clarify the difference between object instances and reference variables, and explain why one thread can access an object created by another thread, even if that thread is in an infinite loop. The article will also analyze in detail how the Java Memory Model (JMM) ensures the visibility and orderliness of multi-threaded operations through the "Happens-Before" principle, and emphasizes the importance of correct synchronization when sharing mutable state to avoid potential concurrency issues.
Understanding objects and references in Java
In Java, understanding the difference between objects and reference variables is fundamental to mastering multi-threaded programming. When we execute a statement like WhatTime wt = new WhatTime(); in our code, two distinct operations actually occur:
- new WhatTime() : This operation creates an instance of the WhatTime class in Heap memory, which is what we often call an "object". Heap memory is shared by all threads and the actual data of the object is stored here.
- WhatTime wt : This declares a reference variable wt. The reference variable itself is stored in stack memory (if it is a local variable) or heap memory (if it is a member variable of another object). wt does not store the actual data of the WhatTime object, but stores the "address" or "reference" of the object in heap memory. We can compare it to "a page in an address book with the address of a house written on it", where the house itself is the object.
When we pass the reference variable wt to another thread, such as threadA ta = new threadA(wt);, Java uses value transfer . This means that the threadA constructor receives a copy of the wt reference variable. Now, both the main thread and threadA have addresses pointing to the same WhatTime object in the heap. Their respective reference variables are independent, but they all point to the same shared "house". Therefore, any modifications made by one thread to this shared object will be observed by other threads holding the same reference.
Thread state and object access
Regarding the misunderstanding that "if a thread is in a while(true) loop, it cannot interact with it", it needs to be clarified that whether a thread is in a loop does not directly determine whether other threads can access the objects it creates or holds. The key is:
- The object is on the heap : No matter what state the thread that created the object is in (running, blocked, waiting), as long as the object exists in heap memory and other threads hold references to it, then these threads can access the object.
- Passing by reference : By passing a reference to an object to other threads, these threads obtain the "key" to access the shared object.
So, even though the main thread is in the while(true) loop, it has passed the reference of the whatTime object to threadA. ThreadA can call the wt.getTime() method normally through this reference, because the getTime() method operates on the whatTime object in the heap, not the stack frame of the main thread or its private data.
Java Memory Model (JMM) and Concurrency Challenges
Although multiple threads can access the same shared object, this does not mean that all concurrent accesses are safe. In order to improve performance, modern CPUs use multi-level cache. The CPU core does not directly read or write main memory, but operates its local cache. This leads to a core problem: modifications to shared variables by one thread may only exist in its local cache and will not be immediately synchronized to main memory, and other threads may therefore read stale data.
To solve this memory visibility problem and ensure the correctness of concurrent programs, Java introduced the Java Memory Model (JMM) . JMM defines the visibility and ordering rules of various operations in the program, the core concept of which is the Happens-Before principle.
Happens-Before principle
If operation A Happens-Before operation B, then the results of operation A are visible to operation B, and operation A occurs before operation B in time. JMM establishes the Happens-Before relationship through a series of rules, such as:
- Program sequence rules : Each operation in a thread happens-before any subsequent operation in the thread.
- Monitor locking rules : Unlocking a monitor happens-before subsequent locking of the monitor.
- Volatile variable rules : A write operation to a volatile variable happens-before a subsequent read operation to the volatile variable.
- Thread startup rules : The Thread.start() method happens-before every action of the thread.
- Thread termination rules : All operations in a thread happen-before the thread's termination detection.
Risks of Shared Mutable State
If multiple threads access and modify the same shared mutable state (that is, variables that can be modified on the heap) at the same time, and no Happens-Before relationship is established to ensure visibility and ordering, a data race (Data Race) will occur, leading to unpredictable results.
Consider the following example:
class Example {
int x; // Shared mutable state void crazy() {
x = 1; // The main thread writes new Thread(() -> x = 5).start(); // Thread 1 writes new Thread(() -> x = 10).start(); // Thread 2 writes System.out.println(x); // The main thread reads}
}
In the above Example class, x is a shared mutable state. The main thread, thread 1 and thread 2 are all accessing and modifying x. Since there is no synchronization mechanism (such as synchronized or volatile) to establish the Happens-Before relationship, JMM does not guarantee the visibility and ordering of these operations. Therefore, the output of System.out.println(x) may be 1, 5, or 10, or even other unexpected values (although less common). The Java virtual machine can freely perform instruction rearrangement and cache optimization without violating the JMM specification.
Mechanisms to ensure concurrency safety
In order to avoid data competition and ensure the correctness of concurrent programs, we need to use the synchronization mechanism provided by JMM to establish the Happens-Before relationship:
- synchronized keyword : used to implement a mutex lock to ensure that only one thread can execute a protected code block at the same time.
- volatile keyword : ensures the visibility of variables, that is, write operations on volatile variables will be immediately flushed to main memory, and read operations will be reloaded from main memory.
- java.util.concurrent package : Provides a rich set of concurrency tool classes, such as AtomicInteger, ConcurrentHashMap, CountDownLatch, etc., which have internally handled synchronization and memory visibility issues.
Analyze the original code example
Back to the original question code:
public class mainClass {
public static void main(String[] args) {
whatTime wt = new whatTime(); // 1. Create the object and get the reference threadA ta = new threadA(wt); // 2. Pass the reference to the new thread ta.start(); // 3. Start thread A
while (true) { // 4. The main thread enters an infinite loop // ...
}
}
}
public class threadA extends Thread {
private whatTime wt;
public threadA(whatTime wt) {
this.wt = wt; //Receive a copy of the reference}
public void run() {
while (true) {
try {
Thread.sleep(10000);
System.out.println("threadA: " wt.getTime()); // 5. Access shared objects} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class whatTime {
public long getTime() {
return System.currentTimeMillis(); // 6. Get system time}
}
In this particular code example, there are no concurrency issues . Here’s why:
- The wt reference is read-only : whatTime wt = new whatTime(); statement is executed in the main thread, and then the wt reference is passed to threadA. In mainClass or threadA, the wt reference itself has not been reassigned or modified, so it is an effectively final reference.
- whatTime objects are immutable or read-only : there is only one getTime() method in the whatTime class, which returns System.currentTimeMillis(). System.currentTimeMillis() is a static method. It does not access any member fields of the whatTime object, nor does it modify the internal state of the whatTime object. In other words, the whatTime object itself does not have any mutable shared state .
Since the wt reference is stable and the whatTime object itself has no mutable shared state, threadA's call to wt.getTime() will not cause any data race. JMM's thread start rules ensure that the wt reference is fully initialized before ta.start() and is visible to threadA. Therefore, this code is completely safe and works as expected.
Summarize
Understanding the separation of objects and references in Java is key to multi-threaded programming. Objects are stored in heap memory shared by all threads, and reference variables point to these objects. By passing a reference, multiple threads can access the same object. However, this shared access must be well managed through Java Memory Model (JMM)-defined synchronization mechanisms (such as synchronized, volatile, or java.util.concurrent tools) to ensure visibility and ordering of shared mutable state , thereby avoiding data races and unpredictable behavior. In the absence of shared mutable state, it is generally safe for multiple threads to access the same object.
The above is the detailed content of . For more information, please follow other related articles on the PHP Chinese website!
Hot AI Tools
Undress AI Tool
Undress images for free
AI Clothes Remover
Online AI tool for removing clothes from photos.
Undresser.AI Undress
AI-powered app for creating realistic nude photos
ArtGPT
AI image generator for creative art from text prompts.
Stock Market GPT
AI powered investment research for smarter decisions
Hot Article
Popular tool
Notepad++7.3.1
Easy-to-use and free code editor
SublimeText3 Chinese version
Chinese version, very easy to use
Zend Studio 13.0.1
Powerful PHP integrated development environment
Dreamweaver CS6
Visual web development tools
SublimeText3 Mac version
God-level code editing software (SublimeText3)
Hot Topics
20522
7
13634
4
How to configure Spark distributed computing environment in Java_Java big data processing
Mar 09, 2026 pm 08:45 PM
Spark cannot run in local mode, ClassNotFoundException: org.apache.spark.sql.SparkSession. This is the most common first step of getting stuck: even the dependencies are not correct. Only spark-core_2.12 is written in Maven, but spark-sql_2.12 is not added. SparkSession crashes as soon as it is built. The Scala version must strictly match the official Spark compiled version - Spark3.4.x uses Scala2.12 by default. If you use spark-sqljar of 2.13, the class loader cannot directly find the main class. Practical advice: Go to mvnre
How to safely map user-entered weekday string to integer value and implement date offset operation in Java
Mar 09, 2026 pm 09:43 PM
This article introduces a concise and maintainable way to map the weekday string (such as "Monday") to the corresponding serial number (1-7), and use the modulo operation to realize the forward and backward offset of any number of days (such as Monday plus 4 days to get Friday), avoiding lengthy if chains and hard-coded logic.
How to use Homebrew to install Java on Mac_A must-have Java tool chain for developers
Mar 09, 2026 pm 09:48 PM
Homebrew installs the latest stable version of openjdk (such as JDK22) by default, not the LTS version; you need to explicitly execute brewinstallopenjdk@17 or brewinstallopenjdk@21 to install the LTS version, and manually configure PATH and JAVA_HOME to be correctly recognized by the system and IDE.
What is exception masking (Suppressed Exceptions) in Java_Multiple resource shutdown exception handling
Mar 10, 2026 pm 06:57 PM
What is SuppressedException: It is not "swallowed", but actively archived by the JVM. SuppressedException is not an exception loss, but the JVM quietly attaches the secondary exception to the main exception under the premise that "only one exception must be thrown" for you to verify afterwards. It is automatically triggered by the JVM in only two scenarios: one is that the resource closure in try-with-resources fails, and the other is that you manually call addSuppressed() in finally. The key difference is: the former is fully automatic and safe; the latter requires you to keep it to yourself, and it can be written as shadowing if you are not careful. try-
How to correctly implement runtime file writing in Java applications (avoiding JAR internal write failures)
Mar 09, 2026 pm 07:57 PM
After a Java application is packaged as a JAR, data cannot be written directly to the resources in the JAR package (such as test.txt) because the JAR is essentially a read-only ZIP archive; the correct approach is to write variable data to an external path (such as a user directory, a temporary directory, or a configuration-specified path).
What is the underlying principle of array expansion in Java_Java memory dynamic adjustment analysis
Mar 09, 2026 pm 09:45 PM
ArrayList.add() triggers expansion because grow() is called when size is equal to elementData.length. The first add allocates 10 capacity, and subsequent expansion is 1.5 times and not less than the minimum requirement, relying on delayed initialization and System.arraycopy optimization.
Complete tutorial on reading data from file and initializing two-dimensional array in Java
Mar 09, 2026 pm 09:18 PM
This article explains in detail how to load an integer sequence in an external text file into a Java two-dimensional array according to a specified row and column structure (such as 2500×100), avoiding manual assignment or index out-of-bounds, and ensuring accurate data order and robust and reusable code.
A concise method in Java to compare whether four byte values are equal and non-zero
Mar 09, 2026 pm 09:40 PM
This article introduces several professional solutions for efficiently and safely comparing multiple byte type return values (such as getPlayer()) in Java to see if they are all equal and non-zero. We recommend two methods, StreamAPI and logical expansion, to avoid Boolean and byte mis-comparison errors.





