如何在Java中超时阻塞函数?

3

基本上,我在调用BufferedReader.ReadLine()方法;但是我在一个多线程服务器中,其中我正在同步树中的节点。所以当调用这个ReadLine函数时,如果有人到达该节点,他们将被锁定。我无法想出如何在线程退出之前对ReadLine等待响应的时间限制。我最接近的做法是创建一个新线程,让它休眠1毫秒,然后检查我设置ReadLine变量是否已更改。所以代码大致如下:

synchronized (pointer) {
    String answer = "";
    Thread d = new Thread(new Runnable() {
        public void run() {
            try {
                int i = 0;
                while (answer.equals("")) {
                    if (i == 10000) {
                        System.out.println("Timeout Occured");
                        System.exit(0);
                    }
                    try {
                        Thread.sleep(1);
                        i++;
                    }
                    catch(Exception e) {
                        System.out.println("sleep problem occured");
                    }
                }
            }
            catch (IOException ex) {
            }
        }    
    });

    d.start();
    answer = socketIn.readLine();
}

这个做了我想要的事情,但我无法找出如何停止当前线程以解锁节点,让其他用户继续而不是杀死整个服务器。最后,我想也许我可以这样做:

    Thread d = new Thread(new Runnable() {
        public void run() {
            try {
                answer = socketIn.readLine(); 
            } catch (IOException ex) {
            }
        }    
    });

    d.join(10000);
    catch (InterruptedException e){
    socketOut.println("Timeout Occured. Returning you to the beginning...");
    socketOut.flush();
    return;
}

但是它仍然似乎被阻塞,无法继续。有人能帮我解决这个问题吗?我不明白我做错了什么?
我也尝试让ExecutorService工作,但失败了。这是我的答案吗?我应该如何实现它?
[编辑] socketIn是一个BufferedReader,应该明确说明,很抱歉。此外,客户端通过telnet连接,虽然我认为这并不重要。
我在这里做的是“名人猜测游戏”,用户可以将名人添加到树中。所以我需要锁定正在编辑的节点以进行线程安全。

请看下面的答案 - 如果您在等待输入时锁定了,那么您就会被卡住(除非您超时读取,在这种情况下您只是慢)。您上面的代码更加危险,因为您正在启动一个新线程,并尝试让两个线程在没有锁定(同步)的情况下访问“answer”。 - Brian Roach
3个回答

3

这是作业吗?它与昨天其他人提出的问题非常接近,让人怀疑。如果是,请添加“homework”标签。

只有当一个线程会修改其他线程可能读取/修改的数据时,才需要锁定某些内容。

如果你正在等待输入并锁定了某些内容,那么你的锁范围太宽了。

你的流程应该是:

  • 从客户端读取输入(使用阻塞式readLine())
  • 锁定共享资源
  • 修改
  • 解除锁定

(假设你每个连接/客户端都有一个线程,并且在等待来自客户端的读取)

话虽如此...... 如果你正在从套接字中读取并希望超时,那么你需要在首次接受连接时使用clientSocket.setSoTimeout(1000);。如果你的BufferedReader等待那么长时间(以毫秒为单位)并且没有获得任何输入,则会抛出一个java.net.SocketTimeoutException

String inputLine = null;
try 
{
    inputLine = in.readLine();
    if (inputLine == null)
    {
        System.out.println("Client Disconnected!");
    }
    else 
    {
        // I have input, do something with it
    }
}
catch(java.net.SocketTimeoutException e)
{
    System.out.println("Timed out trying to read from socket");
}

1

一切都已经完成。尝试使用java.util.concurrent

    //
    // 1. construct reading task
    //
    final FutureTask<String> readLine = new FutureTask<String> (
        new Callable<String>() {
            @Override public String call() throws Exception {
                return socketIn.readLine();
            }
        }
    );
    //
    // 2. wrap it with "timed logic"
    //    *** remember: you expose to your users only this task
    //
    final FutureTask<String> timedReadLine = new FutureTask<String> (
        new Callable<String>() {
            @Override public String call() throws Exception {
                try {
                    //
                    // you give reading task a time budget:
                    //      try to get a result for not more than 1 minute
                    //
                    return readLine.get( 1, TimeUnit.MINUTES );
                } finally {
                    //
                    // regardless of the result you MUST interrupt readLine task
                    // otherwise it might run forever
                    //      *** if it is already done nothing bad will happen 
                    //
                    readLine.cancel( true );
                }
            }
        }
    )
    {
        //
        // you may even protect this task from being accidentally interrupted by your users:
        //      in fact it is not their responsibility
        //
        @Override
        public boolean cancel(boolean mayInterruptIfRunning) {
            return false;
        }
    };

    Executor executor = Executors.newCachedThreadPool();

    // 3. execute both
    executor.execute( readLine );
    executor.execute( timedReadLine );

    // 4. ...and here comes one of your users who can wait for only a second
    try {
        String answer = timedReadLine.get(1, TimeUnit.SECONDS);
        //
        // finally user got his (her) answer
        //
    } catch (InterruptedException e) {
        //
        // someone interrupted this thread while it was blocked in timedReadLine.get(1, TimeUnit.SECONDS)
        //
    } catch (ExecutionException e) {
         //
        // e.getCause() probably is an instance of IOException due to I/O failure
        //
    } catch (TimeoutException e) {
        //
        // it wasn't possible to accomplish socketIn.readLine() in 1 second
        //
    }

0
我找到了一个解决方案:
answer = "";
try{
    Thread d = new Thread(new Runnable() {
        public void run() {
            try {
                answer = socketIn.readLine();
            }   
            catch (IOException ex) {
                System.out.println("IO exception occurred");
            }
        }
    });
    d.join(10000); //Not sure if this is superfluous or not, but it didn't seem to work without it.
    d.start();
    i = 0;
    while (true){
        if (i == 10000){
            if (d.isAlive()) throw InterruptedException;
        }
        if (answer.equals("")){
            Thread.sleep(1);
        }
        else{
            break;
        }
        i++;
    }
    //This essentially acts as Thread.sleep(10000), but the way I 
    //implemented it, it checks to see if answer is modified or not every  
    //.001 seconds. It will run for just over 10 seconds because of these checks. 
    //The number in Thread.sleep could probably be higher, as it is 
    //unnecessary to check so frequently and it will make the code more efficient
    //Once it hits 10000, it throws an exception to move to the catch block below
    //where the catch block returns everything to its original state and 
    //returns the client to the beginning
    }
catch (Exception e){
    socketOut.println("Timeout Occurred. Returning you to the beginning...");
    socketOut.flush();
    pointer = tree.root;
    //reset class members to their original state
    return;
}

谢谢你的关注,Brian Roach。你的帖子很有启发性,但是我不小心漏掉了一些关键信息。以后我会更加小心的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接