Java多接收器长流写入场景的线程安全优化方案咨询
对讲系统多Socket发送优化方案求助
我正在开发一套对讲系统,扬声器需要根据注册接收器数量以及用户按键选择的目标,向多个Socket传输连续的音视频字节流。
我维护了一个监听地址列表,通过发送线程向每个地址写入数据;同时还有另一个线程负责监控硬件与网络通信,对列表中的地址进行增删操作。
发送线程会持续循环从输入设备读取数据流,然后向列表内的所有地址发送数据,几乎全程都要访问这个列表。目前我用的是加了同步锁的ArrayList,但同步块几乎每次循环都要获取锁,开销极高,而实际上列表的修改频率很低。
由于传输的是微块数据,少量丢包是可以接受的,所以竞态条件不是问题。现在想寻求更优的实现方案。
// 该列表可能随时被其他线程修改 ArrayList<InetSocketAddress> currentReceivers = new ArrayList<>(); . . public void run() { . . byte[] toSend = new byte[bufferSize]; DatagramPacket d = new DatagramPacket(toSend,bufferSize); while(!bTerminating) { int actuallyRead = myMicrophoneLine.read(toSend, 0, bufferSize); d.setData(toSend,0,actuallyRead); synchronized (currentReceivers) { for(InetSocketAddress addr:currentReceivers) { d.setSocketAddress(addr); try { socket.send(d); } catch (IOException e) { e.printStackTrace(); } } } } . . . }
优化方案建议
使用CopyOnWriteArrayList替代同步ArrayList:
这个容器的核心特性是,每次修改操作(增/删)都会复制一份新的底层数组,读操作完全不需要加锁。正好匹配你的场景:列表修改频率极低,读操作(发送时遍历列表)非常频繁。发送线程遍历的始终是当前列表的快照,即使遍历过程中列表被修改,也只会用到修改前的地址,少量丢包完全在可接受范围内,完美契合你的需求。替换后可以直接去掉synchronized块,代码更简洁,性能开销大幅降低。手动快照优化:
如果不想引入CopyOnWriteArrayList,也可以手动在每次发送前获取列表的快照。比如在同步块里只做一次列表拷贝,然后遍历拷贝后的列表发送:List<InetSocketAddress> snapshot; synchronized (currentReceivers) { snapshot = new ArrayList<>(currentReceivers); } for(InetSocketAddress addr : snapshot) { d.setSocketAddress(addr); try { socket.send(d); } catch (IOException e) { e.printStackTrace(); } }这样同步块的执行时间大幅缩短,只做拷贝操作,而遍历发送的过程不需要加锁,能减少锁的持有时间,降低开销。
内容的提问来源于stack exchange,提问作者Allahjane
相关产品推荐
相关产品推荐

