不明原因的停止执行

时间:2015-07-09 15:56:02

标签: java multithreading concurrency

我正在开发一个包含2个线程的Java进程:一个用于读取文件的内容并将它们添加到一个共享阻塞队列中;一个用于从阻塞队列中检索线路并通过网络发送它们(在指定的发送速率下)。我有两个课程如下:

更新了以下代码

制片人主题:

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.concurrent.ArrayBlockingQueue;

public class SourceFileProducer implements Runnable {

    private File file;

    private BufferedReader reader;

    private ArrayBlockingQueue<String> buffer;

    private String fileName;

    private String endMarker;

    public SourceFileProducer(ArrayBlockingQueue<String> buffer, 
            String endMarker, String fileName) {
        this.buffer = buffer;
        this.endMarker = endMarker;
        file = new File(fileName);
        if(file.exists()) {
            try {
                reader = new BufferedReader(new FileReader(file));
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }
        this.fileName = fileName;
    }

    @Override
    public void run() {
        System.out.println("SourceFileProducer thread-" + Thread.currentThread().getId() + " initiating with source file: " + fileName);
        String line = "";
        try {
            while((line = reader.readLine()) != null) {
                try {
                    buffer.put(line);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            try {
                buffer.put(endMarker);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("SourceFileProducer thread-" + Thread.currentThread().getId() + " scanned and buffered the whole file.");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

和消费者主题:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ArrayBlockingQueue;

public class SourceFileConsumer implements Runnable {

    private ArrayBlockingQueue<String> buffer;

    private BufferedReader socketInput;

    private PrintWriter socketOutput;

    private Socket client;

    private ServerSocket serverSocket;

    private long checkpoint[] = null;

    private int rate[] = null;

    private String endMarker;

    public SourceFileConsumer(ArrayBlockingQueue<String> buffer, String endMarker, 
            ServerSocket serverSocket, Socket client, long checkpoint[], int rate[]) {
        this.buffer = buffer;
        this.endMarker = endMarker;
        this.client = client;
        try {
            socketOutput = new PrintWriter(client.getOutputStream(), true);
            socketInput = new BufferedReader(new InputStreamReader(client.getInputStream()));
        } catch (IOException e) {
            e.printStackTrace();
        }
        this.checkpoint = new long[checkpoint.length];
        this.rate = new int[rate.length];
        for(int i = 0; i < checkpoint.length; i++) {
            this.checkpoint[i] = checkpoint[i];
            this.rate[i] = rate[i];
        }
        this.serverSocket = serverSocket;
    }

    @Override
    public void run() {
        String line = null;
        long start = System.currentTimeMillis();
        int index = 0;
        boolean fileScanFlag = true;
        while(fileScanFlag) {
            long startTimestamp = System.currentTimeMillis();
            long interval = (startTimestamp - start) / 1000L;
            if(interval >= checkpoint[index]) {
                if(index < checkpoint.length - 1) {
                    if(interval >= checkpoint[index + 1]) {
                        index += 1;
                        System.out.println("SourceFileConsumer thread-" + Thread.currentThread().getId() + 
                                " progressed to checkpoint " + index + " with rate: " + rate[index]);
                    }
                }
            }
            int counter = 0;
            while(counter < rate[index]) {
                try {
                    line = buffer.take();
                } catch (InterruptedException e1) {
                    e1.printStackTrace();
                }
                if(line == endMarker) {
                    fileScanFlag = false;
                    break;
                }
                if(socketOutput != null && socketOutput.checkError()) {
                    System.out.println("SourceFileConsumer Thread-" + Thread.currentThread().getId() + " detected broken link...");
                    try {
                        client = serverSocket.accept();
                        socketOutput = new PrintWriter(client.getOutputStream(), true);
                        socketInput = new BufferedReader(new InputStreamReader(client.getInputStream()));
                    } catch(IOException e) {
                        e.printStackTrace();
                    }
                    System.out.println("SourceFileConsumer Thread-" + Thread.currentThread().getId() + " re-established connection...");
                }
                if(socketOutput != null)
                    socketOutput.println(line);
                counter += 1;
            }
            long endTimestamp = System.currentTimeMillis();
            if(endTimestamp - startTimestamp <= 1000) {
                System.out.println("thread-" + Thread.currentThread().getId() + " input rate: " + counter + 
                        ", wait time: " + (1000 - (endTimestamp - startTimestamp)));
                try {
                    Thread.sleep((1000 - (endTimestamp - startTimestamp)));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        if(socketInput != null && socketOutput != null && client != null) {
            try {
                socketInput.close();
                socketOutput.close();
                client.close();
            } catch(IOException e) {
                e.printStackTrace();
            }
        }
        System.out.println("SourceFileConsumer Thread-" + Thread.currentThread().getId() + " transfer complete.");
    }   
}

问题是,一段时间后,两个线程都挂起,没有发送元组。当我在Linux机器上运行top命令时,我发现运行两个线程的Java进程使用的CPU时间非常少。为什么会这样?这是饥饿的问题吗?我认为使用LinkedBlockingQueue可以避免饥饿。

任何提示?

谢谢, 尼克

1 个答案:

答案 0 :(得分:1)

这是相当多的代码,尤其是在您的消费者中。因此,不可能排除存在多个错误。我建议您简化代码以缩小问题范围,例如:独立测试您的生产者 - 消费者切换和网络操作。

一个明显的问题是,您试图通过AtomicBoolean发出文件的结尾信号,但您的消费者在拍摄物品之前并没有实际测试它。如果你看一下take项的位置,就会有一个内循环:

while(counter < rate[index]) {
            try {
                line = buffer.take();
…

由于生产者对counter < rate[index]条件没有影响,因此在检查take的状态之前,它无法控制消费者尝试fileScanFlag的行数。

但即使您尝试通过检查take之前的布尔标志来解决此问题,结果也会因可能的竞争条件而中断。原子布尔值和阻塞队列本身都是线程安全的,但两者的组合不是。

将最后一项放在队列中并设置标志是两个不同的操作。在这两个操作之间,消费者可以获取最后一个项目,重新检查标志并找到它false并在生产者即将设置为{take时进行下一次尝试true {1}}。

一种解决方案是改变消费者方面的操作顺序,这需要求助于轮询:

polling: for(;;) {
    line = buffer.poll(timeout, timeOutUnit); // control the cpu consumption via timeout
    if(line!=null) break polling;
    if(fileScanFlag.get()) break outerLoop;
}

另一种方法是不使用两种不同的通信结构。一旦文件结束,将结束标记对象放置到队列中,而不是维护布尔标志。这是极少数情况之一,使用String而非equals的身份是恰当的:

public class SourceFileProducer implements Runnable {
    private String endMarker;
    …
    public SourceFileProducer(LinkedBlockingQueue<String> buffer, 
            String endMarker, String fileName) {
        this.buffer = buffer;
        this.endMarker = endMarker;
    …

    @Override
    public void run() {
        System.out.println("SourceFileProducer thread-" + Thread.currentThread().getId()
            + " initiating with source file: " + fileName);
        String line;
        try {
            while((line = reader.readLine()) != null) buffer.put(line);
        } catch (IOException|InterruptedException e) {
            e.printStackTrace();
        }
        buffer.put(endMarker);
    }

public class SourceFileConsumer implements Runnable {
    private String endMarker;
    …

    public SourceFileConsumer(LinkedBlockingQueue<String> buffer, String endMarker, 
            ServerSocket serverSocket, Socket client, long checkpoint[], int rate[]) {
        this.buffer = buffer;
        this.endMarker = endMarker;
…

                    line = buffer.take();
                    if(line==endMarker) break;

结束标记的值无关紧要,但它是对象标识。因此,创建两个线程的代码必须包含以下内容:

 // using new to ensure unique identity
private static final String EOF = new String("end of file");

…
new SourceFileProducer(queue, EOF, …)
new SourceFileConsumer(queue, EOF, …)

new运算符保证生成具有唯一标识的对象,因此,将该标记对象与任何其他String进行比较,即BufferedReader返回的行,通过{{1总是评估为false。必须注意不要让标记对象逃脱编码而不知道它的特殊作用。