在 Thread 上调用方法 - 它有效,但为什么呢?

Calling a method on a Thread - it works, but why?

我有以下 Thread 子类(为了便于阅读而稍微简化):

public class ConnectionHandlerThread extends Thread {
    private Socket socket;
    private volatile boolean disconnected = false;
    private ObjectOutputStream out = null;
    private ObjectInputStream in = null;

    public ConnectionHandlerThread(Socket socket){
        this.socket = socket;
    }

    public void run(){
        disconnected = false;

        try {           
            out = new ObjectOutputStream(socket.getOutputStream());
            in = new ObjectInputStream(socket.getInputStream());

            while(!disconnected){
                try{
                    Object data = in.readObject();                  
                }
                catch(ClassNotFoundException e){
                    // Handle exception
                }               
                catch(IOException e){
                    // Handle exception
                }
            }
        } 
        catch (IOException e) {
            // Handle exception
        }

    }

    public void send(Object data){
        try{
            out.writeObject( data );
        }
        catch(IOException e){
            // Handle exception
        }       
    }
}

当我连接到服务器时,我的客户端(使用 Swing GUI)创建了这个线程的一个实例。我觉得奇怪的是我可以从主 GUI 调用方法 send(Object data),并且它有效。为什么无限 while 循环 and/or 调用 in.readObject() 不阻止我这样做?我的意思是线程不应该一直忙于做其他事情吗?这是做事的好方法吗?如果不是,为什么不呢?

编辑

澄清让我困惑的地方:如果这是在主线程中,它会忙于那个 in.readObject() 直到读取某些内容,然后它会在循环的下一次迭代中再次开始监听.当然,我知道我可以从另一个线程调用 send() 。但我的想法是 - "who" 实际上正在执行 send()?是我的线程在做,还是调用线程在做?如果是前者,怎么会在执行send()方法的while循环中都忙着等待输入呢?我只是很难全神贯注...

有两件事:

1) 无限循环不会让 cpu 只忙于自身。它只是在它可用时一直保持忙碌,但我也使用它的其他线程。

2) 当你打电话给你的

send(Object data)

你不会在你的线程中这样做,所以请记住 1) 它被调用没有什么奇怪的

示例:

代码:

public class Main {
    public static void main(String[] args) {
        InfiniteThread t = new InfiniteThread();
        t.start();
        for(int i=0;i<10;i++) {
            t.fromOut(i);
        }
    }
    @DebugLog
    public static class InfiniteThread extends Thread {

        public void run() {
            for(int i=0;i<10;i++) {
                fromIn();
            }
        }

