简体   繁体   中英

Is it good practice to pass opened resources to threads?

I have a class MesssageReader which reads messages from the stream in a separate thread. Now, when I first created the solution I've created the stream object outside of the thread class, and then passed it as a reference to the MessageReader class. I'm passing it via my custom interface since at some point I want to maybe change the stream reader class. The reader thread ends gracefully if it receives a special message indicating the end of the message stream. The main thread which created the stream, closes it. Now, my concern here is that this is a bad design? Since, if I create a stream outside of the reader thread someone else might close it and it could cause problems. But, at the other hand, I've created the socket also outside of the reader thread, which makes sense? since, you could create more threads which read messages from the same connection. Is it better to create the wanted stream outside of the reader thread or inside, and let the thread handle the stream opening and closing(But still have the flexibility of cretaing different stream options/objects)? Here is my example class:

public class MessageReader implements Runnable {
    private MessageInputReader reader;
    /* Custom interface for reading messages */
    public MessageReader(MessageInputReader reader) {
        this.reader = reader;
    }

    public void run() {
        String line = null;
        try {
            while ((line = reader.readMessage()) != null) {
                if ("BYE".equals(line)) {
                    break;
                }
                System.out.println(line);
            }
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }
    }
}
public class ReaderExample {

    public static void main(String[] args) {
        Socket clientSocket = null;
        MessageBufferStream bufferedStream = null;
        MessageReader reader;
        try {
            clientSocket = new Socket("hostname", 4060);
            bufferedStream = new MessageBufferStream(clientSocket);
            reader = new MessageReader(bufferedStream);
            (new Thread(reader)).start();
            someBlockingMethodWhichDoesOtherWork();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (bufferedStream != null)
                    bufferedStream.closeStream();
                if (clientSocket != null)
                    clientSocket.close();
            } catch (IOException e) {
                System.out.println(e.getMessage());
            }
        }
    }
}

And here is the second option:

public class MessageReader implements Runnable {
    private MessageBufferStream reader;

    public MessageReader(Socket clientSocket) throws IOException {
    /* I could probably create a factory method here which would
     * return different stream objects and achieve the same flexibility as in the first example? */
        this.reader = new MessageBufferStream(clientSocket);
    }

    public void run() {
        String line = null;
        try {
            while ((line = reader.readMessage()) != null) {
                if ("BYE".equals(line)) {
                    break;
                }
                System.out.println(line);
            }
        } catch (IOException e) {
            System.out.println(e.getMessage());
        } finally {
            try {
                if (reader != null)
                    reader.closeStream();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

And here is the rest of the code:

public interface MessageInputStream {
    public String readMessage() throws IOException;
    public void closeStream() throws IOException;
}

public class MessageBufferStream implements MessageInputStream {
    private BufferedReader reader;

    public MessageBufferStream(Socket clientSocket) throws IOException {
        this.reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
    }

    @Override
    public String readMessage() throws IOException {
        return reader.readLine();
    }

    @Override
    public void closeStream() throws IOException {
        reader.close();
    }
}

It's not good or bad practice. It's not so much a question of practice as it is life cycle management. If you take care to control the management of a resource that is being passed to a thread and ensure that it is open before it is used and never closed while it is still needed, then you're fine.

Documentation is the best practice here. Make it clear what is expected and what has to happen so that when you or somebody else look at it later, you don't get into trouble. Where you open and close the resource, explain that. In your thread code or wherever, document pre and post conditions and any assumptions made about the resource life cycle. Etc.

Now this of course assumes a few things about said resource. In particular, that it is either threadsafe or that you are taking care not to access it from more than one thread at the same time via other means (actual synchronization or simply design invariants), and also that said resource can be used on a different thread than the one on which it was constructed (for that you'd have to consult it's documentation or source. Eg If the resource allocates and initializes thread local storage on construction, you may have no choice but to open it on the thread itself.

Now, when I first created the solution I've created the stream object outside of the thread class, and then passed it as a reference to the MessageReader class. ... my concern here is that this is a bad design?

Possibly. You certainly need to create the Socket outside of the thread but I think that starting the stream on that socket might be better done inside of the thread. The main thread might keep a reference to the Socket (see below) but the forked thread handles the opening of the stream on the socket and would also be responsible for closes the stream when the messages have finished.

Since, if I create a stream outside of the reader thread someone else might close it and it could cause problems.

You can certainly write the main thread code so that it forgets about the stream object if that is a problem. The main thread might want to keep knowledge of the Socket around which sometimes is a good idea. If you want to stop the processing on a socket because some timeout has expired or the application is shutting down, the main thread could then close the socket which would cause IO methods to throw IOException giving your thread an opportunity to cleanly shutdown. This is a well used pattern.

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