簡體   English   中英

在 java 中運行的線程是 object

[英]In java in which thread is an object runned

我不明白為什么一個線程能夠調用一個 object 的方法,該方法在另一個處於 while true 循環中的線程中被刪除。 根據我的基本知識,如果線程處於 while true 循環中,您將無法與其交互,因此即使在該線程中刪除了 object。

謝謝你的建議。

這是主要的 class

/**
 * main
 */
public class mainClass {
   public static void main(String[] args) {
      whatTime wt = new whatTime();
      threadA ta = new threadA(wt);
      ta.start();

      while (true) {
         
      }

   }
}

這是線程 A class

/**
 * threadA
 */
public class threadA extends Thread {
   private whatTime wt;
   
   public threadA(whatTime wt) {
      System.out.println("threadA() constructor");
      this.wt = wt;
   }

   public void run() {
      while (true) {
         //every 10s
         try {
            Thread.sleep(10000);
            System.out.println("threadA: " + wt.getTime());
         } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
         }
      }

   }
}

這是我使用的 object

public class whatTime {
   public whatTime() {
      System.out.println("whatTime() constructor");
   }

   public long getTime() {
      return System.currentTimeMillis();
   }

}

我不明白為什么一個線程能夠調用在另一個線程中刪除的 object 的方法

我想你誤會了。

 whatTime wt = new whatTime();

這做了兩件完全不同的事情。

在本地空間(在堆棧中,事實上,不能與其他線程交互,無論如何 - 該方法返回后立即發生的任何事情都會立即重用該堆棧,所以如果任何其他線程敢於嘗試,你就是麻煩大了)——它聲明了一個引用變量 這通常是一個 64 位值,它告訴 JVM 在堆中的何處找到 object。 它不是存儲在wt中的 whatTime 本身的實例! - 它只是對它的引用 想想:“地址簿中的一頁,上面有房子的地址”,而不是:“房子”。 whatTime的一個實例是房子, wt是地址簿頁面。

其他線程無法與地址簿頁面進行交互 - 充其量,您可以復制它,然后將副本交給另一個線程。 但他們可以拿走地址簿頁面的副本,開車過去,通過 window 扔磚頭。如果那天晚些時候你也開車過去,你會看到損壞的 window。

new WhatTime() - “建造”房子,並將房子的地址分配給wt變量。

 threadA ta = new threadA(wt);

這創建了一個新的threadA實例(並將它的地址記錄到局部變量空間),並將地址簿頁面的副本交給它(java 始終是傳遞副本,但wt是參考,而不是 object 本身)。 您現在有一個地址簿頁面,上面有“123 Fairfield lane”, threadA也是如此 - threadA 有自己的私有副本。 threadA對該地址簿頁面所做的任何操作都不會影響您的地址簿。 但是,如果他們決定撥打 go 到該地址的房子 - 您也可以看到。

根據我的基本知識,如果線程處於 while true 循環中,則您無法與之交互

在你說“在一段時間內真正循環”的地方,你真正的意思是“如果它正在運行”。 它是否在循環並不重要,重要的是它在運行。

這指的是 memory model。現代 CPU根本無法讀取或寫入 memory。 因為 memory 總線太遠了,那些電子以大約 60% 的光速或類似的速度穿過主板上的線路,這意味着它就像相對於 CPU 的慢糖蜜,這就是為什么他們不能這樣做的原因。 相反,有一小塊非常快的 memory 直接在核心上,分成幾個具有設定大小的“頁面”(大約 64k - 取決於你的處理器),CPU 唯一能做的就是告訴memory controller: Go 將此頁寫回到主 memory(全部 64k)的這個地址,然后將其擦除並替換為從 A 到 B 的主 memory 庫的內容。我會等待。 它的所有 1000 個周期,因為,男孩,我需要等待很長時間才能讓你做這些事情,因為 memory 銀行太遠了。

鑒於這就是 CPU 的工作方式,java 需要“聲明”一些權利,因為如果不這樣做,它將運行得非常慢。 它聲稱的權利之一是重新排序和本地緩存。

JVM可能會將您從堆中獲得的任何內容(因此任何非本地)緩存在 CPU 內核的片上緩存中。 這意味着如果您有 1 個字段,並且有 2 個線程同時讀取和寫入該字段,則每個線程實際上可能只是寫入其本地副本,而 JVM 並沒有具體說明如何將其合並回 memory -通常,一些任意頁面寫入“獲勝”並且都是不可靠的。

換句話說,如果 2 個線程正在訪問同一個堆(同一個字段),您的代碼就會被破壞:取決於 CPU、體系結構、在您執行操作時音樂播放器上播放的音樂,或月相- 你得到一個或另一個結果。 哎喲。

為了首先使線程化成為可能,JMM(Java Memory 模型)定義了某些特定的操作來建立“Happens-Before”。 如果根據 JMM 動作 A“發生在”動作 B 之前,那么 B 行及其后的任何行將無法觀察到 state,就像 A 運行之前那樣。 本質上,如果 A“發生在(根據 JMM)”B 之前,那么 A 確實發生在 B 之前,除非B 不可能觀察到這一點,在這種情況下,JVM 仍然可以按照它想要的任何順序自由運行這些事情.

建立 HB 可以通過synchronizedvolatile ,各種特殊動作來完成( thread.start()是 HB 相對於該線程內的第一行,例如),當然也可以通過使用(核心)庫函數來完成這些事情,例如ConcurrentHashMap和 juc package 中的許多其他東西。

這大概是您閱讀和誤解的內容:您不應該交互(讀取或寫入)另一個已經啟動但尚未死亡但也與之交互的線程的任何字段,除非您仔細管理此訪問並確保所有可能的交互可能受到 HB/HA 關系的保護,因此您的代碼每次都會以相同的方式運行,並且不會變成一些瘋狂的邪惡硬幣翻轉游戲,它取決於眾所周知的蝴蝶的翅膀。 如果您違反規則,並且您從 2 個單獨的線程與同一字段交互而沒有建立 HB/HA——那么“它有效”(沒有直接崩潰,沒有拋出異常,代碼編譯正常,並且會運行)——但是結果或多或少是任意的:

class Example {
  int x;

  void crazy() {
    x = 1;
    new Thread(() -> x = 5).start();
    new Thread(() -> x = 10).start();
    System.out.println(x);
  }
}

上面的代碼可以合法地打印 1、510。JVM 工作正常並且滿足規范,無論它恰好返回哪一個- 並且 JVM 也不保證它是隨機的(始終返回的 JVM 1 在這里,很好。一個 JVM 總是返回 1 除了它在第 3 個月的第 5 個星期一返回 10 - 很好)。 這樣寫的代碼就是問題所在。 沒有辦法知道,真的。 基本上不可能對其進行測試(假設始終返回 1 的 JVM 在這里也很好)。 您可以運行它的絕大多數方式(JVM 供應商、版本、操作系統、體系結構等的組合)將打印“1”,但“10”不會不正確。

在此特定代碼中,完全沒有問題 - wt引用被寫入一次,這是相對於線程的 HB(因為它先於t.start() ),因此訪問引用不是問題,因為它永遠不會改變,並且System.currentTimeMillis()不訪問任何字段,因此對.getTime()的調用也沒有問題。

因此,這段代碼有效……就好了。 沒有什么問題。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM