我正在閱讀一個簡單的 Java 多執行緒聊天室。在程式中,有一個名為 的類Chatroom,它有方法broadcast。該方法被另一個serverThread執行緒呼叫,并在原始執行緒(聊天室執行緒)中列印了一些訊息。
我對此完全困惑。我的問題是:
- 怎么可能就這樣從另一個執行緒呼叫一個方法?我們不是必須做某種“信號”或將某些東西放入共享資料空間,以便另一個執行緒中的方法可以自發地采取相應的行動嗎?
- 即使有可能。為什么它不在呼叫者執行緒中輸出,而是在定義它的執行緒中輸出?
- 我猜一個更普遍的問題是:在多執行緒的情況下如何翻譯和執行代碼?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執行緒中列印
輸出
uj5u.com熱心網友回復:
所有執行緒都列印到只有一個標準輸出嗎?
System是類的名稱,并且System.out是該類的static成員的名稱。在任何給定的時間點只能有一個System.out物件——一個PrintStream物件。
通常,Java 運行時環境設定System.out為指向一些有用的地方,例如控制臺視窗。但System.out不是final,因此您的程式(或您的程式* 呼叫的某個庫)可能會重新分配它以將輸出發送到其他地方。
在執行緒中呼叫物件的方法時,是否所有代碼都像在該執行緒“內部”一樣執行?
是的。這就是執行緒所做的。他們執行你的代碼。每個執行緒開始在run()某個Runnable實體的方法中執行您的代碼,并且從那時起繼續執行您的代碼告訴它執行的任何操作,直到 (a) 到達run()方法的末尾,或 (b) 拋出例外那沒有被抓住。
不過,我不會說“內部”。執行緒不是容器。執行緒“內部”沒有任何東西,盡管通常有一些其他執行緒不訪問或無法訪問的變數(例如,執行緒呼叫的所有函式的所有區域變數)。
*圖書館有可能這樣做,但圖書館這樣做將是一件非常粗魯的事情,除非檔案非常清楚會發生什么。
uj5u.com熱心網友回復:
該方法由另一個 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具有內部鎖的a 的回呼。所以這是猶太潔食。同樣,如果對每個ServerThread物件的本地欄位有一些訪問,則需要以某種方式鎖定它。
OOP 只是讓我感到更加困惑。
我認為這是一個旨在在這種情況下令人困惑的問題,我認為這是一個學術練習。至于你如何弄清楚它在做什么,我會列印出來,Thread.currentThread().getId()這樣你就可以看到哪個執行緒正在列印什么。我還會減少執行緒數,然后將其放大,直到您可以遵循它。
逐行執行并記住執行緒不會將控制權移交給其他執行緒,而無需您會看到的特定呼叫。
警告:練習中有幾個錯誤需要修復。希望下面的內容不會讓您感到困惑,因為這有點像是校隊的話題。
- 中的所有欄位
ServerThread都應標記為final。final強制在ServerThread建構式完成之前完全初始化欄位。這非常重要,否則新的ServerThread可能會在正確設定之前訪問它自己的欄位。是的,這是瘋狂的東西,但這是語言定義的一部分。對于這個問題,這將是有一個良好的模式Chatroom.serverThreads也可以final。final如果可能的話,應該在建構式中初始化任何東西。 - 作為規則和上一點的延續,你永遠不
start()應該在它自己的建構式中使用執行緒。您冒著讓執行緒在初始化之前訪問它自己的欄位或讓其他執行緒訪問未初始化的欄位的風險。構造物件然后呼叫start()它是正確的做法。 - 該
Chatroom物件在其建構式中也做得太多了。它不應該在其建構式中創建其他執行緒,然后回呼Chatroom可能未初始化的 。構造套接字并且serverThreads應該在建構式中,但是while在建構式完成后,應該在另一個方法中完成創建新服務器執行緒的回圈。
希望這可以幫助。
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/356493.html
