简体   繁体   中英

Why does Java send needs Thread.Sleep

I am implementing a Server/Client system using Java. the server listens for incoming connection from clients and after a client connects, the server creates a new socket and passes it to a new thread which will only be used for receiving data:

while (true){

        clientSocket=serverSocket.accept();
        new ClientReceiver(clientSocket,this.clientsManager).start();
    }

the clientReceiver class is as follows:

public class ClientReceiver extends Thread {

private Socket clientSocket=null;
private Client client=null;
private ClientsManager clientsManager;

private ClientActionParser clientActionParser=new ClientActionParser();

ClientHandlerState clientHandlerState;

PrintWriter outputStream=null;
BufferedReader inputStream=null;

public ClientReceiver(Socket clientSocket, ClientsManager clientsManager){

    this.clientSocket=clientSocket;
    this.clientsManager=clientsManager;
    this.setClientHandlerState(ClientHandlerState.Connected);
}

public void run(){

    String actionString;
    try{
        //define output and input stream to client
        outputStream =new PrintWriter(clientSocket.getOutputStream(),true);
        inputStream = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));



        //while client is connected read input to actionString
        while((actionString=inputStream.readLine()) != null){

                AbstractClientAction clientAction= this.clientActionParser.parse(actionString);

                if(this.getClientHandlerState()==ClientHandlerState.Connected){

                    if(clientAction instanceof ClientLoginAction){

                        ClientLoginAction clientLoginAction=(ClientLoginAction) clientAction;
                        if(this.authenticate(clientLoginAction)){


                        }
                        else{
                                throw new AuthenticationException();
                        }

                    }
                    else{

                        throw new AuthenticationException();
                    }
                }

            }
            if(this.getClientHandlerState()==ClientHandlerState.Authorized){

                //receive other client actions: transfer barge ....
            }
            try {
                Thread.sleep(400);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
    }
    catch(IOException e){

    }
    catch (AuthenticationException e) {
        // TODO: handle exception
    }

    //clean up the resources
    try{
        outputStream.close();
        inputStream.close();
        clientSocket.close();
    }
    catch(Exception e){

    }
}

private boolean authenticate(ClientLoginAction clientLoginAction){
    //perform authentication. If authentication successful:
    this.client=this.clientsManager.authenticateClient(clientLoginAction.getUsername(), clientLoginAction.getPassword());
    if(this.client==null){
        return false;
    }
    else{
        ClientSender clientSender=new ClientSender(this.outputStream, this.client);
        this.clientsManager.addClientSender(clientSender);
        this.setClientHandlerState(ClientHandlerState.Authorized);
        clientSender.start();
        return true;
    }
}

public ClientHandlerState getClientHandlerState(){

    return this.clientHandlerState;
}

public void setClientHandlerState(ClientHandlerState clientHandlerState){

    this.clientHandlerState=clientHandlerState;
}

after successful authentication in the receiver thread, a new thread is created for sending data to client and socket's outputStream is passed to the new thread. the clientSender class contains a queue as a buffer which contains the data that should be sent to the client. here is class clientSender:

public class ClientSender extends Thread {

private Client client=null;
private final Log logger = LogFactory.getLog(getClass());
PrintWriter outputStream=null;
private Queue<String> clientEventsQueue= new LinkedList<String>();

public ClientSender(PrintWriter outputStream, Client client){
    this.outputStream=outputStream;
    this.client=client;
}

public void run(){

    //System.out.println("ClientSender run method called.");

    while(true){

        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }


        if(!this.clientEventsQueue.isEmpty()){

            this.outputStream.println(this.clientEventsQueue.remove());
        }
    }
}

public Client getClient(){

    return this.client;
}

public void insertClientEvent(String clientEvent){

    this.clientEventsQueue.add(clientEvent);
}

whenever I want to send something to the client I use:

clientSender.insertClientEvent("some text");

the problem is that if I remove Thread.sleep(10) I will not receive anything in the client side. Since TCP sockets are blocking I think this should not happen. Is this normal or am I doing something wrong?

EDIT: there is no "termination" for the sender thread. the server should send proper information to all clients whenever it receives an event from another system. so I think the best scenario is to stop the thread when there is no data to send and start it whenever there is. so I tried this in the clientSender class:

public void run(){

    while(true){

        if(this.clientEventsQueue.isEmpty()){
            break;
        }
        else{
            try {
                this.outputStream.println(this.clientEventsQueue.take());
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}

but now the problem is when to start the thread? I tried starting it whenever I want to send data but as I expected it does not work properly and only sends the fist package:

clientSender.insertClientEvent(clientEvent.getEventString());
clientSender.start();

EDIT2 I came up with this idea. It is very simple and I think it consumes so much less CPU time.

while(true){

        while(this.clientEventsQueue.isEmpty()){

            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

        try {
            this.outputStream.println(this.clientEventsQueue.take());
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

as much as I tested it, it worked just fine. what do you think about it?

I see that you are using a LinkedList as a queue accessed by multiple threads and that you are doing a busy wait on it in the ClientSender .
This implementation is not thread safe and may cause problems like clientEvents posted on it not being made visible to the ClientSender thread, CPU being wasted etc.

You could use a BlockingQueue instead and call take() on it to block on the queue until an event is posted.

I also see that you are relying on sleep(400) to wait for the communication. That will also cause issues. The thread that is using the socket resource can close it when it's done, instead of this.

EDIT:
There are number of techniques to deal with terminating the thread. I think a "poison pill" will work well in this case. Basically you do:

String stuff = queue.take();
if (stuff == null) break;

and post null on the queue when you want to terminate it (doesn't have to be null, can be anything eg "terminate" etc.

EDIT2:
Your way of terminating won't work, as it will terminate immediately before anyone can post an event on it. Theoretically you could be spawning and then immediately killing the thread over and over etc. The easiest way to cope with this is to use a special message (aka "poison pill") for the termination condition.

As for having a thread only when there is an event, at that point I'd recommend using a thread pool. You could encapsulate the event sending into a Runnable , and hold the sockets in a Map. This is however quite complicated to implement and requires good understanding of multithreading to get it right. Multithreading is hard and can introduce severe headache when done wrong. Tbh I wouldn't recommend to try doing this without studying more about multithreaded programming.

EDIT3: @user2355734: Polling the queue with an interval as you did is done by many people, but is discouraged. The take() method will in effect "sleep" and only wake up if there is something on the queue, so in theory by removing the "sleep" loop you should get even lower CPU usage and shorter latency. In general, you should try to avoid using "sleep" altogether in multithreaded code. It's rare that you genuinely need it and it's frequently a sign of broken/suboptimal code. As for tests, although they are useful it's hard or even pretty much impossible to guarantee correctness of multithreaded code through tests. Your code may run fine in your tests yet fail in production, under high load, under different environment etc. Therefore it's important to review the code and make sure it's theoretically correct.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM