繁体   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