        private void fromIn() {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        public void fromOut(Object data){
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

输出:

InfiniteThread :: ⇢ () [Thread:"main"] InfiniteThread :: ⇠ [0ms] InfiniteThread :: ⇢ run() [Thread:"Thread-0"] InfiniteThread :: ⇢ fromIn() [Thread:"Thread-0"] InfiniteThread :: ⇢ fromOut(data=0) [Thread:"main"] InfiniteThread :: ⇠ fromOut [500ms] InfiniteThread :: ⇢ fromOut(data=1) [Thread:"main"] InfiniteThread :: ⇠ fromIn [1000ms] InfiniteThread :: ⇢ fromIn() [Thread:"Thread-0"] InfiniteThread :: ⇠ fromOut [500ms] InfiniteThread :: ⇢ fromOut(data=2) [Thread:"main"] InfiniteThread :: ⇠ fromOut [500ms] InfiniteThread :: ⇢ fromOut(data=3) [Thread:"main"] InfiniteThread :: ⇠ fromIn [1000ms] InfiniteThread :: ⇢ fromIn() [Thread:"Thread-0"] InfiniteThread :: ⇠ fromOut [500ms] InfiniteThread :: ⇢ fromOut(data=4) [Thread:"main"] InfiniteThread :: ⇠ fromOut [500ms] InfiniteThread :: ⇢ fromOut(data=5) [Thread:"main"] InfiniteThread :: ⇠ fromIn [1000ms] InfiniteThread :: ⇢ fromIn() [Thread:"Thread-0"] InfiniteThread :: ⇠ fromOut [500ms] InfiniteThread :: ⇢ fromOut(data=6) [Thread:"main"] InfiniteThread :: ⇠ fromOut [500ms] InfiniteThread :: ⇢ fromOut(data=7) [Thread:"main"] InfiniteThread :: ⇠ fromIn [1000ms] InfiniteThread :: ⇢ fromIn() [Thread:"Thread-0"] InfiniteThread :: ⇠ fromOut [500ms] InfiniteThread :: ⇢ fromOut(data=8) [Thread:"main"] InfiniteThread :: ⇠ fromOut [500ms] InfiniteThread :: ⇢ fromOut(data=9) [Thread:"main"] InfiniteThread :: ⇠ fromIn [1000ms] InfiniteThread :: ⇢ fromIn() [Thread:"Thread-0"] InfiniteThread :: ⇠ fromOut [500ms] InfiniteThread :: ⇠ fromIn [1000ms] InfiniteThread :: ⇢ fromIn() [Thread:"Thread-0"] InfiniteThread :: ⇠ fromIn [1000ms] InfiniteThread :: ⇢ fromIn() [Thread:"Thread-0"] InfiniteThread :: ⇠ fromIn [1000ms] InfiniteThread :: ⇢ fromIn() [Thread:"Thread-0"] InfiniteThread :: ⇠ fromIn [1000ms] InfiniteThread :: ⇢ fromIn() [Thread:"Thread-0"] InfiniteThread :: ⇠ fromIn [1000ms] InfiniteThread :: ⇠ run [10003ms]

这是有效的,因为在 SWING/GUI 你正在做类似的事情:

ConnectionHandlerThread myThread = ConnectionHandlerThread(new Socket());

现在 send() 方法是 public,因此您可以在执行

时从 GUI Class 调用它
myThread.send(data);

诀窍是你在线程中使用了一个套接字,这都是因为你在 class ConnectionHandlerThread

的构造函数中传递了一个套接字

@jameslarge Yes, I am confused right now...

Thread 对象不是线程:它是一种具有可用于配置、创建和与线程交互的方法的东西。你正在使用你的 ConnectionHandlerThread 来做所有这些,你也在使用它(通过覆盖 run() 方法)来定义线程所做的工作,你也在使用它(通过几个私有字段)来表示线程操作的连接状态。这是很多重载。

一位作者这样说,"It is easy to write a program that has too few objects. It's hard to write one that has too many objects."

我会重新编写您的代码以使用三个不同的对象来完成您的 ConnectionHandlerThread 现在所做的三个不同的工作:

public class Connection {
    private Socket socket;
    private volatile boolean connected = true;
    private ObjectOutputStream out = null;
    private ObjectInputStream in = null;

    public Connection(Socket socket){
        this.socket = socket;
        out = new ObjectOutputStream(socket.getOutputStream());
        in = new ObjectInputStream(socket.getInputStream());
    }

    public void send(Object data){
        try{
            out.writeObject( data );
        }
        catch(IOException e){
            handleExceptionInSend(e);
        }       
    }

    public Object receive() {
        try{
            Object data = in.readObject();                  
        }
        catch(ClassNotFoundException e){
            handleExceptionInReceive(e);
            return NULL;
        }               
        catch(IOException e){
            handleExceptionInReceive(e);
            return NULL;
        }
        return Object;
    }

    public boolean isConnected() {
        return connected;
    }

    ...
}


public class ConnectionListener implements Runnable {
    private final connection;

    public ConnectionRunner(Connection connection) {
        this->connection = connection;
    }

    public void run(){
        while(connection.isConnected()){
            Object o = connection.receive();
            if (o) {
                doSomethingWith(o);
            }
        }        
    }
}

public class Whatever {
    ...
    public void whateverelse( ) {
        Socket socket = ...;
        Connection connection = new Connection(socket);
        ConnectionListener listener = new ConnectionListener(connection);
        Thread connectionThread = new Thread(listener);

        connectionThread.start();
        ...
    }
    ...
}

connection 对象知道如何发送和接收数据。

listener 对象定义了线程的作用:即,它从连接接收对象直到 ! isConnected() 并用它们做一些事情。

最后,connectionThread 对象是启动线程的对象(可用于等待它完成等),如果需要的话。

它的代码比你写的要多,但是在我看来,它更容易理解,因为它更容易看到每个单独部分的职责。有时,尤其是当您与其他开发人员协作时,使代码易于理解比使其小巧更重要。