【问题标题】:Calling a method on a Thread - it works, but why?在线程上调用方法 - 它可以工作,但为什么呢?
【发布时间】:2016-03-30 15:02:48
【问题描述】:

我有以下 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循环和/或对in.readObject()的调用不会阻止我这样做?我的意思是线程不应该一直忙于做其他事情吗?这是做事的好方法吗?如果不是,为什么不呢?

编辑

澄清令我困惑的地方:如果这是在主线程中,它将在in.readObject() 上忙,直到读取某些内容,然后它会在循环的下一次迭代中再次开始侦听。当然,我知道我可以从另一个线程调用send()。但让我大吃一惊的是——“谁”实际上正在执行send()?是我的线程在做,还是调用线程在做?如果是前者,怎么可能既忙于等待while循环中的输入执行send()方法呢?我只是很难把我的想法包裹起来......

【问题讨论】:

  • 因为您正在传递您在主 GUI Swing 中创建的套接字的引用...
  • 请详细说明一下...
  • 这听起来好像你对线程是什么感到困惑。当您尚未在两个线程之间建立任何同步时,为什么您会期望一个线程的活动(例如,从 in 读取)会干扰其他线程的活动(例如,写入 out)?线程彼此独立运行,除非它们在相同的同步对象上运行。
  • 查看我回答中的示例
  • @jameslarge 是的,我现在很困惑。我以为我了解线程是什么,但现在我不再了解了:) 我认为让我感到困惑的是send() 方法是在ConnectionHandlerThread 中定义的。换句话说——如果我在主线程上做in.read(),UI 会冻结。所以让我困惑的是为什么ConnectionHandlerThread 不是“冻结”,即为什么它可以执行send(),同时在run() 中被in.read() 阻止。我想这是一个愚蠢的问题,但除非有人以一种好的方式解释它,否则我想我只会感到困惑。

标签: java multithreading swing runnable java-threads


【解决方案1】:

有两件事:

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

2) 当你打电话给你的

发送(对象数据)

您不会从您的线程中执行此操作,因此请记住 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]

【讨论】:

  • 在删除@DebugLog(那是什么?)并使用System.out.println( Thread.currentThread().getName() );之后,我得到了这个工作。是的,它确实让事情变得更清楚了。我的主要困惑是因为被调用的方法是在线程中定义的,我认为线程正在执行它。但我想关键是它与 where 方法的定义无关——它仍然是执行它的调用者。对吗?
  • 正确。 @DebugLog 是一个生成日志的日志库github.com/djodjoni/hugo
【解决方案2】:

@jameslarge 是的,我现在很困惑……

Thread 对象不是线程:它具有可用于配置、创建和与线程交互的方法。您正在使用您的 ConnectionHandlerThread 来完成所有这些工作,并且您还使用它(通过覆盖 run() 方法)来定义线程所做的工作,并且您还使用它(通过几个私有字段)来表示线程操作的连接状态。这太多了。

一位作者说过,“编写对象太少的程序很容易。编写对象太多的程序很难。”

我会重新编写你的代码以使用三个不同的对象来完成你的 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 对象是启动线程的对象(如果需要,可用于等待它完成等)。

它的代码比你写的要多,但是 IMO,它更容易理解,因为它更容易看到每个单独部分的职责。有时,特别是当您与其他开发人员合作时,让代码易于理解比缩小代码更重要。

【讨论】:

  • 谢谢!我有点发现了我推理中的缺陷。出于某种原因,我认为Thread 的线程将执行send(),即使它是从外部调用的......但现在我意识到方法定义的位置完全无关紧要,而且只是因为它实际上是在“Thread”子类中定义的,并不意味着执行run() 方法的实际线程将在另一个线程从外部调用它时执行send()。简短的回答 - Thread不是 线程,run() 是线程......对吗?
  • @BadCash,差不多。是的。
【解决方案3】:

这是可行的,因为在 SWING/GUI 中您正在执行以下操作:

ConnectionHandlerThread myThread = ConnectionHandlerThread(new Socket());

现在send() 方法是公开的,所以你可以从GUI 类中调用它

myThread.send(data);

诀窍是你在 Thread 中使用了一个套接字,这都是因为你在 ConnectionHandlerThread 类的构造函数中传递了一个套接字

【讨论】:

  • 我不明白Socket 有什么关系?我刚刚尝试了@djodjo 发布的代码,它的工作原理是一样的,并且没有任何东西传递给那里的线程的构造函数......
  • 我想我只是不明白......你想知道为什么你可以通过线程中的套接字发送消息
  • 不,套接字恰好在我的代码中。问题实际上是关于谁在线程中执行什么。我认为@djodjo 的回答让我想到了正确的方向。
猜你喜欢
  • 2011-11-21
  • 2023-01-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-21
相关资源
最近更新 更多