将显式和隐式并行性与java-8流混合

时间:2015-02-12 17:46:45

标签: java multithreading parallel-processing java-8 java-stream

过去我用两个线程编写了一些java程序。 第一个线程(生产者)从API(C库)读取数据,创建一个java对象,将对象发送到另一个线程。 C API正在提供事件流(无限)。 线程使用LinkedBlockingQueue作为管道来交换对象(put,poll)。 第二个线程(消费者)正在处理该对象。 (我还发现代码在线程中更具可读性。第一个线程是处理C API的东西并生成 适当的java对象,第二个线程没有C API处理,正在处理数据)。

现在我很感兴趣,我如何通过java 8中的新流API实现上述方案。 但假设我想保留两个线程(生产者/消费者)! 第一个线程是写入流。第二个线程是从流中读取。 我也希望,我能用这种技术处理更好的显式并行性(生产者/消费者) 在流中,我可以使用一些隐式并行(例如stream.parallel())。

我对新流api没有太多经验。 所以我尝试了下面的代码来解决上面的想法。

  • 我使用'generate'来访问C API并将其提供给java流。
  • 我在消费者线程.parallel()中使用来测试和处理隐式并行性。看起来很好。但见下文。

问题:

  1. 为生产者“生成”这种情景的最佳方式吗?
  2. 我有一个理解问题如何在生产者中终止/关闭流, 如果API有一些错误 AND 我想关闭整个管道。 我是否使用stream.close或抛出异常?
    • 2.1我使用了stream.close()。但是'生成'在关闭后仍在运行, 我发现只抛出异常来终止生成部分。 此异常进入流,消费者正在接收异常 (这对我来说很好,消费者可以识别并终止)。 但在这种情况下,生产者已经生产了更多的消费者已处理,而例外正在到来。
    • 2.2如果消费者使用隐式并行性stream.parallel()。制作人正在处理更多的项目。 所以我没有看到任何解决这个问题的方法。 (访问C API,检查错误,做出决定)。
    • 2.3在生产者中抛出异常到达消费者流,但并未处理所有插入的对象。
  3. 再一次:想法是与线程有明确的并行性。 但在内部我可以处理新功能并尽可能使用并行处理

    感谢您对此问题的培育。

    package sandbox.test;
    
    import java.util.concurrent.atomic.AtomicInteger;
    import java.util.stream.LongStream;
    
    public class MyStream {
        private volatile LongStream stream = null;
        private AtomicInteger producerCount = new AtomicInteger(0);
        private AtomicInteger consumerCount = new AtomicInteger(0);
        private AtomicInteger apiError = new AtomicInteger(0);
    
        public static void main(String[] args) throws InterruptedException {
        MyStream appl = new MyStream();
        appl.create();
        }
    
        private static void sleep(long sleep) {
        try {
            Thread.sleep(sleep);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        }
    
        private static void apiError(final String pos, final int iteration) {
        RuntimeException apiException = new RuntimeException("API error pos=" + pos + " iteration=" + iteration);
        System.out.println(apiException.getMessage());
        throw apiException;
        }
    
        final private int simulateErrorAfter = 10;
    
        private Thread produce() {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
            System.out.println("Producer started");
            stream = LongStream.generate(() -> {
                int localCount;
                // Detect error, while using stream.parallel() processing
                int error = apiError.get();
                if ( error > 0 )
                    apiError("1", error);
                // ----- Accessing the C API here -----
                localCount = producerCount.incrementAndGet(); // C API access; delegate for accessing the C API
                // ----- Accessing the C API here -----
    
                // Checking error code from C API
                if ( localCount > simulateErrorAfter ) { // Simulate an API error
                    producerCount.decrementAndGet();
                    stream.close();
                    apiError("2", apiError.incrementAndGet());
                }
                System.out.println("P: " + localCount);
                sleep(200L);
                return localCount;
                });
            System.out.println("Producer terminated");
            }
        });
        thread.start();
        return thread;
        }
    
        private Thread consume() {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
            try {
                stream.onClose(new Runnable() {
                @Override
                public void run() {
                    System.out.println("Close detected");
                }
                }).parallel().forEach(l -> {
                sleep(1000);
                System.out.println("C: " + l);
                consumerCount.incrementAndGet();
                });
            } catch (Exception e) {
                // Capturing the stream end
                System.out.println(e);
            }
            System.out.println("Consumer terminated");
            }
        });
        thread.start();
        return thread;
        }
    
        private void create() throws InterruptedException {
        Thread producer = produce();
        while ( stream == null )
            sleep(10);
        Thread consumer = consume();
        producer.join();
        consumer.join();
        System.out.println("Produced: " + producerCount);
        System.out.println("Consumed: " + consumerCount);
    
        }
    }
    

1 个答案:

答案 0 :(得分:3)

您需要了解有关Stream API的一些基本要点:

  • 在流上应用的所有操作都是惰性的,在应用终端操作之前不会执行任何操作。使用“生产者”线程创建流是没有意义的,因为这个线程不会做任何事情。所有操作都在“消费者”线程中执行,后台线程由Stream实现本身启动。创建Stream实例的线程完全不相关

  • 关闭流与Stream操作本身无关,即不关闭线程。它旨在释放其他资源,例如关闭与Files.lines(…)返回的流关联的文件。您可以使用onClose安排此类清理操作,Stream会在您调用close时调用它们,但就是这样。对于Stream类本身,它没有任何意义。

  • Stream不建模“一个线程在写,另一个在读”的场景。他们的模型是“一个线程正在调用你的Supplier,然后调用你的Consumer而另一个线程也一样,而 x 其他线程也是......”

    如果要实现具有不同生产者和消费者线程的生产者/消费者方案,最好使用ThreadExecutorService和线程安全队列。

    < / LI>

但您仍然可以使用Java 8功能。例如。没有必要使用内部类来实现Runnable;你可以使用lambda表达式。