為什么子線程可以訪問主線程中的局部變量?

為什么子線程可以訪問主線程中的局部變量?

Java線程局部變量訪問機制詳解

java多線程編程中,理解局部變量的訪問方式至關(guān)重要。本文將深入探討子線程如何訪問主線程局部變量,并闡明其背后的機制。

問題場景

考慮以下代碼片段:

public static void main(String[] args) {     Point point = new Point(0, 0);     Runnable runnable1 = () -> System.out.println(point); // 位置1     Runnable runnable2 = () -> System.out.println(point); // 位置2     Thread thread1 = new Thread(runnable1);     Thread thread2 = new Thread(runnable2);     thread1.start();     thread2.start(); }

位置1和位置2的匿名內(nèi)部類(Lambda表達式)都能訪問主線程的局部變量point。這并非因為線程間共享了point,而是因為Java的閉包機制值傳遞

閉包與值傳遞

java編譯器在編譯時,會對匿名內(nèi)部類或lambda表達式進行特殊處理。如果這些代碼塊引用了外部方法的局部變量,編譯器會隱式地將這些變量轉(zhuǎn)換為effectively final(事實上是最終的)。這意味著這些變量在創(chuàng)建后不能被修改。

然而,這并不意味著變量被共享。Java采用的是值傳遞機制。當(dāng)匿名內(nèi)部類或lambda表達式被創(chuàng)建時,point變量的值會被復(fù)制一份到該代碼塊的上下文中。每個線程都擁有point變量的一個獨立副本。因此,線程之間不會發(fā)生數(shù)據(jù)競爭。

封閉

為了更清晰地理解,我們可以引入棧封閉的概念。每個線程都有自己的棧空間,局部變量存儲在各自線程的棧中。即使多個線程訪問同一個局部變量,它們實際操作的是各自棧中獨立的副本。

代碼示例驗證

為了進一步驗證,我們使用AtomicReference進行測試:

public static void main(String[] args) {     AtomicReference<User> user = new AtomicReference<>(new User());     Runnable runnable = () -> user.set(new User("name1"));     Thread thread1 = new Thread(runnable);     Thread thread2 = new Thread(() -> user.set(new User("name2")));     thread1.start();     thread2.start();     System.out.println(user.get()); // 輸出可能不是"name1"或"name2" }

即使user是AtomicReference,子線程修改其值也不會影響主線程的輸出。因為user的引用本身是被復(fù)制的,子線程修改的是其副本,主線程持有的是原始引用。

總結(jié)

子線程能夠訪問主線程的局部變量,并非因為變量共享,而是因為Java的閉包機制和值傳遞機制。編譯器會創(chuàng)建局部變量的副本,每個線程操作的是自己的副本,從而避免了數(shù)據(jù)競爭,保證了線程安全。 這體現(xiàn)了Java局部變量的棧封閉特性。 需要注意的是,如果局部變量是可變對象,子線程修改該對象的屬性,則會影響到主線程,因為它們共享的是同一個對象的引用。

? 版權(quán)聲明
THE END
喜歡就支持一下吧
點贊10 分享