I was reading a simple multi-threaded chatroom in Java. In the program, there's a class called Chatroom
, which has method broadcast
. The method was called by another serverThread
thread, and it printed some messages in the original thread (the chatroom thread).
I am totally confused by this. My questions are:
- How is it even possible to just call a method from another thread just like that? Don't we have to do some kind of "signal" or putting something into a shared data space so that methods in another thread can spontaneously act accordingly?
- Even if it is possible. Why does it output not in the caller thread but in the thread in which it is defined?
- A more general questions is I guess: how are code translated and executed in case of multi-threading? OOP just makes things a lot more confusing for me. (if you can point me to more resources to look at, I would be incredibly grateful)
Java Code
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
}
And here is the output. It seems to me that the "broadcasting messages" are printed not in ServerThread
thread (which btw I don't know how to show the output of), but in Chatroom
Thread
CodePudding user response:
is there only one stdout that all threads print to?
System
is the name of a class, and System.out
is the name of a static
member of that class. There can be only one System.out
object—a PrintStream
object—at any given point in time.
Normally, the Java runtime environment sets up System.out
to point to some useful place such as a console window. But System.out
is not final
, so your program (or some library called by your program*) potentially could reassign it to send output somewhere else.
when calling an object's method in a thread, does all of its code get executed as if they are "inside" that thread?
Yes. That's what threads do. They execute your code. Each thread starts executing your code in the run()
method of some Runnable
instance, and it continues to do whatever your code tells it to do from that point on until either (a) it reaches the end of the run()
method, or (b) it throws an exception that is not caught.
I would not say "inside" though. A thread is not a container. There's nothing "inside" a thread, though there usually are some variables (e.g., all of the local variables of all of the functions that the thread calls) that other threads either do not or cannot access.
* It is possible for a library to do that, but it would be a really rude thing for the library to do unless the documentation was very clear about what would happen.
CodePudding user response:
The method was called by another serverThread thread, and it printed some messages in the original thread (the chatroom thread).
This is a somewhat misleading statement depending on what you mean by the word "in". Each of the ServerThread
objects gets passed the ChatRoom
object and are calling back to the ChatRoom.broadcast(...)
method. Don't be confused by them being threads. This is just one object calling a method on the other.
Why does it output not in the caller thread but in the thread in which it is defined?
Because it is the caller thread which is making the call. Just because you are calling back and forth between the thread methods doesn't mean that different threads are making the calls. The exercise is trying to be confusing. Again, these are just objects calling other objects and there is no magic thread signaling going on here.
how are code translated and executed in case of multi-threading?
Code works the same way in Java OOP regardless if you have multiple threads or not. If ThreadA is running and calls chatRoom.broadcast("foo")
then it is ThreadA which executes that method. If that method turns around and makes a bunch of calls to each of the serverThread.sendMessage(foo)
then it is still ThreadA which is running and making those method calls. You actually have to do a lot of work to hand off control between threads.
How is it even possible to just call a method from another thread just like that? Don't we have to do some kind of "signal" or putting something into a shared data space so that methods in another thread can spontaneously act accordingly?
Just because the one thread calls a method on the other does not in any way imply that there any signaling or automatic data sharing. It depends highly on what data is being accessed.
Let's look at every part of the calls. ThreadA calls back to chatroom.broadCast(...)
. In that method message
is ok to be accessed because it is passed in, System.out.println(...)
is a synchronized
PrintStream
which is also ok so it can be used, and the ArrayBlockingQueue
is also synchronized
so that works. There are no unprotected fields from Chatroom
being accessed. Then you have to evaluate the call back to ServerThread.sendMessage(...)
which uses a PrintWriter
which is has an internal lock. So this is kosher. Again, if there was some access of a field that was local to each of the ServerThread
objects, it would need to be locked somehow.
OOP just makes things a lot more confusing for me.
I think it is the question which is designed to be confusing in this case I assume it is an academic exercise. In terms of how you can figure out what it is doing, I would print out Thread.currentThread().getId()
so you can see which thread is printing what. I would also reduce the number of threads and then scale it back up until you can follow it.
Going line by line and remembering that threads don't hand off control to other threads without specific calls that you would see.
WARNING: there are a couple of bugs in the exercise that should be fixed. Hopefully the following doesn't confuse you because this is somewhat varsity thread stuff.
- All of the fields in the
ServerThread
should be marked asfinal
.final
forces the fields to be fully initialized before theServerThread
constructor finishes. This is very important because otherwise the newServerThread
might access it's own fields before they are properly setup. Yeah this is crazy stuff but part of the language definition. For that matter, it would be a good pattern to haveChatroom.serverThreads
also befinal
. Anything initialized in the constructor should befinal
if possible. - As a rule and a continuation to the previous point, you should never
start()
a thread inside it's own constructor. You run the risk of having the thread access it's own fields before they are initialized or to have other threads access uninitialized fields. Constructing the object and then callingstart()
on it is the right thing to do. - The
Chatroom
object also is doing too much in its constructor. It should not be creating the other threads in its constructor which then call back to theChatroom
which might not be initialized. Constructing the socket and theserverThreads
should be in the constructor but thewhile
loop which creates the new server threads should be done in another method after the constructor finishes.
Hope this helps.