簡體   English   中英

為什么我們可以調用來自 Java 中另一個線程的對象的方法?

[英]WHY can we invoke a method of an object that is FROM another thread in Java?

我正在閱讀一個簡單的 Java 多線程聊天室。 在程序中,有一個叫做Chatroom的類,它有方法broadcast 該方法由另一個serverThread線程調用,並在原始線程(聊天室線程)中打印一些消息。

我對此完全困惑。 我的問題是:

  1. 怎么可能就這樣從另一個線程調用一個方法? 我們不是必須做某種“信號”或將某些東西放入共享數據空間,以便另一個線程中的方法可以自發地采取相應的行動嗎?
  2. 即使有可能。 為什么它不在調用者線程中輸出,而是在定義它的線程中輸出?
  3. 我猜一個更普遍的問題是:在多線程的情況下如何翻譯和執行代碼? OOP 只是讓我感到更加困惑。 (如果你能指出我更多的資源來查看,我將非常感激)

Java代碼

public class ChatRoom {

    private ArrayBlockingQueue<ServerThread> serverThreads; // List<ChatRoom.ServerThread>

    // Entrance of the place
    public static void main(String [] args)
    {
        new ChatRoom(6789);
    }

    public ChatRoom(int port)
    {
        try
        {
            System.out.println("Binding to port " + port);
            ServerSocket ss = new ServerSocket(port);
            serverThreads = new ArrayBlockingQueue<ServerThread>(5); // new ArrayList<>();
            while(true)
            {
                Socket s = ss.accept();   //  Accept the incoming request
                System.out.println("Connection from " + s + " at " + new Date());
                ServerThread st = new ServerThread(s, this); //connection handler
                System.out.println("Adding this client to active client list");
                serverThreads.add(st);
            }
        }
        catch (Exception ex) {
            System.out.println("Server shut down unexpectedly.");
            return;
        }

    }

    public void broadcast(String message)
    {
        if (message != null) {
            System.out.println("broadcasting ..." + message);
            for(ServerThread threads : serverThreads)
                    threads.sendMessage(message);
        }
    }

}


public class ServerThread extends Thread {

    private PrintWriter pw;
    private BufferedReader br;
    private ChatRoom cr;
    private Socket s;

    public ServerThread(Socket s, ChatRoom cr)
    {
        this.s = s;
        this.cr = cr;
        try
        {
            pw = new PrintWriter(s.getOutputStream(), true);
            br = new BufferedReader(new InputStreamReader(s.getInputStream()));
            start();
        }
        catch (IOException ex) {ex.printStackTrace();}
    }

    public void sendMessage(String message)
    {
        pw.println(message);
    }

    public void run()
    {
        try {
            while(true)
            {
                String line = br.readLine();
                //if(line == null) break; // client quits
                cr.broadcast(line);   // Send text back to the clients
            }
        }
        catch (Exception ex) {ex.printStackTrace();}
        finally {
            try {
                pw.close();
                br.close();
                s.close();
            }
            catch (Exception ex) {ex.printStackTrace();}
        }//finally
    }//run
}

這是輸出。 在我看來,“廣播消息”不是在ServerThread線程中打印的(順便說一句,我不知道如何顯示其輸出),而是在Chatroom線程中打印

輸出

所有線程都打印到只有一個標准輸出嗎?

System是類的名稱, System.out是該類的static成員的名稱。 在任何給定的時間點只能有一個System.out對象 — PrintStream對象。

通常,Java 運行時環境設置System.out以指向一些有用的位置,例如控制台窗口。 但是System.out不是final ,因此您的程序(或您的程序*調用的某個庫)可能會重新分配它以將輸出發送到其他地方。

在線程中調用對象的方法時,是否所有代碼都像在該線程“內部”一樣執行?

是的。 這就是線程所做的。 他們執行你的代碼。 每個線程開始在某個Runnable實例的run()方法中執行您的代碼,並且Runnable繼續執行您的代碼告訴它執行的任何操作,直到 (a) 它到達run()方法的末尾,或者(b) 它拋出一個未被捕獲的異常。

