我正在编写一个应用程序,它涉及将相当大的数据块写入OutputStream(属于Socket)。使这有点复杂的是,通常有多个线程试图写入同一个OutputStream。目前,我设计它使得要写入数据的OutputStream在它自己的线程中。该线程包含一个队列(LinkedList),它轮询字节数组并尽快写入它们。
private class OutputStreamWriter implements Runnable {
private final LinkedList<byte[]> chunkQueue = new LinkedList<byte[]>();
public void run() {
OutputStream outputStream = User.this.outputStream;
while (true) {
try {
if (chunkQueue.isEmpty()) {
Thread.sleep(100);
continue;
}
outputStream.write(chunkQueue.poll());
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
这种设计的问题在于,随着越来越多的写入发生,越来越多的数据排队,并且不会更快地写入。最初,当数据被放入队列时,它几乎立即被写入。然后大约15秒后,数据开始落后;从数据排队的时间到实际写入数据的时间延迟。随着时间的推移,这种延迟变得越来越长。 非常显而易见。
解决这个问题的一种方法是某种ConcurrentOutputStream实现,它允许在不阻塞的情况下发送数据,这样就不会开始备份写入(哎呀,队列就没必要了)。我不知道是否有这样的实现 - 我一直找不到 - 我个人认为甚至不可能写一个。
那么,有没有人建议我如何重新设计这个?
答案 0 :(得分:4)
插座的吞吐量有限;如果它比数据生成吞吐量慢,那么必须缓冲数据,没有办法解决这个问题。 “同时”写作根本没有用。
您可以考虑在排队数据超出特定限制时暂停数据生成,以减少内存消耗。
答案 1 :(得分:0)
我同意@irreputable,并发写作不会有任何帮助。相反,你应该看看生产方面,即你已经拥有的东西。
使用BlockingQueue而不是LinkedList。
使用队列的阻塞轮询操作,而不是100msl的盲目睡眠,根据定义,这将平均浪费50%的时间。很长一段时间,真的可以加起来。
答案 2 :(得分:0)
我需要一个过滤器来拦截慢速连接,我需要尽快关闭数据库连接,所以我最初使用Java管道但是当仔细观察它们的实现时,它全部同步,所以我最终使用一个小缓冲区创建了我自己的QueueInputStream阻塞队列将缓冲区放入队列一旦已满,它是无锁的,除非对于LinkedBlockingQueue使用的锁定条件,借助于小缓冲区它应该是便宜的,这个类只用于单个每个实例的生产者和消费者,您应该传递一个ExecutorService来开始将排队的字节流式传输到最终的OutputStream:
import java.io.IOException;
import java.io.OutputStream;
import java.util.concurrent.*;
public class QueueOutputStream extends OutputStream
{
private static final int DEFAULT_BUFFER_SIZE=1024;
private static final byte[] END_SIGNAL=new byte[]{};
private final BlockingQueue<byte[]> queue=new LinkedBlockingDeque<>();
private final byte[] buffer;
private boolean closed=false;
private int count=0;
public QueueOutputStream()
{
this(DEFAULT_BUFFER_SIZE);
}
public QueueOutputStream(final int bufferSize)
{
if(bufferSize<=0){
throw new IllegalArgumentException("Buffer size <= 0");
}
this.buffer=new byte[bufferSize];
}
private synchronized void flushBuffer()
{
if(count>0){
final byte[] copy=new byte[count];
System.arraycopy(buffer,0,copy,0,count);
queue.offer(copy);
count=0;
}
}
@Override
public synchronized void write(final int b) throws IOException
{
if(closed){
throw new IllegalStateException("Stream is closed");
}
if(count>=buffer.length){
flushBuffer();
}
buffer[count++]=(byte)b;
}
@Override
public synchronized void write(final byte[] b, final int off, final int len) throws IOException
{
super.write(b,off,len);
}
@Override
public synchronized void close() throws IOException
{
flushBuffer();
queue.offer(END_SIGNAL);
closed=true;
}
public Future<Void> asyncSendToOutputStream(final ExecutorService executor, final OutputStream outputStream)
{
return executor.submit(
new Callable<Void>()
{
@Override
public Void call() throws Exception
{
try{
byte[] buffer=queue.take();
while(buffer!=END_SIGNAL){
outputStream.write(buffer);
buffer=queue.take();
}
outputStream.flush();
} catch(Exception e){
close();
throw e;
} finally{
outputStream.close();
}
return null;
}
}
);
}