简体   繁体   中英

What happens to stdin data when multiple threads are using Scanners on System.in in Java?

Say I have a main program that listens for command line arguments on stdin. This main program spawns a thread to do some stuff, and the thread also needs to listen to stdin.

If two different threads have Scanners on System.in, does one block the other, or do both threads get all data received off stdin?

I tried out this code (in the main method):

Scanner scan = new Scanner(System.in);
new Thread(){
    public void run(){
        Scanner scan = new Scanner(System.in);
        while(true){
            if(scan.hasNext())System.out.println("Thread:"+scan.nextLine());
        }
    }
}.start();
while(true){
    if(scan.hasNext())System.out.println("Main:"+scan.nextLine());
}

Most of the time, neither thread was printing, and when it did print, it was clear that they were both parsing input at the same time . While they did not (I think) end up with duplicate characters, an input of abcd might send ac to one thread's scanner and bd to the other one.

Sample run (anything not preceded by Main: or Thread: is input):

abcdefghi
jklmnopqr
Thread:ghi
Thread:mnopqr
stuvwxyzA
Thread:vwxyzA
BCDEFGHIJ
KLMNOPQRS
Thread:KLM
Main:abcdefjklstuBCDEFGHIJ
TUVWXYZ`~
1!2@3#4$5
Main:NOPQRS~
Thread:TUVWXYZ`1!2@3#4$5

If you wanted both threads to get the stdin, you could create a class such as this (it works, but is not optimal at all):

class STDIN extends Thread{ //this way, it can only be used once, like intended.
    private static final BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
    private static final ArrayList<Integer> data = new ArrayList<>();
    private static final ArrayList<Integer> dataFlags = new ArrayList<>();

    public void run(){
        while(true){
            synchronized(data){
                try {
                    int i = stdin.read();
                    if (i == -1) break;
                    data.add(i);
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
                synchronized(dataFlags){
                    if (dataFlags.get(0) == 3){
                        dataFlags.remove(0);
                        data.remove(0);
                    }
                }
            }
        }
        try {
            stdin.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
    public int getByte(int index){
        synchronized(data){
            return data.get(index);
        }
    }
    public void flagA(int index){
        synchronized(dataFlags){
            if (index>dataFlags.size()){
                dataFlags.add(1);
            }
            else{
                dataFlags.set(index, dataFlags.get(index) | 1);
            }
        }
    }
    public void flagB(int index){
        synchronized(dataFlags){
            if (index>dataFlags.size()){
                dataFlags.add(2);
            }
            else{
                dataFlags.set(index, dataFlags.get(index) | 2);
            }
        }
    }
}

Using that, you can read data, but only one character at a time. When you are finished with the data, you can flag it based on which thread you are on. ( flagA for one thread, flagB for the other).

This is a partial wrapper for Scanner ; I only include the nextLine method:

class MultithreadedScanner {
    private final Scanner scan;
    private final ArrayList<MultithreadedScannerListener> toNotify;

    public MultithreadedScanner(InputStream is) {
        scan = new Scanner(is);
        toNotify = new ArrayList<>();
    }

    public void addMultithreadedScannerListener(MultithreadedScannerListener l) {
        synchronized (toNotify) {
            toNotify.add(l);
        }
    }

    public void nextLine() {
        String s = scan.nextLine();
        synchronized(toNotify){
            for (MultithreadedScannerListener l: toNotify){
                l.receiveNextLine(s);
            }
        }
    }
}

interface MultithreadedScannerListener {
    public void receiveNextLine(String line);
}

Make each thread you want to listen to stdin on implement MultithreadedScannerListener and add them to a MultithreadedScanner . Call nextLine whenever you want the new info; recommend doing this from only one thread.

Notethat after thread1 calls read() on System.in that the bytes will be removed from the stream, so no both can't recieve the same data.

Also note that if you call hasNextXX() it will also consume input from System.in . It is advisable to have a single thread waiting on input and then use something like a wait()-notify() mechanism to block and then pass the data on.

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