不過,我不會說“內部”。 線程不是容器。 線程“內部”沒有任何東西,盡管通常有一些其他線程不訪問或無法訪問的變量(例如,線程調用的所有函數的所有局部變量)。


* 圖書館有可能這樣做,但圖書館這樣做將是一件非常粗魯的事情,除非文檔非常清楚會發生什么。

該方法由另一個 serverThread 線程調用,並原始線程(聊天室線程)中打印一些消息。

這是一個有點誤導性的陳述,具體取決於您對“in”一詞的含義。 每個ServerThread對象都通過ChatRoom對象並回調ChatRoom.broadcast(...)方法。 不要因為它們是線程而感到困惑。 這只是一個對象調用另一個對象的方法。

為什么它不在調用者線程中輸出,而是在定義它的線程中輸出?

因為它是調用者線程進行調用。 僅僅因為您在線程方法之間來回調用並不意味着不同的線程在進行調用。 該練習試圖令人困惑。 同樣,這些只是調用其他對象的對象,這里沒有魔術線程信號。

在多線程的情況下如何翻譯和執行代碼?

無論您是否有多個線程,代碼在 Java OOP 中的工作方式都是相同的。 如果 ThreadA 正在運行並調用chatRoom.broadcast("foo")那么它是 ThreadA 執行該方法。 如果該方法轉向並對每個serverThread.sendMessage(foo)進行大量調用,那么它仍然是 ThreadA 正在運行並進行這些方法調用。 您實際上必須做很多工作才能在線程之間傳遞控制權。

怎么可能就這樣從另一個線程調用一個方法? 我們不是必須做某種“信號”或將某些東西放入共享數據空間,以便另一個線程中的方法可以自發地采取相應的行動嗎?

僅僅因為一個線程調用另一個線程的方法並不意味着存在任何信令或自動數據共享。 這在很大程度上取決於正在訪問的數據。

讓我們看看調用的每個部分。 ThreadA 回調chatroom.broadCast(...) 在該方法中, message可以被訪問,因為它被傳入, System.out.println(...)是一個synchronized PrintStream ,它也可以使用,並且ArrayBlockingQueue也是synchronized因此可以工作。 Chatroom中沒有未受保護的字段被訪問。 然后,您必須評估對ServerThread.sendMessage(...)的調用,它使用具有內部鎖的PrintWriter 所以這是猶太潔食。 同樣,如果對每個ServerThread對象的本地字段有一些訪問,則需要以某種方式鎖定它。

OOP 只是讓我感到更加困惑。

我認為這是一個旨在在這種情況下令人困惑的問題,我認為這是一個學術練習。 就如何弄清楚它在做什么而言,我會打印出Thread.currentThread().getId()以便您可以看到哪個線程正在打印什么。 我還會減少線程數,然后將其放大,直到您可以遵循它。

逐行進行並記住線程不會將控制權移交給其他線程,而無需您會看到的特定調用。

警告:練習中有幾個錯誤需要修復。 希望下面的內容不會讓您感到困惑,因為這有點像是校隊的話題。

  1. ServerThread中的所有字段都應標記為final final強制在ServerThread構造函數完成之前 完全初始化字段 這非常重要,否則新的ServerThread可能會在正確設置之前訪問它自己的字段。 是的,這是瘋狂的東西,但這是語言定義的一部分。 就此而言,讓Chatroom.serverThreads也是final將是一個很好的模式。 如果可能,在構造函數中初始化的任何內容都應該是final的。
  2. 作為規則和上一點的延續,你永遠不應該在它自己的構造函數中start()一個線程 您冒着讓線程在初始化之前訪問它自己的字段或讓其他線程訪問未初始化的字段的風險。 構造對象然后對其調用start()是正確的做法。
  3. Chatroom對象在其構造函數中也做得太多了。 它不應該在其構造函數中創建其他線程,然后回調可能未初始化的Chatroom 構造套接字和serverThreads應該在構造函數中,但是在構造函數完成后,創建新服務器線程的while循環應該在另一個方法中完成。

希望這可以幫助。

暫無
暫無

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

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