简体   繁体   中英

Thread.sleep blocks other Thread

I have a Output class which just prints everything that it gets to print.

public class Output {
    private static List<String> textList = new ArrayList<>();
    private static Output output = null;

    private Output() {
        Runnable task = () -> {
            int lastIndex = 0;

            while (true) {
                while (lastIndex < textList.size()) {
                    System.out.println(lastIndex + " - " + textList.size() + ": " + textList.get(lastIndex));

                    outputText(textList.get(lastIndex));

                    lastIndex ++;
                }
            }
        };

        new Thread(task).start();
    }

    private static void outputText(String text) {
        synchronized (System.out) {
            System.out.println(text);
        }

    }

    public static void say(String text) {
        if (output == null) {
            output = new Output();
        }

        textList.add(text);
    }
}

When I add something to print, everything works fine:

for (int i = 0; i < 10; i++) {
    Output.say("" + i);
}

But when I add a Thread.sleep to the loop it stops on the first output:

for (int i = 0; i < 10; i++) {
    Output.say("" + i);
    Thread.sleep(100);
}

How can I prevent it? I mean, I'm stopping with sleep just the main thread and not the separate thread.

When you don't synchronize threads correctly, there is no guaranty that threads see updates made by other threads. They may either completely miss updates or see only parts of them, creating an entirely inconsistent result. Sometimes they may even appear to do the right thing. Without proper synchronization (in the sense of any valid construct specified to be thread safe), this is entirely unpredictable.

Sometimes, the chances of seeing a particular behavior are higher, like in your example. In most runs, the loop without sleep will complete before the other thread even starts its work, whereas inserting sleep raises the chance of lost updates after the second thread has seen values. Once the second thread has seen a value for textList.size() , it might reuse the value forever, evaluating lastIndex < textList.size() to false and executing the equivalent of while(true) { } .

It's funny that the only place where you inserted a construct for thread safety, is the method outputText that is called by a single thread only (and printing to System.out is synchronized internally in most environments anyway).

Besides, it's not clear why you are creating an object of type Output that has no relevance here, as all fields and methods are static .

Your code can be corrected and simplified to

public static void main(String[] args) throws InterruptedException {
    List<String> textList = new ArrayList<>();
    new Thread( () -> {
        int index=0;
        while(true) synchronized(textList) {
            for(; index<textList.size(); index++)
                System.out.println(textList.get(index));
        }
    }).start();

    for (int i = 0; i < 10; i++) {
        synchronized(textList) {
            textList.add(""+i);
        }
        Thread.sleep(100);
    }
}

though it still contains the issues of you original code of never terminating due to the infinite second thread and also burning the CPU with a polling loop. You should let the second thread wait for new items and add a termination condition:

public static void main(String[] args) throws InterruptedException {
    List<String> textList = new ArrayList<>();
    new Thread( () -> {
        synchronized(textList) {
            for(int index=0; ; index++) {
                while(index>=textList.size()) try {
                    textList.wait();
                } catch(InterruptedException ex) { return; }
                final String item = textList.get(index);
                if(item==null) break;
                System.out.println(item);
            }
        }
    }).start();

    for (int i = 0; i < 10; i++) {
        synchronized(textList) {
            textList.add(""+i);
            textList.notify();
        }
        Thread.sleep(100);
    }
    synchronized(textList) {
        textList.add(null);
        textList.notify();
    }
}

This is still only an academic example that you shouldn't use in real life code. There are classes for thread safe data exchange provided by the Java API removing the burden of implementing such things yourself.

public static void main(String[] args) throws InterruptedException {
    ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(10);
    String endMarker = "END-OF-QUEUE"; // the queue does not allow null
    new Thread( () -> {
        for(;;) try {
            String item = queue.take();
            if(item == endMarker) break;// don't use == for ordinary strings
            System.out.println(item);
        } catch(InterruptedException ex) { return; }
    }).start();

    for (int i = 0; i < 10; i++) {
        queue.put(""+i);
        Thread.sleep(100);
    }
    queue.put(endMarker);
}

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