Java ConcurrentModificationException异常分析
来源:互联网 发布:阿里云邮的企业版 编辑:程序博客网 时间:2024/05/16 00:44
项目中有一个需求:
服务器在检测到设备接入后给设备下发配置。在服务器异常重启的场景下,服务器检测到终端建链时底层通信模块还没有初始化完成,此时需要将已经建链设备的建链通知消息缓存起来,创建线程,待通信模块初始化完成后给设备下发配置。因频繁涉及节点增删,这里用LinkedList缓存触发建链通知的设备。
代码如下,在运行时抛出java.util.ConcurrentModificationException异常:
package com.ancha.helloworld;import java.util.LinkedList;import com.ancha.oss.api.util.logging.DebugPrn;public class LinkStateReadyAction{ private static DebugPrn debugPrn = new DebugPrn(LinkStateReadyAction.class.getName()); private static LinkStateReadyAction instance = null; private LinkedList<String> linkReadyNes = new LinkedList<String>(); private static final long WAIT_FOR_LINK_STATE_READY = 5000; private static final long ACTION_PEROID = 100; private LinkStateReadyAction() { Thread thread = new NeStateReadyProc(); thread.setName("Link-State-Ready-Proc"); thread.start(); } public static synchronized LinkStateReadyAction getInstance() { if (instance == null) { instance = new LinkStateReadyAction(); } return instance; } public void addNe(String ne) { linkReadyNes.add(ne); //此处应该有同步保护 } private class NeStateReadyProc extends Thread { public void run() { try { Thread.sleep(WAIT_FOR_LINK_STATE_READY); while (true) { try { synchronized (linkReadyNes) { if (linkReadyNes.size() > 0) { for (String ne : linkReadyNes) //linkReadyNes中节点较多时,线程耗时较长,新的终端建链消息阻塞在addNe方法 { debugPrn.info(ne); linkReadyNes.remove(ne); // WARN: 在for循环中,不允许进行节点的增删操作! } } } } catch (Exception e) { debugPrn.info("exception in ne state ready proc: ", e); } finally { try { Thread.sleep(ACTION_PEROID); } catch (Exception e) { debugPrn.warn("sleep error: ", e); } } } } catch (Exception e) { debugPrn.warn("sleep error: ", e); } } } public static void main(String[] args) { debugPrn.info(" T-E-S-T "); int i = 0; while (true) { try { LinkStateReadyAction.getInstance().addNe(String.valueOf(i++)); Thread.sleep(ACTION_PEROID); } catch (InterruptedException e) { debugPrn.info("", e); } } }}
抛出的异常:
2015-08-21 10:47:14,718 INFO [com.ancha.helloworld.LinkStateReadyAction] exception in ne state ready proc: java.util.ConcurrentModificationExceptionat java.util.LinkedList$ListItr.checkForComodification(LinkedList.java:761)at java.util.LinkedList$ListItr.next(LinkedList.java:696)at com.ancha.helloworld.LinkStateReadyAction$NeStateReadyProc.run(LinkStateReadyAction.java:56)
代码存在的问题:
1、由于服务器需要支持10万台设备的并发容量,当服务器重启后,短时间内会有大量设备建链并触发建链消息,List没有长度限制会有内存溢出的风险。
2、addNe中没有检查是否已经存在相同的元素,网元频繁断链时会导致List中添加大量相同的元素。
3、addNe()对List的操作没有添加同步保护,会抛出ConcurrentModificationException异常!
4、在for循环中遍历所有元素,给每个终端下发配置,会导致线程耗时过长,在同步保护下,主线程addNe操作会被长时间阻塞,在不允许长时间阻塞的消息响应机制里,消息时序会被打乱。
5、慎重在for循环中执行集合元素的增删操作。
for循环中对List执行执行remove操作,每当删除一个元素时,集合的size方法的值都会减小1,这将直接导致集合中元素的索引重新排序,进一步说,就是剩余所有元素的索引值都减1,如下图所示,而for循环语句的局部变量i仍然在递增,这将导致删除操作发生跳跃,遗漏元素。
修改后代码如下:
package com.ancha.helloworld;import java.util.LinkedList;import com.ancha.oss.api.util.logging.DebugPrn;public class LinkStateReadyAction{ private static DebugPrn debugPrn = new DebugPrn(LinkStateReadyAction.class.getName()); private static LinkStateReadyAction instance = null; private LinkedList<String> linkReadyNes = new LinkedList<String>(); private static final long WAIT_FOR_LINK_STATE_READY = 5000; private static final long ACTION_PEROID = 100; private LinkStateReadyAction() { Thread thread = new NeStateReadyProc(); thread.setName("Link-State-Ready-Proc"); thread.start(); } public static synchronized LinkStateReadyAction getInstance() { if (instance == null) { instance = new LinkStateReadyAction(); } return instance; } public void addNe(String ne) { synchronized (linkReadyNes) { linkReadyNes.add(ne); //此处应该有同步保护 } } private class NeStateReadyProc extends Thread { public void run() { try { Thread.sleep(WAIT_FOR_LINK_STATE_READY); while (true) { try { synchronized (linkReadyNes) { if (linkReadyNes.size() > 0) { for (String ne : linkReadyNes) //linkReadyNes中节点较多时,线程耗时较长,新的终端建链消息阻塞在addNe方法 { debugPrn.info(ne); linkReadyNes.remove(ne); // WARN: 在for循环中,不允许进行节点的增删操作! } } } } catch (Exception e) { debugPrn.info("exception in ne state ready proc: ", e); } finally { try { Thread.sleep(ACTION_PEROID); } catch (Exception e) { debugPrn.warn("sleep error: ", e); } } } } catch (Exception e) { debugPrn.warn("sleep error: ", e); } } } public static void main(String[] args) { debugPrn.info(" T-E-S-T "); int i = 0; while (true) { try { LinkStateReadyAction.getInstance().addNe(String.valueOf(i++)); Thread.sleep(ACTION_PEROID); } catch (InterruptedException e) { debugPrn.info("", e); } } }}
修改后代码解决了前面提到的问题2、3、4、5,但问题1依然存在 。
重构代码,其中LinkedBlockingQueue是一个线程安全的队列。
package com.ancha.helloworld;import java.util.concurrent.LinkedBlockingQueue;import java.util.concurrent.TimeUnit;import com.ancha.oss.api.util.logging.DebugPrn;public class LinkReadyActionRefactor{ private static DebugPrn debugPrn = new DebugPrn( LinkReadyActionRefactor.class.getName()); private static LinkReadyActionRefactor instance = null; private LinkedBlockingQueue<String> linkReadyNes = new LinkedBlockingQueue<String>(); private static final long WAIT_FOR_LINK_STATE_READY = 1000; private static final long ACTION_PEROID = 10; private LinkReadyActionRefactor() { } public static synchronized LinkReadyActionRefactor getInstance() { if (instance == null) { instance = new LinkReadyActionRefactor(); instance.initNeStateReadyProc(); } return instance; } private void initNeStateReadyProc() { Thread thread = new NeStateReadyProc(); thread.setName("Link-State-Ready-Proc"); thread.start(); } public void addNe(String ne) { try { linkReadyNes.put(ne); } catch (InterruptedException e) { debugPrn.warn("insert ne failed.", e); } } public String popNe() { try { //等待30分钟,如果30分钟内队列无数据,则返回null. return linkReadyNes.poll(30, TimeUnit.MINUTES); } catch (InterruptedException e) { debugPrn.warn("pop ne failed, ", e); return null; } } private class NeStateReadyProc extends Thread { public void run() { try { Thread.sleep(WAIT_FOR_LINK_STATE_READY); while (true) { try { String ne = popNe(); if (ne != null) { //NeStateReadyOperation(ne); debugPrn.info(ne); } } catch (Exception e) { debugPrn.info("ne state ready action failed. ", e); } } } catch (InterruptedException e) { debugPrn.warn("sleep error: ", e); } } } public static void main(String[] args) { debugPrn.info(" T-E-S-T "); int i = 0; while (true) { try { LinkReadyActionRefactor.getInstance().addNe(String.valueOf(i++)); Thread.sleep(ACTION_PEROID); } catch (InterruptedException e) { debugPrn.info("", e); } } }}
新手代码,是不是还有很多问题啊
- Java ConcurrentModificationException异常分析
- java.util.ConcurrentModificationException异常分析
- java.util.ConcurrentModificationException异常分析
- java中ConcurrentModificationException异常分析
- java.util.ConcurrentModificationException异常分析
- java.util.ConcurrentModificationException异常分析
- java.util.ConcurrentModificationException异常 分析
- java.util.ConcurrentModificationException 异常的分析
- Java ConcurrentModificationException 异常分析与解决方案
- Java ConcurrentModificationException 异常分析与解决方案
- Java ConcurrentModificationException 异常分析与解决方案
- Java ConcurrentModificationException 异常分析与解决方案
- Java ConcurrentModificationException 异常分析与解决方案
- Java ConcurrentModificationException 异常分析与解决方案
- Java ConcurrentModificationException 异常分析与解决方案
- (转)Java ConcurrentModificationException 异常分析与解决方案
- Java ConcurrentModificationException 异常分析与解决方案
- Java ConcurrentModificationException 异常分析与解决方案
- Android中Button设置drawablePadding没效果和设置selector后按下时没效果
- PHP Curl post上传图片版本不兼容而引起服务器端接收不到图片问题。
- 文章标题
- mysql日期函数
- KMP算法的next、next value数组的手工计算
- Java ConcurrentModificationException异常分析
- 如何减少Linux服务器TIME_WAIT过多
- Android中UID与PID的作用与区别
- android 动态加载布局
- selenium(webdriver)学习笔记1--ChromeDriver
- opencv_objdetect249d.lib未加的错误 CascadeClassifier::~CascadeClassifier
- 图解Javascript上下文与作用域
- PHP--froeach语句
- Leetcode#7||Reverse Integer