为什么我在Socket连接中的Java线程中看到这个奇怪的输出?

时间:2014-06-28 15:35:59

标签: java multithreading sockets synchronization static-methods

所以我有这个小代码:

public class MyCounterClass {
    private static int counter = 0;
    synchronized public static int getCounter(){
        return counter++;
    }
}

这是服务器:

public class MyServer implements Runnable{

    public static void go() throws IOException {

        System.out.println("MyServer: Go called...");
        ServerSocket serverSocket = new ServerSocket(5000);
        while(true){
            Socket socket = serverSocket.accept();
            System.out.println(time() + "MyServer: Connection accepted!");
            OutputStream outputStream = socket.getOutputStream();
            System.out.println(time() + "MyServer: socket.getOutputStream");
            PrintWriter printWriter = new PrintWriter(outputStream);
            System.out.println(time() + "MyServer: New PrintWriter object created!");
            printWriter.write(time() + "Hello from my socket!");
            System.out.println(time() + "MyServer: printwriter.write method called..");
            printWriter.flush();
            System.out.println(time() + "MyServer: Flushed!");
            printWriter.close();
            System.out.println(time() + "MyServer: printWriter closed...");
        }
    }

    public static String time(){
        return String.valueOf(MyCounterClass.getCounter()) + " ";
    }

    @Override
    public void run() {
        try {
            go();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

这是客户:

public class MyClient implements Runnable {

    public static void go() throws IOException {
        Socket socket = new Socket("localhost",5000);
        System.out.println(time() + "My Client: Connection established...");
        InputStream inputStream = socket.getInputStream();
        System.out.println(time() + "MyClient: socket.getInputStream...");
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
        System.out.println(time() + "MyClient: BufferedReader object created...");
        System.out.println(time() + bufferedReader.readLine());
    }

    public static String time(){
        return String.valueOf(MyCounterClass.getCounter()) + " ";
    }

    @Override
    public void run() {
        try {
            go();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

我如何运行程序:

public class TestClass {
    public static void main(String[] args) throws IOException, InterruptedException {
        Thread server = new Thread(new MyServer());
        server.start();
        Thread.sleep(750);
        Thread client = new Thread(new MyClient());
        client.start();
    }
}

输出:

MyServer: Go called...
0 My Client: Connection established...
1 MyServer: Connection accepted!
2 MyClient: socket.getInputStream...
3 MyServer: socket.getOutputStream
4 MyClient: BufferedReader object created...
6 MyServer: New PrintWriter object created!
8 MyServer: printwriter.write method called..
9 MyServer: Flushed!
10 MyServer: printWriter closed...
5 7 Hello from my socket!

我的问题是,最后一行怎么能得到" 5和7"?

2 个答案:

答案 0 :(得分:4)

  • 5 - > "时间"客户收到消息
  • 7 - > "时间"服务器发送了消息

由于客户端和服务器是独立的线程,因此它们不会按顺序输出时间。

主要的混乱是因为这一行:

System.out.println(time() + bufferedReader.readLine());

似乎println正在及时回归"。然而,正在发生的事情是,time()方法被称为回溯时间" 5"。但是,由于普通的Java套接字是阻塞的,所以bufferedReader.readLine()调用会挂起,直到服务器实际可以获得数据,直到服务器发送数据之后才会发生这种情况(当然)。然后,一旦readLine方法返回,println调用实际上就完成了。

如果你想看到更多"逻辑"进度,将最后一个客户行更改为:

String result = bufferedReader.readLine();
System.out.println(time() + result);

这会延迟客户的上一次()电话,直到实际收到回复之后。

答案 1 :(得分:2)

因为您在服务器和客户端的服务器上添加(prepend = oposite of append)时间 ,所以

printWriter.write(time() + "Hello from my socket!");

'7'会被添加到您的消息中,然后'7 Hello from my socket!'已发送,然后5被添加到您的客户端:

System.out.println(time() + bufferedReader.readLine());

导致5 7 Hello from my socket!

那么time()如何先返回7然后再返回5?

好吧,因为counter不是volatile,请查看原因here

现在考虑以下场景:在您的客户端,调用time()并存储结果,然后线程等待它接收数据,然后服务器创建数据并发送它们。它将解释为什么客户端在发送的消息上打印较低的值然后服务器。

您也可以尝试写:

System.out.println(time() + bufferedReader.readLine() + time());

要查看time的第二次调用是否会提供更大的值,即第一次调用+ 1,因为readLine()冻结当前线程直到某些要读取的数据可用。