簡體   English   中英

Thread.sleep阻止其他線程

[英]Thread.sleep blocks other Thread

我有一個Output類,它僅打印將要打印的所有內容。

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);
    }
}

當我添加打印內容時,一切正常:

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

但是,當我將Thread.sleep添加到循環中時,它將在第一個輸出上停止:

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

我該如何預防? 我的意思是,我將僅在主線程而不是單獨的線程處於休眠狀態。

如果您沒有正確同步線程,則無法保證線程會看到其他線程所做的更新。 他們要么完全錯過更新,要么只看到其中的一部分,從而造成完全不一致的結果。 有時他們甚至看起來似乎在做正確的事情。 如果沒有適當的同步(就任何指定為線程安全的有效構造而言),這是完全不可預測的。

有時,像您的示例一樣,看到特定行為的幾率更高。 在大多數運行中,沒有sleep的循環將在另一個線程甚至開始工作之前完成,而插入sleep會增加第二個線程看到值后丟失更新的機會。 一旦第二個線程看到了textList.size()的值,它可能會永遠重復使用該值,將lastIndex < textList.size()評估為false並執行while(true) { }的等效操作。

有趣的是,為確保線程安全而插入結構的唯一位置是僅由單個線程調用的outputText方法(無論如何,在大多數環境中,打印到System.out都是內部同步的)。

此外,由於所有字段和方法都是static ,因此尚不清楚為什么要創建與此處不相關的Output類型的對象。

您的代碼可以更正並簡化為

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);
    }
}

盡管它仍然包含您的原始代碼的問題,這些代碼永遠不會由於無限的第二個線程而終止,並且還會通過輪詢循環來燒毀CPU。 您應該讓第二個線程等待新項並添加終止條件:

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();
    }
}

這仍然只是一個學術示例,您不應該在現實生活中使用它。 Java API提供了用於線程安全的數據交換的類,從而減輕了您自己實現此類事情的負擔。

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);
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM