Java ConcurrentModificationException异常分析

项目中有一个需求:

服务器在检测到设备接入后给设备下发配置。在服务器异常重启的场景下,服务器检测到终端建链时底层通信模块还没有初始化完成,此时需要将已经建链设备的建链通知消息缓存起来,创建线程,待通信模块初始化完成后给设备下发配置。因频繁涉及节点增删,这里用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.ConcurrentModificationException
	at 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);
            }
        }
    }
}

 

新手代码,是不是还有很多问题啊大笑

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值